diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 69e2d22..42b81d4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,9 @@ name: Build on: [push] +env: + GO_VERSION: "1.21" + jobs: linting: @@ -9,14 +12,14 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v9 with: - version: v1.54 + version: latest test: name: Test @@ -24,12 +27,12 @@ jobs: steps: - name: Install Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v6 with: - go-version: 1.21 + go-version: ${{ env.GO_VERSION }} - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v6 with: fetch-depth: 0 @@ -46,33 +49,29 @@ jobs: steps: - name: Install Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v6 with: - go-version: 1.21 + go-version: ${{ env.GO_VERSION }} - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v6 with: fetch-depth: 0 - - name: Login to Docker Hub - uses: docker/login-action@v1 + - name: Login to Docker Registry + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Snapshot - uses: goreleaser/goreleaser-action@v5 + uses: goreleaser/goreleaser-action@v7 with: - distribution: goreleaser - version: latest args: release --snapshot - name: Release if: startsWith(github.ref, 'refs/tags/') - uses: goreleaser/goreleaser-action@v5 + uses: goreleaser/goreleaser-action@v7 with: - distribution: goreleaser - version: latest - args: release --rm-dist + args: release --clean diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..7dc8017 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,47 @@ +version: "2" +run: + tests: true +linters: + enable: + - asciicheck + - bidichk + - gocheckcompilerdirectives + - misspell + - rowserrcheck + - sqlclosecheck + - forbidigo + disable: + - gochecknoglobals + - prealloc + - wsl + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ + rules: + - path: cmd + linters: + - forbidigo + - path: fmt + linters: + - forbidigo + settings: + forbidigo: + forbid: + - pattern: ^(fmt\.Print(|f|ln))$ +formatters: + enable: + - gofmt + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/.goreleaser.yml b/.goreleaser.yml index 2366ec4..a4411dd 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,3 +1,4 @@ +version: 2 builds: - env: diff --git a/cmd/completion.go b/cmd/completion.go index 0efb620..7f10997 100644 --- a/cmd/completion.go +++ b/cmd/completion.go @@ -1,12 +1,12 @@ package cmd import ( + "fmt" + "io" "os" "strings" "github.com/spf13/cobra" - - "github.com/gomicro/forge/fmt" ) const ( @@ -28,23 +28,29 @@ func init() { var CompletionCmd = &cobra.Command{ Use: "completion", Short: "Generate completion files for the forge cli", - Run: completionFunc, + RunE: completionFunc, +} + +func completionFunc(cmd *cobra.Command, args []string) error { + return generateCompletion(shell, os.Stdout) } -func completionFunc(cmd *cobra.Command, args []string) { +func generateCompletion(targetShell string, out io.Writer) error { var err error - switch strings.ToLower(shell) { + switch strings.ToLower(targetShell) { case "bash": - err = RootCmd.GenBashCompletion(os.Stdout) + err = RootCmd.GenBashCompletion(out) case "ps", "powershell", "power_shell": - err = RootCmd.GenPowerShellCompletion(os.Stdout) + err = RootCmd.GenPowerShellCompletion(out) case "zsh": - err = RootCmd.GenZshCompletion(os.Stdout) + err = RootCmd.GenZshCompletion(out) default: + return fmt.Errorf("unsupported shell: %q", targetShell) } if err != nil { - fmt.Printf("error generating completion output: %v", err.Error()) - os.Exit(1) + return fmt.Errorf("generating completion output: %w", err) } + + return nil } diff --git a/cmd/confmt.go b/cmd/confmt.go index 8e99f8e..1ddbf78 100644 --- a/cmd/confmt.go +++ b/cmd/confmt.go @@ -1,10 +1,11 @@ package cmd import ( + "fmt" + "github.com/spf13/cobra" "github.com/gomicro/forge/confile" - "github.com/gomicro/forge/fmt" ) func init() { @@ -15,17 +16,19 @@ var confmtCmd = &cobra.Command{ Use: "confmt", Short: "Format the forge config file", Long: `Format and adjust the forge file for consistency.`, - Run: confmtFunc, + RunE: confmtFunc, } -func confmtFunc(cmd *cobra.Command, args []string) { +func confmtFunc(cmd *cobra.Command, args []string) error { conf, err := confile.ParseFromFile() if err != nil { - fmt.Printf("Failed: %v", err.Error()) + return fmt.Errorf("parsing config file: %w", err) } err = conf.Fmt() if err != nil { - fmt.Printf("Failed: %v", err.Error()) + return fmt.Errorf("formatting config file: %w", err) } + + return nil } diff --git a/cmd/init.go b/cmd/init.go index ce5528e..560ddaf 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -1,13 +1,14 @@ package cmd import ( + "errors" + "fmt" "os" "path" "github.com/spf13/cobra" "github.com/gomicro/forge/confile" - "github.com/gomicro/forge/fmt" ) func init() { @@ -18,19 +19,17 @@ var initCmd = &cobra.Command{ Use: "init", Short: "Initialize a forge config file", Long: `Collects the basic information to initialize a forge config file.`, - Run: initFunc, + RunE: initFunc, } -func initFunc(cmd *cobra.Command, args []string) { +func initFunc(cmd *cobra.Command, args []string) error { if confile.Exists() { - fmt.Printf("config file already exists") - os.Exit(1) + return errors.New("config file already exists") } currentDir, err := os.Getwd() if err != nil { - fmt.Printf("Failed to get current working dir: %v", err.Error()) - os.Exit(1) + return fmt.Errorf("getting current working dir: %w", err) } projName := path.Base(currentDir) @@ -49,6 +48,8 @@ func initFunc(cmd *cobra.Command, args []string) { err = f.Fmt() if err != nil { - fmt.Printf("Error Initializing File: %v", err.Error()) + return fmt.Errorf("initializing file: %w", err) } + + return nil } diff --git a/cmd/root.go b/cmd/root.go index 0c77b88..6049173 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,6 +1,7 @@ package cmd import ( + "fmt" "os" "sort" @@ -8,7 +9,7 @@ import ( "github.com/spf13/viper" "github.com/gomicro/forge/confile" - "github.com/gomicro/forge/fmt" + clifmt "github.com/gomicro/forge/fmt" ) func init() { @@ -17,28 +18,28 @@ func init() { RootCmd.PersistentFlags().Bool("verbose", false, "show more verbose output") err := viper.BindPFlag("verbose", RootCmd.PersistentFlags().Lookup("verbose")) if err != nil { - fmt.Printf("Error setting up: %v\n", err.Error()) + clifmt.Printf("Error setting up: %v\n", err.Error()) os.Exit(1) } RootCmd.PersistentFlags().Bool("solo", false, "run a step solo, without its pre or post steps") err = viper.BindPFlag("solo", RootCmd.PersistentFlags().Lookup("solo")) if err != nil { - fmt.Printf("Error setting up: %v\n", err.Error()) + clifmt.Printf("Error setting up: %v\n", err.Error()) os.Exit(1) } RootCmd.PersistentFlags().Bool("no-pre", false, "skip running pre steps") err = viper.BindPFlag("no-pre", RootCmd.PersistentFlags().Lookup("no-pre")) if err != nil { - fmt.Printf("Error setting up: %v\n", err.Error()) + clifmt.Printf("Error setting up: %v\n", err.Error()) os.Exit(1) } RootCmd.PersistentFlags().Bool("no-post", false, "skip running post steps") err = viper.BindPFlag("no-post", RootCmd.PersistentFlags().Lookup("no-post")) if err != nil { - fmt.Printf("Error setting up: %v\n", err.Error()) + clifmt.Printf("Error setting up: %v\n", err.Error()) os.Exit(1) } } @@ -52,7 +53,8 @@ var RootCmd = &cobra.Command{ Short: "A CLI for building projects", Long: `Forge is a CLI tool for executing, in a consistent manner, scripts and commands for building and maintaining projects.`, Args: cobra.MinimumNArgs(1), - Run: rootFunc, + RunE: rootFunc, + SilenceErrors: true, ValidArgsFunction: validArgsFunc, } @@ -60,33 +62,32 @@ var RootCmd = &cobra.Command{ // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { if err := RootCmd.Execute(); err != nil { - fmt.Printf("Failed to execute: %v", err.Error()) + clifmt.Printf("Failed to execute: %v\n", err.Error()) os.Exit(1) } } -func rootFunc(cmd *cobra.Command, args []string) { +func rootFunc(cmd *cobra.Command, args []string) error { conf, err := confile.ParseFromFile() if err != nil { - fmt.Printf("Failed: %v", err.Error()) - os.Exit(1) + return err } for _, a := range args { _, found := conf.Steps[a] if !found { - fmt.Printf("target not found: %v", a) - os.Exit(1) + return fmt.Errorf("target not found: %v", a) } } for _, s := range args { err := conf.Steps[s].Execute(conf.Steps, conf.Envs, conf.Vars) if err != nil { - fmt.Printf("failed executing step %v: %v", s, err.Error()) - os.Exit(1) + return fmt.Errorf("executing step %v: %w", s, err) } } + + return nil } func validArgsFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { diff --git a/confile/file.go b/confile/file.go index 1d4ba54..7e5c93e 100644 --- a/confile/file.go +++ b/confile/file.go @@ -26,19 +26,19 @@ type File struct { Vars *vars.Vars `yaml:"-"` } -// ParseFromFile reads an Forge config file from the from the curent directory. +// ParseFromFile reads an Forge config file from the from the current directory. // A File with the populated values is returned and any errors encountered while // trying to read the file. func ParseFromFile() (*File, error) { b, err := ioutil.ReadFile(file) if err != nil { - return nil, fmt.Errorf("Failed to read config file: %v", err.Error()) + return nil, fmt.Errorf("parseFromFile: reading config file: %w", err) } var conf File err = yaml.Unmarshal(b, &conf) if err != nil { - return nil, fmt.Errorf("Failed to unmarshal config file: %v", err.Error()) + return nil, fmt.Errorf("parseFromFile: unmarshaling config file: %w", err) } for name, step := range conf.Steps { @@ -60,17 +60,17 @@ func ParseFromFile() (*File, error) { shaBytes, err := exec.Command("git", "rev-parse", "HEAD").Output() if err != nil { - return nil, fmt.Errorf("Failed to get sha: %v", err.Error()) + return nil, fmt.Errorf("parseFromFile: getting sha: %w", err) } branchBytes, err := exec.Command("git", "branch", "--show-current").Output() if err != nil { - return nil, fmt.Errorf("Failed to get branch: %v", err.Error()) + return nil, fmt.Errorf("parseFromFile: getting branch: %w", err) } currentDir, err := os.Getwd() if err != nil { - return nil, fmt.Errorf("Failed to get current working dir: %v", err.Error()) + return nil, fmt.Errorf("parseFromFile: getting working directory: %w", err) } vars.Set("Branch", string(branchBytes)) @@ -90,12 +90,12 @@ func ParseFromFile() (*File, error) { func (f *File) Fmt() error { b, err := yaml.Marshal(f) if err != nil { - return fmt.Errorf("fmt: marshal: %v", err.Error()) + return fmt.Errorf("fmt: marshaling: %w", err) } err = ioutil.WriteFile(file, b, 0644) if err != nil { - return fmt.Errorf("fmt: write file: %v", err.Error()) + return fmt.Errorf("fmt: writing file: %w", err) } return nil