From aa5403c8c3660f9caaa80c5deba99ed9c0ef6752 Mon Sep 17 00:00:00 2001 From: Luigi Morel Date: Fri, 12 Sep 2025 14:06:19 +0300 Subject: [PATCH 1/2] add github action and linter --- .github/workflows/lint.yml | 59 +++++++++++++++++++++++++++++++++ .golangci.yml | 68 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 .github/workflows/lint.yml create mode 100644 .golangci.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..022dd85 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,59 @@ +name: Lint + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.21.13" + + - name: Verify dependencies + run: go mod verify + + - name: Build + run: go build -v ./... + + - name: Run go vet + run: go vet ./... + + - name: Install staticcheck + run: go install honnef.co/go/tools/cmd/staticcheck@latest + + - name: Run staticcheck + run: staticcheck ./... + + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: latest + args: --timeout=5m + + - name: Check formatting + run: | + if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then + echo "The following files are not formatted:" + gofmt -s -l . + exit 1 + fi + + - name: Check go mod tidy + run: | + go mod tidy + if [ -n "$(git status --porcelain)" ]; then + echo "go mod tidy resulted in changes" + git diff + exit 1 + fi diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..4feba06 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,68 @@ +run: + timeout: 5m + issues-exit-code: 1 + tests: true + modules-download-mode: readonly + +output: + formats: + - format: colored-line-number + +linters-settings: + depguard: + rules: + main: + files: + - $all + allow: + - $gostd + - github.com/luigimorel/gogen + - github.com/urfave/cli/v2 + gofmt: + simplify: true + goimports: + local-prefixes: github.com/luigimorel/gogen + misspell: + locale: US + +linters: + enable: + - errcheck + - gosimple + - govet + - ineffassign + - staticcheck + - unused + - gofmt + - goimports + - misspell + - goconst + - gosec + - unconvert + - dupl + - gocritic + - gocyclo + - whitespace + - bodyclose + - depguard + - dogsled + + disable: + - funlen + - gochecknoglobals + - gocognit + - godot + - godox + - nestif + +issues: + exclude-rules: + - path: _test\.go + linters: + - gosec + - dupl + - linters: + - lll + source: "^//go:generate " + max-issues-per-linter: 0 + max-same-issues: 0 From 2bec9e89d5a96a60f6140e1bb4c5807b67036260 Mon Sep 17 00:00:00 2001 From: Luigi Morel Date: Sat, 13 Sep 2025 20:06:05 +0300 Subject: [PATCH 2/2] refactor: improve file permissions and fix lint errors --- Makefile | 2 +- cmd/frontend.go | 23 +++++++----- cmd/install.go | 37 ++++++++++++++------ cmd/new.go | 24 +++++++++---- cmd/router.go | 36 +++++++++++-------- internal/create_frontend.go | 60 ++++++++++++++++++++----------- internal/env-file.go | 8 ++--- internal/llm.go | 70 ++++++++++++++++++------------------- internal/project.go | 16 +++++---- internal/tailwindcss.go | 48 +++++++++++++------------ 10 files changed, 196 insertions(+), 128 deletions(-) diff --git a/Makefile b/Makefile index 95821d9..dd557e4 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ test-coverage: go test -cover ./... fmt: - go fmt ./... + go fmt ./... && goimports -w . && gofmt -s -w . lint: golangci-lint run diff --git a/cmd/frontend.go b/cmd/frontend.go index 4911a0c..554991c 100644 --- a/cmd/frontend.go +++ b/cmd/frontend.go @@ -5,8 +5,15 @@ import ( "os/exec" "strings" - "github.com/luigimorel/gogen/internal" "github.com/urfave/cli/v2" + + "github.com/luigimorel/gogen/internal" +) + +// Runtime constants +const ( + node = "node" + bun = "bun" ) type FrontendManager struct { @@ -113,14 +120,14 @@ func (fm *FrontendManager) execute() error { func (fm *FrontendManager) validateSetup() error { switch fm.Runtime { - case "node": + case node: if !fm.commandExists("node") { return fmt.Errorf("node.js is required but not installed. Please install Node.js from https://nodejs.org/") } if !fm.commandExists("npm") { return fmt.Errorf("npm is required but not installed. Please install npm") } - case "bun": + case bun: if !fm.commandExists("bun") { return fmt.Errorf("bun is required but not installed. Please install Bun from https://bun.sh/") } @@ -130,13 +137,13 @@ func (fm *FrontendManager) validateSetup() error { switch fm.FrameworkType { case "angular": - if fm.Runtime == "node" && !fm.commandExists("ng") { + if fm.Runtime == node && !fm.commandExists("ng") { fmt.Println("Angular CLI not found. Installing @angular/cli globally...") var cmd *exec.Cmd switch fm.Runtime { - case "node": + case node: cmd = exec.Command("npm", "install", "-g", "@angular/cli") - case "bun": + case bun: cmd = exec.Command("bun", "add", "-g", "@angular/cli") } @@ -167,9 +174,9 @@ func (fm *FrontendManager) printInstructions() { var devCommand string switch fm.Runtime { - case "node": + case node: devCommand = "npm run dev" - case "bun": + case bun: devCommand = "bun run dev" default: devCommand = "npm run dev" diff --git a/cmd/install.go b/cmd/install.go index 003ee23..b509ed5 100644 --- a/cmd/install.go +++ b/cmd/install.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "net/http" + "net/url" "os" "os/exec" "path/filepath" @@ -13,6 +14,10 @@ import ( "github.com/urfave/cli/v2" ) +const ( + PlatformWindows = "windows" +) + type Installer struct { Method string Force bool @@ -86,7 +91,7 @@ func (i *Installer) autoInstall() error { return i.nixInstall() } return i.binaryInstall() - case "windows": + case PlatformWindows: return i.binaryInstall() default: return fmt.Errorf("unsupported operating system: %s", runtime.GOOS) @@ -114,7 +119,7 @@ func (i *Installer) binaryInstall() error { return fmt.Errorf("failed to download binary: %w", err) } - if runtime.GOOS != "windows" { + if runtime.GOOS != PlatformWindows { if err := os.Chmod(binPath, 0755); err != nil { return fmt.Errorf("failed to make binary executable: %w", err) } @@ -156,7 +161,7 @@ func (i *Installer) fileExists(path string) bool { func (i *Installer) getBinaryInstallDir() string { switch runtime.GOOS { - case "windows": + case PlatformWindows: // Use %USERPROFILE%\AppData\Local\gogen home, _ := os.UserHomeDir() return filepath.Join(home, "AppData", "Local", "gogen") @@ -168,7 +173,7 @@ func (i *Installer) getBinaryInstallDir() string { } func (i *Installer) getBinaryName() string { - if runtime.GOOS == "windows" { + if runtime.GOOS == PlatformWindows { return "gogen.exe" } return "gogen" @@ -184,15 +189,24 @@ func (i *Installer) getDownloadURL() string { version := "latest" filename := fmt.Sprintf("gogen_%s_%s", os, arch) - if runtime.GOOS == "windows" { + if runtime.GOOS == PlatformWindows { filename += ".exe" } return fmt.Sprintf("https://github.com/luigimorel/gogen/releases/%s/download/%s", version, filename) } -func (i *Installer) downloadFile(url, filepath string) error { - resp, err := http.Get(url) +func (i *Installer) downloadFile(downloadURL, filepath string) error { + u, err := url.Parse(downloadURL) + if err != nil { + return fmt.Errorf("invalid URL: %w", err) + } + + if !strings.HasPrefix(u.String(), "https://github.com/luigimorel/gogen/releases/") { + return fmt.Errorf("invalid download URL: %s", u.String()) + } + + resp, err := http.Get(u.String()) if err != nil { return err } @@ -214,7 +228,7 @@ func (i *Installer) downloadFile(url, filepath string) error { func (i *Installer) printPathInstructions(binDir string) { switch runtime.GOOS { - case "windows": + case PlatformWindows: fmt.Println("\nTo add gogen to your PATH:") fmt.Println("1. Press Win + R, type 'sysdm.cpl', and press Enter") fmt.Println("2. Click 'Environment Variables'") @@ -224,13 +238,14 @@ func (i *Installer) printPathInstructions(binDir string) { fmt.Println("6. Restart your terminal and run 'gogen --help'") default: shell := os.Getenv("SHELL") - if strings.Contains(shell, "fish") { + switch { + case strings.Contains(shell, "fish"): fmt.Printf("\nTo add gogen to your PATH, run:\n") fmt.Printf("fish_add_path %s\n", binDir) - } else if strings.Contains(shell, "zsh") { + case strings.Contains(shell, "zsh"): fmt.Printf("\nTo add gogen to your PATH, add this to your ~/.zshrc:\n") fmt.Printf("export PATH=\"%s:$PATH\"\n", binDir) - } else { + default: fmt.Printf("\nTo add gogen to your PATH, add this to your ~/.bashrc or ~/.profile:\n") fmt.Printf("export PATH=\"%s:$PATH\"\n", binDir) } diff --git a/cmd/new.go b/cmd/new.go index 2723e66..d4187a4 100644 --- a/cmd/new.go +++ b/cmd/new.go @@ -5,8 +5,14 @@ import ( "os" "os/exec" - "github.com/luigimorel/gogen/internal" "github.com/urfave/cli/v2" + + "github.com/luigimorel/gogen/internal" +) + +// Template constants +const ( + TemplateWeb = "web" ) type ProjectCreator struct { @@ -114,7 +120,7 @@ This command will create a new directory, initialize a Go module, and create a n // Check if runtime was explicitly set by user runtimeExplicitlySet := c.IsSet("runtime") - if runtimeExplicitlySet && template != "web" { + if runtimeExplicitlySet && template != TemplateWeb { return fmt.Errorf("runtime flag is only applicable when template is 'web'") } @@ -163,7 +169,7 @@ func (pc *ProjectCreator) execute() error { } func (pc *ProjectCreator) validate() error { - if pc.FrontendFramework != "" && pc.Template != "web" { + if pc.FrontendFramework != "" && pc.Template != TemplateWeb { return fmt.Errorf("frontend flag is only applicable when template is 'web'") } @@ -201,7 +207,7 @@ func (pc *ProjectCreator) ChangeToProjectDirectory() (string, func(), error) { } func (pc *ProjectCreator) initializeGoModule() error { - if pc.Template != "web" { + if pc.Template != TemplateWeb { moduleName := pc.ModuleName if moduleName == "" { moduleName = pc.Name @@ -222,7 +228,7 @@ func (pc *ProjectCreator) createProjectFiles() error { switch pc.Template { case "cli": return pg.CreateCLIProject(pc.Name, pc.ModuleName) - case "web": + case TemplateWeb: return pg.CreateWebProject(pc.Name, pc.ModuleName, pc.Router, pc.FrontendFramework, pc.Runtime, pc.UseTypeScript, pc.UseTailwind) case "api": return pg.CreateAPIProject(pc.Name, pc.ModuleName, pc.Router) @@ -235,7 +241,11 @@ func (pc *ProjectCreator) createEditorLLMRules() error { if err := os.Chdir(".."); err != nil { return fmt.Errorf("failed to change to project root directory: %w", err) } - defer os.Chdir(pc.DirName) + defer func() { + if err := os.Chdir(pc.DirName); err != nil { + fmt.Fprintf(os.Stderr, "Warning: failed to change back to %s directory: %v\n", pc.DirName, err) + } + }() llmTemplate := internal.NewLLMTemplate() return llmTemplate.CreateTemplate(pc.Editor, pc.FrontendFramework, pc.Runtime, pc.Router) @@ -245,7 +255,7 @@ func (pc *ProjectCreator) printNextSteps() { fmt.Println("\nNext steps:") fmt.Printf(" cd %s\n", pc.Name) - if pc.Template == "web" { + if pc.Template == TemplateWeb { fmt.Println(" cd api") fmt.Println(" go run main.go") if pc.FrontendFramework != "" { diff --git a/cmd/router.go b/cmd/router.go index bfc8897..109d1d6 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -8,6 +8,14 @@ import ( "github.com/urfave/cli/v2" ) +// Router type constants +const ( + RouterStdlib = "stdlib" + RouterChi = "chi" + RouterGorilla = "gorilla" + RouterHttpRouter = "httprouter" +) + type Router struct { Type string UpdateMain bool @@ -92,14 +100,14 @@ func (r *Router) installDependency() error { var dependency string switch r.Type { - case "stdlib": + case RouterStdlib: fmt.Println("Using standard library http.ServeMux - no additional dependency needed") return nil - case "chi": + case RouterChi: dependency = "github.com/go-chi/chi/v5" - case "gorilla": + case RouterGorilla: dependency = "github.com/gorilla/mux" - case "httprouter": + case RouterHttpRouter: dependency = "github.com/julienschmidt/httprouter" default: return fmt.Errorf("unsupported router type: %s", r.Type) @@ -127,10 +135,10 @@ func (r *Router) updateMainFile() error { newContent := r.generateMainContent(string(mainContent)) backupPath := "main.go.backup" - if err := os.WriteFile(backupPath, mainContent, 0644); err != nil { + if err := os.WriteFile(backupPath, mainContent, 0600); err != nil { fmt.Printf("Warning: failed to create backup at %s: %v\n", backupPath, err) } - if err := os.WriteFile("main.go", []byte(newContent), 0644); err != nil { + if err := os.WriteFile("main.go", []byte(newContent), 0600); err != nil { return fmt.Errorf("failed to write updated main.go: %w", err) } @@ -139,13 +147,13 @@ func (r *Router) updateMainFile() error { func (r *Router) generateMainContent(existingContent string) string { switch r.Type { - case "stdlib": + case RouterStdlib: return r.generateServeMuxContent() - case "chi": + case RouterChi: return r.generateChiContent() - case "gorilla": + case RouterGorilla: return r.generateGorillaContent() - case "httprouter": + case RouterHttpRouter: return r.generateHttpRouterContent() default: return existingContent @@ -159,22 +167,22 @@ func (r *Router) printInstructions() { fmt.Println(" curl http://localhost:8080/api/health") switch r.Type { - case "chi": + case RouterChi: fmt.Println("\nChi router features:") fmt.Println(" - Built-in middleware (Logger, Recoverer, RequestID)") fmt.Println(" - Route groups and subrouting") fmt.Println(" - Fast and lightweight") - case "gorilla": + case RouterGorilla: fmt.Println("\nGorilla Mux features:") fmt.Println(" - Path variables: r.HandleFunc(\"/users/{id}\", handler)") fmt.Println(" - Query parameter matching") fmt.Println(" - Host and scheme matching") - case "httprouter": + case RouterHttpRouter: fmt.Println("\nHttpRouter features:") fmt.Println(" - Extremely fast performance") fmt.Println(" - Path parameters: router.GET(\"/users/:id\", handler)") fmt.Println(" - Zero memory allocation") - case "stdlib": + case RouterStdlib: fmt.Println("\nhttp.ServeMux features:") fmt.Println(" - Part of Go standard library") fmt.Println(" - Simple and reliable") diff --git a/internal/create_frontend.go b/internal/create_frontend.go index 1681b73..05605fe 100644 --- a/internal/create_frontend.go +++ b/internal/create_frontend.go @@ -6,17 +6,30 @@ import ( "os/exec" ) +const ( + react = "react" + vue = "vue" + svelte = "svelte" + solidjs = "solidjs" + angular = "angular" +) + +const ( + bun = "bun" + node = "node" +) + func (pg *ProjectGenerator) CreateFrontendProject(framework, dirName string, useTypeScript bool, runtime string, useTailwind bool) error { fmt.Printf("DEBUG: Creating frontend project with runtime: %s, framework: %s, dir: %s, typescript: %v\n", runtime, framework, dirName, useTypeScript) allowPrompts := "--" - if runtime == "bun" { + if runtime == bun { allowPrompts = "" } packageManager := map[string]string{ - "bun": "bun", - "node": "npm", + bun: bun, + node: "npm", }[runtime] //TODO: Remove directory if it exists? @@ -27,35 +40,35 @@ func (pg *ProjectGenerator) CreateFrontendProject(framework, dirName string, use var cmd *exec.Cmd switch framework { - case "react": - template := "react" + case react: + template := react if useTypeScript { template = "react-ts" } cmd = pg.getCreateCommand(packageManager, "create", "vite@latest", dirName, allowPrompts, "--template", template) - case "vue": + case vue: args := []string{"create", "vue@latest", dirName, allowPrompts, "--jsx", "--router", "--pinia", "--vitest", "--playwright", "--eslint", "--prettier"} if useTypeScript { args = append(args, "--ts") } cmd = pg.getCreateCommand(packageManager, args...) - case "svelte": + case svelte: mode := "jsdoc" if useTypeScript { mode = "ts" } cmd = pg.getSvelteCommand(packageManager, dirName, mode) - case "solidjs": + case solidjs: mode := "js" if useTypeScript { mode = "ts" } cmd = pg.getSolidCommand(packageManager, dirName, mode) - case "angular": + case angular: args := []string{"new", dirName, "--routing=true", "--style=css", "--skip-git=true", "--package-manager=" + packageManager} if useTypeScript { args = append(args, "--strict=true") @@ -75,7 +88,7 @@ func (pg *ProjectGenerator) CreateFrontendProject(framework, dirName string, use } // Install dependencies for non-Angular projects (Angular CLI handles this automatically) - if framework != "angular" { + if framework != angular { if err := pg.installDependencies(runtime, dirName); err != nil { return err } @@ -117,12 +130,12 @@ func (pg *ProjectGenerator) installDependencies(runtime, dirName string) error { func (pg *ProjectGenerator) getCreateCommand(runtime string, args ...string) *exec.Cmd { switch runtime { - case "bun": + case bun: if args[0] == "create" { bunArgs := append([]string{"create"}, args[1:]...) - return exec.Command("bun", bunArgs...) + return exec.Command(bun, bunArgs...) } - return exec.Command("bun", args...) + return exec.Command(bun, args...) default: return exec.Command("npm", args...) } @@ -130,12 +143,12 @@ func (pg *ProjectGenerator) getCreateCommand(runtime string, args ...string) *ex func (pg *ProjectGenerator) getSvelteCommand(runtime, dirName, typeOption string) *exec.Cmd { switch runtime { - case "bun": + case bun: return exec.Command("bunx", "sv", "create", dirName, "--template", "minimal", "--types", typeOption, "--no-add-ons", - "--install", "bun") + "--install", bun) default: return exec.Command("npx", "sv", "create", dirName, "--template", "minimal", @@ -146,18 +159,25 @@ func (pg *ProjectGenerator) getSvelteCommand(runtime, dirName, typeOption string } func (pg *ProjectGenerator) getSolidCommand(runtime, dirName, template string) *exec.Cmd { + // Validate template to prevent command injection + if template != "js" && template != "ts" { + template = "js" // default to safe value + } + + templatePath := "solidjs/templates/" + template + switch runtime { - case "bun": - return exec.Command("bunx", "--yes", "degit", fmt.Sprintf("solidjs/templates/%s", template), dirName, "--force") + case bun: + return exec.Command("bunx", "--yes", "degit", templatePath, dirName, "--force") default: - return exec.Command("npx", "--yes", "degit", fmt.Sprintf("solidjs/templates/%s", template), dirName, "--force") + return exec.Command("npx", "--yes", "degit", templatePath, dirName, "--force") } } func (pg *ProjectGenerator) getInstallCommand(runtime string) *exec.Cmd { switch runtime { - case "bun": - return exec.Command("bun", "install") + case bun: + return exec.Command(bun, "install") default: return exec.Command("npm", "install") } diff --git a/internal/env-file.go b/internal/env-file.go index 0c1ab8a..7e5a656 100644 --- a/internal/env-file.go +++ b/internal/env-file.go @@ -31,11 +31,11 @@ VITE_NODE_ENV=development return fmt.Errorf("failed to create directory for env files: %w", err) } - if err := os.WriteFile(envExamplePath, []byte(envContent), 0644); err != nil { + if err := os.WriteFile(envExamplePath, []byte(envContent), 0600); err != nil { return fmt.Errorf("failed to create .env.example: %w", err) } - if err := os.WriteFile(envPath, []byte(envContent), 0644); err != nil { + if err := os.WriteFile(envPath, []byte(envContent), 0600); err != nil { return fmt.Errorf("failed to create .env: %w", err) } @@ -43,7 +43,7 @@ VITE_NODE_ENV=development } func (pg *ProjectGenerator) CreateEnvConfig(dirName, framework string, useTypeScript bool) error { - if framework == "angular" { + if framework == angular { return nil } @@ -117,7 +117,7 @@ dist-ssr return fmt.Errorf("failed to create directory for .gitignore: %w", err) } - if err := os.WriteFile(gitignorePath, []byte(gitignoreContent), 0644); err != nil { + if err := os.WriteFile(gitignorePath, []byte(gitignoreContent), 0600); err != nil { return fmt.Errorf("failed to create .gitignore in %s: %w", dirName, err) } diff --git a/internal/llm.go b/internal/llm.go index ce4ad66..ec15458 100644 --- a/internal/llm.go +++ b/internal/llm.go @@ -33,7 +33,7 @@ func (lt *LLMTemplate) CreateTemplate(template, frontendFramework, runtime, rout return fmt.Errorf("unsupported template: %s", template) } - return os.WriteFile(filePath, []byte(content), 0644) + return os.WriteFile(filePath, []byte(content), 0600) } func (lt *LLMTemplate) generateCursorContent(frontendFramework, runtime, router string) string { @@ -51,34 +51,34 @@ This is a Go project using: - Frontend: %s framework`, frontendFramework) switch frontendFramework { - case "react": + case react: baseContent += ` - React components with hooks and modern patterns - JSX/TSX for component templates` - case "vue": + case vue: baseContent += ` - Vue 3 composition API - Single File Components (SFC)` - case "svelte": + case svelte: baseContent += ` - Svelte components with reactive statements - SvelteKit for full-stack applications` - case "solidjs": + case solidjs: baseContent += ` - SolidJS with fine-grained reactivity - JSX templating with solid patterns` - case "angular": + case angular: baseContent += ` - Angular with TypeScript - Component-based architecture with dependency injection` } } - if runtime != "" && runtime != "node" { + if runtime != "" && runtime != node { baseContent += fmt.Sprintf(` - JavaScript runtime: %s`, runtime) - if runtime == "bun" { + if runtime == bun { baseContent += ` - Fast package management and bundling with Bun - TypeScript support out of the box` @@ -144,7 +144,7 @@ This is a Go project using: - Implement loading states and error boundaries` switch frontendFramework { - case "react": + case react: content += ` ### React-Specific Rules @@ -153,7 +153,7 @@ This is a Go project using: - Use proper key props for list items - Implement proper cleanup in useEffect - Use TypeScript for better type safety (if enabled)` - case "vue": + case vue: content += ` ### Vue-Specific Rules @@ -161,7 +161,7 @@ This is a Go project using: - Follow Vue 3 best practices - Use proper reactive references and computed properties - Implement proper component lifecycle management` - case "svelte": + case svelte: content += ` ### Svelte-Specific Rules @@ -169,7 +169,7 @@ This is a Go project using: - Follow Svelte best practices for component communication - Use stores for global state management - Implement proper component lifecycle` - case "solidjs": + case solidjs: content += ` ### SolidJS-Specific Rules @@ -177,7 +177,7 @@ This is a Go project using: - Follow SolidJS patterns for reactivity - Implement proper resource management - Use JSX patterns specific to SolidJS` - case "angular": + case angular: content += ` ### Angular-Specific Rules @@ -187,7 +187,7 @@ This is a Go project using: - Implement proper component lifecycle hooks` } - if runtime == "bun" { + if runtime == bun { content += ` ### Bun Runtime Guidelines @@ -218,17 +218,17 @@ func (lt *LLMTemplate) generateVSCodeContent(frontendFramework, runtime, router "css": true` switch frontendFramework { - case "react": + case react: baseSettings += `, "javascriptreact": true, "typescriptreact": true` - case "vue": + case vue: baseSettings += `, "vue": true` - case "svelte": + case svelte: baseSettings += `, "svelte": true` - case "angular": + case angular: baseSettings += `, "html": true` } @@ -266,7 +266,7 @@ func (lt *LLMTemplate) generateVSCodeContent(frontendFramework, runtime, router if frontendFramework != "" { switch frontendFramework { - case "react": + case react: baseSettings += `, "typescript.preferences.includePackageJsonAutoImports": "on", "typescript.suggest.autoImports": true, @@ -276,23 +276,23 @@ func (lt *LLMTemplate) generateVSCodeContent(frontendFramework, runtime, router "typescript": "typescriptreact" }, "emmet.triggerExpansionOnTab": true` - case "vue": + case vue: baseSettings += `, "vetur.validation.template": false, "vetur.validation.script": false, "vetur.validation.style": false, "volar.takeOverMode": true` - case "svelte": + case svelte: baseSettings += `, "svelte.enable-ts-plugin": true, "typescript.preferences.includePackageJsonAutoImports": "on"` - case "angular": + case angular: baseSettings += `, "typescript.preferences.includePackageJsonAutoImports": "on", "angular.enableCodeCompletion": true` } - if runtime == "bun" { + if runtime == bun { baseSettings += `, "terminal.integrated.defaultProfile.linux": "bash", "npm.packageManager": "bun"` @@ -320,19 +320,19 @@ This is a Go project with the following characteristics: - Frontend framework: %s`, frontendFramework) switch frontendFramework { - case "react": + case react: baseContent += ` with modern hooks and functional components` - case "vue": + case vue: baseContent += ` with Composition API and SFC` - case "svelte": + case svelte: baseContent += ` with reactive statements and SvelteKit` - case "solidjs": + case solidjs: baseContent += ` with fine-grained reactivity` - case "angular": + case angular: baseContent += ` with TypeScript and dependency injection` } - if runtime == "bun" { + if runtime == bun { baseContent += fmt.Sprintf(` - JavaScript runtime: %s for fast package management and execution`, runtime) } @@ -401,33 +401,33 @@ This is a Go project with the following characteristics: ### Frontend Best Practices` switch frontendFramework { - case "react": + case react: content += ` - Use functional components with hooks - Implement proper error boundaries - Use React.memo for performance optimization - Follow React testing library best practices - Use proper TypeScript types when applicable` - case "vue": + case vue: content += ` - Use Composition API for new components - Implement proper reactive state management - Use Vue 3 best practices for component communication - Follow Vue testing utils conventions - Ensure proper component lifecycle management` - case "svelte": + case svelte: content += ` - Use reactive statements ($:) appropriately - Implement proper store patterns for state management - Follow SvelteKit conventions for routing and data loading - Use proper component communication patterns` - case "solidjs": + case solidjs: content += ` - Use signals and effects properly - Implement proper resource management - Follow SolidJS patterns for reactivity - Use proper JSX patterns specific to SolidJS` - case "angular": + case angular: content += ` - Follow Angular style guide conventions - Use proper dependency injection patterns @@ -436,7 +436,7 @@ This is a Go project with the following characteristics: - Follow RxJS best practices for reactive programming` } - if runtime == "bun" { + if runtime == bun { content += ` ### Bun Runtime Optimization diff --git a/internal/project.go b/internal/project.go index d7f14e6..efb8c5a 100644 --- a/internal/project.go +++ b/internal/project.go @@ -113,7 +113,7 @@ func main() { } `, projectName) - if err := os.WriteFile("main.go", []byte(mainContent), 0644); err != nil { + if err := os.WriteFile("main.go", []byte(mainContent), 0600); err != nil { return err } @@ -136,7 +136,9 @@ func (pg *ProjectGenerator) CreateWebProject(projectName, moduleName, router, fr } if err := pg.createConfigFiles(); err != nil { - os.Chdir(originalDir) + if chdirErr := os.Chdir(originalDir); chdirErr != nil { + return fmt.Errorf("failed to create config files and to change back to original directory: %w, %w", err, chdirErr) + } return fmt.Errorf("failed to create config files: %w", err) } @@ -223,17 +225,17 @@ func (pg *ProjectGenerator) createAPIProjectInDir(baseDir, projectName, moduleNa goModPath = "go.mod" } - if err := os.WriteFile(mainGoPath, []byte(mainContent), 0644); err != nil { + if err := os.WriteFile(mainGoPath, []byte(mainContent), 0600); err != nil { return err } - if err := os.WriteFile(routesPath, []byte(routesContent), 0644); err != nil { + if err := os.WriteFile(routesPath, []byte(routesContent), 0600); err != nil { return err } baseModuleName := pg.setModuleName(moduleName, projectName) apiModContent := fmt.Sprintf("module %s\n\ngo 1.21\n", baseModuleName) - if err := os.WriteFile(goModPath, []byte(apiModContent), 0644); err != nil { + if err := os.WriteFile(goModPath, []byte(apiModContent), 0600); err != nil { return err } @@ -255,7 +257,9 @@ func (pg *ProjectGenerator) createAPIProjectInDir(baseDir, projectName, moduleNa cmd := exec.Command("go", "mod", "tidy") if err := cmd.Run(); err != nil { if baseDir != "." { - os.Chdir(originalDir) + if chdirErr := os.Chdir(originalDir); chdirErr != nil { + return fmt.Errorf("failed to tidy go.mod and to change back to original directory: %w, %w", err, chdirErr) + } } return fmt.Errorf("failed to tidy go.mod: %w", err) } diff --git a/internal/tailwindcss.go b/internal/tailwindcss.go index 738432f..f21ab0d 100644 --- a/internal/tailwindcss.go +++ b/internal/tailwindcss.go @@ -56,21 +56,25 @@ func (tc *TailwindConfig) InstallTailwindCSS() error { fmt.Println("✅ Tailwind CSS configured successfully!") return nil } + func (tc *TailwindConfig) tailwindLibInstall(framework, runtime string) error { - if runtime == "node" { + if runtime == node { runtime = "npm" } switch framework { - case "react", "vue", "svelte", "solidjs": + case react, vue, svelte, solidjs: args := append([]string{"add"}, "tailwindcss", "@tailwindcss/vite") return exec.Command(runtime, args...).Run() - case "angular": - if runtime == "bun" { - return exec.Command(runtime, "add", "tailwindcss", "@tailwindcss/postcss", "postcss", "--force").Run() + case angular: + angularArgs := []string{"tailwindcss", "@tailwindcss/postcss", "postcss", "--force"} + if runtime == bun { + args := append([]string{"add"}, angularArgs...) + return exec.Command(runtime, args...).Run() } - return exec.Command("npm", "install", "tailwindcss", "@tailwindcss/postcss", "postcss", "--force").Run() + args := append([]string{"install"}, angularArgs...) + return exec.Command("npm", args...).Run() default: return fmt.Errorf("unsupported framework: %s", framework) @@ -79,12 +83,12 @@ func (tc *TailwindConfig) tailwindLibInstall(framework, runtime string) error { func (tc *TailwindConfig) updateConfigFile(framework string) error { configExt := ".js" - if tc.UseTypeScript && framework != "angular" { + if tc.UseTypeScript && framework != angular { configExt = ".ts" } switch framework { - case "react": + case react: viteConfig := `import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import tailwindcss from '@tailwindcss/vite' @@ -96,8 +100,8 @@ export default defineConfig({ ], }) ` - return os.WriteFile("vite.config"+configExt, []byte(viteConfig), 0644) - case "vue": + return os.WriteFile("vite.config"+configExt, []byte(viteConfig), 0600) + case vue: viteConfig := `import { fileURLToPath, URL } from "node:url"; import vue from "@vitejs/plugin-vue"; @@ -117,8 +121,8 @@ export default defineConfig({ }); ` - return os.WriteFile("vite.config"+configExt, []byte(viteConfig), 0644) - case "svelte": + return os.WriteFile("vite.config"+configExt, []byte(viteConfig), 0600) + case svelte: viteConfig := `import tailwindcss from '@tailwindcss/vite'; import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; @@ -127,8 +131,8 @@ export default defineConfig({ plugins: [tailwindcss(), sveltekit()] }); ` - return os.WriteFile("vite.config"+configExt, []byte(viteConfig), 0644) - case "solidjs": + return os.WriteFile("vite.config"+configExt, []byte(viteConfig), 0600) + case solidjs: viteConfig := `import { defineConfig } from 'vite'; import solidPlugin from 'vite-plugin-solid'; import tailwindcss from '@tailwindcss/vite'; @@ -146,10 +150,10 @@ export default defineConfig({ }, }); ` - return os.WriteFile("vite.config"+configExt, []byte(viteConfig), 0644) + return os.WriteFile("vite.config"+configExt, []byte(viteConfig), 0600) case "angular": postcssConfig := `{ "plugins": { "@tailwindcss/postcss": {} }}` - return os.WriteFile(".postcssrc.json", []byte(postcssConfig), 0644) + return os.WriteFile(".postcssrc.json", []byte(postcssConfig), 0600) } return nil @@ -158,15 +162,15 @@ export default defineConfig({ func (tc *TailwindConfig) updateStylesFile(framework string) error { var cssFile string switch framework { - case "react": + case react: cssFile = "src/index.css" - case "vue": + case vue: cssFile = "src/assets/main.css" - case "svelte": + case svelte: cssFile = "src/main.css" - case "solidjs": + case solidjs: cssFile = "src/index.css" - case "angular": + case angular: cssFile = "src/styles.css" default: return nil @@ -186,5 +190,5 @@ func (tc *TailwindConfig) updateStylesFile(framework string) error { newContent = []byte(tailwindImport) } - return os.WriteFile(cssFile, newContent, 0644) + return os.WriteFile(cssFile, newContent, 0600) }