From e72c08d24799facf7ff78a2db17d4886be9ef05e Mon Sep 17 00:00:00 2001 From: Victor Hang Date: Sun, 22 Dec 2024 00:01:18 +0100 Subject: [PATCH] =?UTF-8?q?chore=20=F0=9F=A7=B9:=20initial=20commit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Victor Hang --- .github/dependabot.yml | 12 +++ .github/release.yml | 24 +++++ .github/workflows/build.yaml | 18 ++++ .github/workflows/linting.yaml | 33 +++++++ .github/workflows/release.yaml | 28 ++++++ .github/workflows/unittest.yaml | 18 ++++ .goreleaser.yaml | 32 +++++++ LICENSE | 0 README.md | 56 +++++++++++ cmd/root.go | 137 +++++++++++++++++++++++++++ docs/ginx.md | 26 +++++ go.mod | 42 ++++++++ go.sum | 163 ++++++++++++++++++++++++++++++++ internal/utils/git.go | 111 ++++++++++++++++++++++ internal/utils/zap.go | 33 +++++++ main.go | 11 +++ scripts/gen_doc.go | 17 ++++ 17 files changed, 761 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/release.yml create mode 100644 .github/workflows/build.yaml create mode 100644 .github/workflows/linting.yaml create mode 100644 .github/workflows/release.yaml create mode 100644 .github/workflows/unittest.yaml create mode 100644 .goreleaser.yaml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 cmd/root.go create mode 100644 docs/ginx.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/utils/git.go create mode 100644 internal/utils/zap.go create mode 100644 main.go create mode 100644 scripts/gen_doc.go diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f901647 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: + - package-ecosystem: gomod + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 99 + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 99 diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..7900e12 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,24 @@ +changelog: + exclude: + labels: + - ignore-for-release + authors: + - Victor Hang + categories: + - title: Breaking Changes 🛠 + labels: + - Semver-Major + - breaking-change + - title: Features 🎉 + labels: + - Semver-Minor + - enhancement + - title: Enhancements ✨ + labels: + - '*' + exclude: + labels: + - dependencies + - title: 👒 Dependencies + labels: + - dependencies diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..3ebb55c --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,18 @@ +name: GoReleaser Dry Run +on: + pull_request: +jobs: + release: + 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.22.6' + - name: Run GoReleaser (dry run) + uses: goreleaser/goreleaser-action@v6 + with: + args: release --snapshot # Perform a dry run + version: '~> v2' diff --git a/.github/workflows/linting.yaml b/.github/workflows/linting.yaml new file mode 100644 index 0000000..5ad1e76 --- /dev/null +++ b/.github/workflows/linting.yaml @@ -0,0 +1,33 @@ +name: Go Lint and Format Check +on: + pull_request: +jobs: + lint_and_format: + 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.22.6' + - name: Install Tools + run: | + go install mvdan.cc/gofumpt@latest + go install github.com/segmentio/golines@latest + - name: Run gofumpt + run: | + gofumpt -d . | tee gofumpt_output.txt + if [ -s gofumpt_output.txt ]; then + echo "gofumpt found issues:" + cat gofumpt_output.txt + exit 1 + fi + - name: Run golines + run: | + golines --max-len=140 . --dry-run | tee golines_output.txt + if [ -s golines_output.txt ]; then + echo "golines found lines exceeding 140 characters:" + cat golines_output.txt + exit 1 + fi diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..3107918 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,28 @@ +name: goreleaser +on: + push: + tags: + - '*' +permissions: + contents: write +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.22.6' + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: + distribution: goreleaser + version: '~> v2' + args: release --clean + workdir: ./ + env: + GITHUB_TOKEN: ${{ secrets.DIDACTIKLABS_GITHUB_TOKEN }} diff --git a/.github/workflows/unittest.yaml b/.github/workflows/unittest.yaml new file mode 100644 index 0000000..e5ffe4d --- /dev/null +++ b/.github/workflows/unittest.yaml @@ -0,0 +1,18 @@ +name: Go Test +on: + push: + branches: [main] + pull_request: + branches: [main] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.22.6' + - name: Run tests with coverage + run: | + go test -coverprofile=coverage.out ./... diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..c3a2fa0 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,32 @@ +version: 2 +before: + hooks: + - go mod tidy +builds: + - binary: ginx + main: ./ + env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + goarch: + - amd64 + - arm64 + - arm + ldflags: + - -s -w -X github.com/didactiklabs/ginx/cmd.version=v{{- .Version }} +archives: + - format: tar.gz + # this name template makes the OS and Arch compatible with the results of `uname`. + name_template: >- + {{ .ProjectName }}_v{{- .Version }}_ {{- title .Os }}_ {{- if eq .Arch "amd64" }}x86_64 {{- else if eq .Arch "386" }}i386 {{- else }}{{ .Arch }}{{ end }} {{- if .Arm }}v{{ .Arm }}{{ end }} + # use zip for windows archives + format_overrides: + - goos: windows + format: zip + files: + - README.md + - LICENSE + - docs/* diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md new file mode 100644 index 0000000..30d5201 --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +
+

Ginx

+ +![star] +[![Downloads][downloads-badge]][releases] +![version] + +
+ +**ginx** is a lightweight CLI tool designed to monitor changes in remote Git repositories and execute custom commands when updates occur. It is ideal for automating tasks, deploying applications, or triggering workflows whenever the target repository is updated. + +## Table of Contents + +- [Features](#features) +- [Installation](#installation) +- [Usage](#usage) +- [License](#license) + +## Features + +- **Monitor Remote Repositories**: Watch specific branches for updates. +- **Custom Commands**: Execute any command when changes are detected. +- **Configurable Intervals**: Set the polling frequency for repository checks. +- **Flexible Logging**: Adjust log levels for better visibility or minimal noise. +- **Version Display**: Check the current version of the tool. + +## Installation + +To install `ytui`, follow the instructions for your operating system. +Ensure that you have the required dependencies installed. + +1. **Install binary** + + ginx runs on most major platforms. If your platform isn't listed below, + please [open an issue][issues]. + + Please note that binaries are available on the release pages, you can extract the archives for your + platform and manually install it. + +## Usage + +See [Documentations](docs/ginx.md). + +## License + +![licence] + +`ginx` is open-source and available under the [LICENCE](LICENSE). + +For more detailed usage, you can always use `ginx --help`. + +[licence]: https://img.shields.io/github/license/didactiklabs/ginx +[downloads-badge]: https://img.shields.io/github/downloads/didactiklabs/ginx/total?logo=github&logoColor=white&style=flat-square +[releases]: https://github.com/didactiklabs/ginx/releases +[star]: https://img.shields.io/github/stars/didactiklabs/ginx +[version]: https://img.shields.io/github/v/release/didactiklabs/ginx diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..6d8e26d --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,137 @@ +package cmd + +import ( + "fmt" + "log" + "os" + "time" + + "github.com/go-git/go-git/v5" + "github.com/spf13/cobra" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/didactiklabs/ginx/internal/utils" +) + +var ( + versionFlag bool + version string + logLevelFlag string + sourceFlag string + branchFlag string + pollIntervalFlag int +) + +var RootCmd = &cobra.Command{ + Use: "ginx [flags] -- ", + Short: "ginx", + Long: ` +Ginx is a cli tool that watch a remote repository and run an arbitrary command on changes/updates. +`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + // Initialize configuration here + if versionFlag { + fmt.Printf("%s", version) + } else { + initConfig() + } + }, + Run: func(cmd *cobra.Command, args []string) { + var r *git.Repository + var err error + source := sourceFlag + branch := branchFlag + interval := time.Duration(pollIntervalFlag) * time.Second + dir, err := os.MkdirTemp("", "ginx-*") + if err != nil { + utils.Logger.Fatal("Failed to create temporary directory.", zap.Error(err)) + } + + if !utils.IsRepoCloned(source) { + utils.Logger.Info("Cloning repository.", zap.String("url", source), zap.String("branch", branch)) + r, err = utils.CloneRepo(source, branch, dir) + if err != nil { + utils.Logger.Fatal("Failed to clone repository.", zap.Error(err)) + } + } else { + r, err = git.PlainOpen(dir) + utils.Logger.Info("Repository already exist, open directory repository.", zap.String("directory", dir)) + if err != nil { + utils.Logger.Fatal("Failed to open existing directory repository.", zap.Error(err)) + } + } + + log.Println("Starting remote repository watcher...") + for { + // Get the latest commit hash from the remote repository + remoteCommit, err := utils.GetLatestRemoteCommit(r, branch) + utils.Logger.Info("Fetched remote commit.", zap.String("remoteCommit", remoteCommit)) + if err != nil { + utils.Logger.Fatal("error fetching local commit.", zap.Error(err)) + } + + // Get the latest commit hash from the local repository + localCommit, err := utils.GetLatestLocalCommit(dir) + utils.Logger.Info("Fetched local commit.", zap.String("localCommit", localCommit)) + if err != nil { + utils.Logger.Fatal("error fetching local commit.", zap.Error(err)) + } + + if remoteCommit != localCommit { + log.Println("Detected remote changes. Pulling for latest changes...") + if err := utils.PullRepo(r); err != nil { + utils.Logger.Info("Failed to pull. Recloning repository.", zap.String("url", source)) + err := os.RemoveAll(dir) + if err != nil { + utils.Logger.Fatal("error removing directory.", zap.Error(err)) + } + _, err = utils.CloneRepo(source, branch, dir) + if err != nil { + utils.Logger.Fatal("Failed to clone repository.", zap.Error(err)) + } + } + if len(args) > 0 { + utils.Logger.Info("Running command.", zap.String("command", args[0]), zap.Any("args", args[1:])) + if err := utils.RunCommand(dir, args[0], args[1:]...); err != nil { + utils.Logger.Error("Failed to run command.", zap.Error(err)) + } + } + } else { + utils.Logger.Info("No changes detected in remote repository.", zap.String("url", source)) + } + time.Sleep(interval) + } + }, + Args: cobra.ArbitraryArgs, +} + +func initConfig() { + // Your configuration initialization logic + logLevel := zapcore.InfoLevel //nolint:all + switch logLevelFlag { + case "debug": + logLevel = zapcore.DebugLevel + case "error": + logLevel = zapcore.ErrorLevel + default: + logLevel = zapcore.InfoLevel + } + utils.InitializeLogger(logLevel) + utils.Logger.Info("Initialized configuration.") +} + +func Execute() { + err := RootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + RootCmd.Flags().BoolVarP(&versionFlag, "version", "v", false, "display version information") + RootCmd.PersistentFlags().StringVarP(&logLevelFlag, "log-level", "l", "info", "override log level (debug, info, error)") + RootCmd.PersistentFlags().StringVarP(&sourceFlag, "source", "s", "", "git repository to watch") + RootCmd.PersistentFlags().StringVarP(&branchFlag, "branch", "b", "main", "branch to watch") + RootCmd.PersistentFlags().IntVarP(&pollIntervalFlag, "interval", "n", 30, "interval in seconds to poll the remote repo") +} diff --git a/docs/ginx.md b/docs/ginx.md new file mode 100644 index 0000000..9b86cc2 --- /dev/null +++ b/docs/ginx.md @@ -0,0 +1,26 @@ +## ginx + +ginx + +### Synopsis + + +Ginx is a cli tool that watch a remote repository and run an arbitrary command on changes/updates. + + +``` +ginx [flags] -- +``` + +### Options + +``` + -b, --branch string branch to watch (default "main") + -h, --help help for ginx + -n, --interval int interval in seconds to poll the remote repo (default 30) + -l, --log-level string override log level (debug, info, error) (default "info") + -s, --source string git repository to watch + -v, --version display version information +``` + +###### Auto generated by spf13/cobra on 22-Dec-2024 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9d4a54b --- /dev/null +++ b/go.mod @@ -0,0 +1,42 @@ +module github.com/didactiklabs/ginx + +go 1.23.3 + +require ( + github.com/go-git/go-git/v5 v5.12.0 + github.com/spf13/cobra v1.8.1 + go.uber.org/zap v1.27.0 +) + +require ( + dario.cat/mergo v1.0.0 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/ProtonMail/go-crypto v1.0.0 // indirect + github.com/cloudflare/circl v1.3.7 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.5.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/pjbgf/sha1cd v0.3.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/skeema/knownhosts v1.2.2 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + go.uber.org/multierr v1.10.0 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.23.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/tools v0.13.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..2a56d94 --- /dev/null +++ b/go.sum @@ -0,0 +1,163 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= +github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= +github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= +github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= +github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/utils/git.go b/internal/utils/git.go new file mode 100644 index 0000000..1ad6f54 --- /dev/null +++ b/internal/utils/git.go @@ -0,0 +1,111 @@ +package utils + +import ( + "fmt" + "os" + "os/exec" + + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" +) + +func IsRepoCloned(dirName string) bool { + if _, err := os.Stat(dirName); os.IsNotExist(err) { + return false + } + return true +} + +func PullRepo(r *git.Repository) error { + // Get the working directory for the repository + w, err := r.Worktree() + if err != nil { + return err + } + // Pull the latest changes from the origin remote and merge into the current branch + err = w.Pull(&git.PullOptions{RemoteName: "origin"}) + if err != nil { + return err + } + // Print the latest commit that was just pulled + ref, err := r.Head() + if err != nil { + return err + } + commit, err := r.CommitObject(ref.Hash()) + if err != nil { + return err + } + fmt.Println(commit) + return nil +} + +func CloneRepo(source, branch, dir string) (*git.Repository, error) { + url := source + directory := dir + // Clone the given repository to the given directory + branchRefName := plumbing.NewBranchReferenceName(branch) + r, err := git.PlainClone(directory, false, &git.CloneOptions{ + URL: url, + RecurseSubmodules: git.DefaultSubmoduleRecursionDepth, + ReferenceName: plumbing.ReferenceName(branchRefName), + }) + if err != nil { + return r, err + } + // Print the latest commit that was just pulled + ref, err := r.Head() + if err != nil { + return r, err + } + commit, err := r.CommitObject(ref.Hash()) + if err != nil { + return r, err + } + fmt.Println(commit) + return r, nil +} + +func RunCommand(dirName, command string, args ...string) error { + cmd := exec.Command(command, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Dir = dirName + return cmd.Run() +} + +func GetLatestRemoteCommit(r *git.Repository, branch string) (string, error) { + // Create the remote with repository URL + rem, err := r.Remote("origin") + if err != nil { + return "", err + } + refs, err := rem.List(&git.ListOptions{ + // Returns all references, including peeled references. + PeelingOption: git.IgnorePeeled, + }) + if err != nil { + return "", err + } + var refHash string + for _, ref := range refs { + if ref.Name().String() == fmt.Sprintf("refs/heads/%s", branch) { + refHash = ref.Hash().String() + break + } + } + return refHash, nil +} + +func GetLatestLocalCommit(dir string) (string, error) { + r, err := git.PlainOpen(dir) + if err != nil { + return "", err + } + // Print the latest commit. + ref, err := r.Head() + if err != nil { + return "", err + } + return ref.Hash().String(), nil +} diff --git a/internal/utils/zap.go b/internal/utils/zap.go new file mode 100644 index 0000000..b319046 --- /dev/null +++ b/internal/utils/zap.go @@ -0,0 +1,33 @@ +package utils + +import ( + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +var Logger *zap.Logger + +func InitializeLogger(logLevel zapcore.Level) { + // Create all necessary directories for the log file + + config := zap.Config{ + Level: zap.NewAtomicLevelAt(logLevel), // Set the log level + Development: false, + Sampling: &zap.SamplingConfig{ + Initial: 100, + Thereafter: 100, + }, + Encoding: "json", + EncoderConfig: zap.NewProductionEncoderConfig(), + + OutputPaths: []string{"stdout"}, + ErrorOutputPaths: []string{"stderr"}, + } + + var err error + Logger, err = config.Build() + if err != nil { + panic(err) + } + defer Logger.Sync() //nolint:all +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..d60622b --- /dev/null +++ b/main.go @@ -0,0 +1,11 @@ +/* +Copyright © 2024 NAME HERE + +*/ +package main + +import "github.com/didactiklabs/ginx/cmd" + +func main() { + cmd.Execute() +} diff --git a/scripts/gen_doc.go b/scripts/gen_doc.go new file mode 100644 index 0000000..10937c6 --- /dev/null +++ b/scripts/gen_doc.go @@ -0,0 +1,17 @@ +package main + +import ( + "log" + + "github.com/spf13/cobra/doc" + + cmd "github.com/didactiklabs/ginx/cmd" +) + +func main() { + rootCmd := cmd.RootCmd + err := doc.GenMarkdownTree(rootCmd, "./docs") + if err != nil { + log.Fatal(err) + } +}