diff --git a/.github/workflows/.goreleaser.yaml b/.github/workflows/.goreleaser.yaml new file mode 100644 index 0000000..51b4a26 --- /dev/null +++ b/.github/workflows/.goreleaser.yaml @@ -0,0 +1,79 @@ +project_name: agentexec + +before: + hooks: + - go mod tidy + +builds: + - env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + goarch: + - amd64 + - arm64 + - "386" + ignore: + - goos: darwin + goarch: "386" + mod_timestamp: '{{ .CommitTimestamp }}' + flags: + - -trimpath + ldflags: + - -s -w + - -X agentexec/pkg/version.Version={{.Version}} + - -X agentexec/pkg/version.Commit={{.Commit}} + - -X agentexec/pkg/version.BuildTime={{.Date}} + +archives: + - format: tar.gz + name_template: >- + {{ .ProjectName }}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}v{{ .Arm }}{{ end }} + format_overrides: + - goos: windows + format: zip + files: + - README* + - LICENSE* + - CHANGELOG.md + - .env.example + +nfpms: + - package_name: agentexec + homepage: https://github.com/drengskapur/agentexec + maintainer: Drengskapur + description: AgentExec CLI tool + license: MIT + formats: + - deb + - rpm + bindir: /usr/local/bin + +checksum: + name_template: 'checksums.txt' + +snapshot: + name_template: "{{ incpatch .Version }}-next" + +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' + - '^ci:' + - '^chore:' + - Merge pull request + - Merge branch + +release: + prerelease: auto + draft: false + name_template: "Release {{.Version}}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 83db6df..ad80379 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,20 +1,97 @@ -name: CI +name: Build AgentExec Distributions on: workflow_dispatch: + push: + branches: + - main + tags: + - 'v*' + pull_request: jobs: - build-test: + source-archive: + name: Create Source Archive + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 # Needed for proper version detection + + - name: Set Version Information + id: version + shell: bash + run: | + VERSION=$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "0.0.1") + echo "version=${VERSION}" >> $GITHUB_OUTPUT + + - name: Create Source Archive + run: | + mkdir -p dist + tar --exclude='.git' --exclude='dist' -czf "dist/agentexec${VERSION}.src.tar.gz" . + cd dist && sha256sum "agentexec${VERSION}.src.tar.gz" > SHASUMS256.txt + echo "# agentexec ${VERSION} checksums" > SHASUMS256.txt.tmp + cat SHASUMS256.txt >> SHASUMS256.txt.tmp + mv SHASUMS256.txt.tmp SHASUMS256.txt + + - name: Upload Artifacts + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: source-archive + path: dist/* + if-no-files-found: error + + build-binaries: + name: Build Binaries + needs: source-archive runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - go-version: ['1.23'] - env: - GO111MODULE: 'on' - CGO_ENABLED: 1 - + include: + # Linux builds + - os: ubuntu-latest + goos: linux + architecture: amd64 + format: tar.gz + - os: ubuntu-latest + goos: linux + architecture: arm64 + format: tar.gz + - os: ubuntu-latest + goos: linux + architecture: 386 + format: tar.gz + - os: ubuntu-latest + goos: linux + architecture: arm + format: tar.gz + arm: 6 + # macOS builds + - os: macos-latest + goos: darwin + architecture: amd64 + format: tar.gz + - os: macos-latest + goos: darwin + architecture: arm64 + format: tar.gz + # Windows builds + - os: windows-latest + goos: windows + architecture: amd64 + format: zip + - os: windows-latest + goos: windows + architecture: arm64 + format: zip + - os: windows-latest + goos: windows + architecture: 386 + format: zip steps: - name: Checkout Repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -24,67 +101,177 @@ jobs: - name: Setup Go uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0 with: - go-version: ${{ matrix.go-version }} - cache: 'go' + go-version: '1.23.3' + check-latest: true - - name: Cache Go Modules - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + - name: Download Source Archive + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: - path: | - ~/go/pkg/mod - ~/.cache/go-build - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- + name: source-archive + path: dist/ + + - name: Build and Package + shell: bash + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.architecture }} + GOARM: ${{ matrix.arm }} + run: | + VERSION=$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "0.0.1") + COMMIT=$(git rev-parse --short HEAD) + BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") - - name: Install Dependencies - run: go mod tidy + # Set binary extension for Windows + EXT="" + if [[ "$GOOS" == "windows" ]]; then + EXT=".exe" + fi - - name: Cache golangci-lint - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 - with: - path: ~/.golangci-lint - key: ${{ runner.os }}-golangci-lint-${{ hashFiles('**/.golangci.yml') }} - restore-keys: | - ${{ runner.os }}-golangci-lint- + # Build binary + echo "Building for GOOS=$GOOS, GOARCH=$GOARCH" + mkdir -p dist + go build -v -trimpath -ldflags="-s -w \ + -X 'agentexec/pkg/version.Version=${VERSION}' \ + -X 'agentexec/pkg/version.Commit=${COMMIT}' \ + -X 'agentexec/pkg/version.BuildTime=${BUILD_TIME}'" \ + -o "dist/agentexec${EXT}" main.go - - name: Install golangci-lint - if: "!steps.cache-golangci-lint.outputs.cache-hit" - run: | - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.27.0 - env: - GOLANGCI_LINT_VERSION: v1.27.0 + # Create archive name + OUTPUT_NAME="agentexec${VERSION}.${GOOS}" + if [[ "$GOARCH" == "arm" ]] && [[ -n "$GOARM" ]]; then + OUTPUT_NAME="${OUTPUT_NAME}-armv${GOARM}l" + else + OUTPUT_NAME="${OUTPUT_NAME}-${GOARCH}" + fi - - name: Verify golangci-lint - run: golangci-lint --version + # Create archive + cd dist + if [[ "${{ matrix.format }}" == "zip" ]]; then + zip -q "${OUTPUT_NAME}.zip" "agentexec${EXT}" + sha256sum "${OUTPUT_NAME}.zip" >> SHASUMS256.txt + else + tar -czf "${OUTPUT_NAME}.tar.gz" "agentexec${EXT}" + sha256sum "${OUTPUT_NAME}.tar.gz" >> SHASUMS256.txt + fi - - name: Run Linters - run: golangci-lint run + # Cleanup binary + rm "agentexec${EXT}" - - name: Run Tests - run: | - go test ./... -v -race -coverprofile=coverage.out - go tool cover -func=coverage.out + # Sort SHASUMS256.txt + sort -k2 SHASUMS256.txt > SHASUMS256.txt.sorted + head -n1 SHASUMS256.txt > SHASUMS256.txt + tail -n +2 SHASUMS256.txt.sorted >> SHASUMS256.txt + rm SHASUMS256.txt.sorted - - name: Upload Coverage - if: success() + - name: Upload Build Artifacts uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: - name: coverage-${{ matrix.os }} - path: coverage.out + name: binary-${{ matrix.goos }}-${{ matrix.architecture }} + path: dist/* + if-no-files-found: error + + create-installers: + name: Create Installers + needs: build-binaries + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: macos-latest + goos: darwin + architecture: amd64 + - os: macos-latest + goos: darwin + architecture: arm64 + - os: windows-latest + goos: windows + architecture: amd64 + - os: windows-latest + goos: windows + architecture: arm64 + steps: + - name: Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Download Binary + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: binary-${{ matrix.goos }}-${{ matrix.architecture }} + path: dist/ - - name: Build Application + - name: Setup Windows Environment + if: matrix.os == 'windows-latest' + shell: bash run: | - mkdir -p build - go build -ldflags="-s -w \ - -X 'agentexec/pkg/version.Version=$(git describe --tags --abbrev=0 2>/dev/null || echo v0.0.1)' \ - -X 'agentexec/pkg/version.Commit=$(git rev-parse --short HEAD)' \ - -X 'agentexec/pkg/version.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)'" \ - -o build/agentexec${{ matrix.os == 'windows-latest' && '.exe' || '' }} main.go + choco install wix --version=3.11.2 -y + refreshenv - - name: Upload Build Artifacts - if: success() + - name: Create Installer + shell: bash + run: | + VERSION=$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "0.0.1") + mkdir -p packages + EXT="" + if [[ "${{ matrix.goos }}" == "windows" ]]; then + EXT=".exe" + fi + + if [[ "${{ matrix.goos }}" == "darwin" ]]; then + pkgbuild --identifier com.agentexec.cli \ + --install-location /usr/local/bin \ + --root dist \ + --scripts scripts/macos \ + "packages/agentexec${VERSION}.${GOOS}-${GOARCH}.pkg" + elif [[ "${{ matrix.goos }}" == "windows" ]]; then + export INSTALLER_GUID=$(uuidgen) + export COMPONENT_GUID=$(uuidgen) + + candle.exe -arch x64 \ + -o installer.wixobj \ + .github/workflows/installer.wix + if [ $? -ne 0 ]; then + echo "WiX candle.exe failed" + exit 1 + fi + + light.exe -o "packages/agentexec${VERSION}.${GOOS}-${GOARCH}.msi" installer.wixobj + if [ $? -ne 0 ]; then + echo "WiX light.exe failed" + exit 1 + fi + fi + + cd packages + if [[ "${{ matrix.goos }}" == "darwin" ]]; then + sha256sum "agentexec${VERSION}.${GOOS}-${GOARCH}.pkg" >> ../dist/SHASUMS256.txt + else + sha256sum "agentexec${VERSION}.${GOOS}-${GOARCH}.msi" >> ../dist/SHASUMS256.txt + fi + + - name: Upload Installer uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: - name: build-${{ matrix.os }} - path: build/ + name: installer-${{ matrix.goos }}-${{ matrix.architecture }} + path: | + packages/* + dist/SHASUMS256.txt + if-no-files-found: error + + validate-checksums: + name: Validate Checksums + needs: [source-archive, build-binaries, create-installers] + runs-on: ubuntu-latest + steps: + - name: Download All Artifacts + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + path: dist + merge-multiple: true + + - name: Validate Checksums + shell: bash + run: | + cd dist + echo "Verifying checksums from SHASUMS256.txt..." + sha256sum -c SHASUMS256.txt \ No newline at end of file diff --git a/.github/workflows/installer.wix b/.github/workflows/installer.wix new file mode 100644 index 0000000..615bbee --- /dev/null +++ b/.github/workflows/installer.wix @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ab8618b..1f2631f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,70 +1,67 @@ name: Release on: + push: + tags: + - "v*" workflow_dispatch: + inputs: + tag: + description: 'Tag to create (e.g., v1.0.0)' + required: true + type: string + +permissions: + contents: write + packages: write + issues: write + pull-requests: write jobs: release: runs-on: ubuntu-latest - permissions: - contents: write environment: name: production - env: - VERSION: ${{ github.ref_name }} - steps: - name: Checkout Repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 - - name: Setup Go + - name: Set up Go uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0 with: - go-version: '1.23' + go-version: '1.23.3' cache: true - - name: Install Dependencies - run: go mod tidy + - name: Import GPG key + id: import_gpg + uses: crazy-max/ghaction-import-gpg@cb9bde2e2525e640591a934b1fd28eef1dcaf5e5 # v6.2.0 + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.GPG_PASSPHRASE }} + + - name: Set up WiX + if: runner.os == 'Windows' + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + repository: wixtoolset/wix3 + path: wix + ref: wix3112rtm - name: Run Tests run: go test ./... -v -race -coverprofile=coverage.out - - name: Build and Package - run: | - mkdir -p build/${{ env.VERSION }} - for os in windows linux darwin; do - case $os in - windows) - ext=".exe" - ;; - *) - ext="" - ;; - esac - - GOOS=$os GOARCH=amd64 go build -ldflags="-s -w \ - -X 'agentexec/pkg/version.Version=${{ env.VERSION }}' \ - -X 'agentexec/pkg/version.Commit=$(git rev-parse --short HEAD)' \ - -X 'agentexec/pkg/version.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)'" \ - -o "build/${{ env.VERSION }}/agentexec-${{ env.VERSION }}-${os}-amd64${ext}" main.go - - # Package binaries - if [ "$os" = "windows" ]; then - (cd "build/${{ env.VERSION }}" && zip "agentexec-${{ env.VERSION }}-${os}-amd64.zip" "agentexec-${{ env.VERSION }}-${os}-amd64${ext}") - else - tar -czf "build/${{ env.VERSION }}/agentexec-${{ env.VERSION }}-${os}-amd64.tar.gz" -C "build/${{ env.VERSION }}" "agentexec-${{ env.VERSION }}-${os}-amd64" - fi - done - - # Generate Checksums - cd "build/${{ env.VERSION }}" - sha256sum * > checksums.txt - - # Create final assets archive - zip -r "../agentexec-${{ env.VERSION }}-assets.zip" * + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@9ed2f89a662bf1735a48bc8557fd212fa902bebf # v6.1.0 + with: + distribution: goreleaser + version: latest + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} - name: Generate Release Notes id: release_notes @@ -137,14 +134,12 @@ jobs: return releaseNotes; - - name: Create GitHub Release + - name: Update Release Notes uses: softprops/action-gh-release@01570a1f39cb168c169c802c3bceb9e93fb10974 # v2.1.0 + if: success() && startsWith(github.ref, 'refs/tags/') with: - name: Release ${{ env.VERSION }} body: ${{ steps.release_notes.outputs.result }} draft: false - prerelease: ${{ contains(github.ref_name, '-') }} - files: | - ./build/${{ env.VERSION }}/* + prerelease: ${{ contains(github.ref, '-rc') || contains(github.ref, '-beta') || contains(github.ref, '-alpha') }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 5064166..5e21bda 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "yaml.schemas": { - "https://taskfile.dev/schema.json": "file:///workspaces/agentexec/Taskfile.yml" + "https://taskfile.dev/schema.json": "file:///workspaces/agentexec/Taskfile.yml", + "https://goreleaser.com/static/schema.json": "file:///workspaces/agentexec/.github/workflows/.goreleaser.yaml" } } \ No newline at end of file diff --git a/Taskfile.yml b/Taskfile.yml index 77f2330..e6688db 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -1,167 +1,262 @@ +# ============================================================================= +# Global Configuration +# ============================================================================= version: '3' -# Global configurations output: prefixed silent: false interval: 5s set: [pipefail] shopt: [globstar] -# Global variables for reusability +# ============================================================================= +# Variables +# ============================================================================= vars: BINARY_NAME: agentexec BUILD_DIR: build MAIN_GO: main.go VERSION: - sh: git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.1" + sh: git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "0.0.1" COMMIT: sh: git rev-parse --short HEAD BUILDTIME: sh: date -u +"%Y-%m-%dT%H:%M:%SZ" LDFLAGS: >- + -s -w -X 'agentexec/pkg/version.Version={{.VERSION}}' -X 'agentexec/pkg/version.Commit={{.COMMIT}}' -X 'agentexec/pkg/version.BuildTime={{.BUILDTIME}}' - exeExt: - sh: | - case "{{.OS}}" in - windows) echo ".exe" ;; - *) echo "" ;; - esac +# ============================================================================= # Tasks +# ============================================================================= tasks: default: desc: Build the application - deps: [lint, test] cmds: - task: build +# ============================================================================= +# Build Tasks +# ============================================================================= build: - desc: Build the Go application with versioning + desc: Build for the current platform + deps: [build:check-deps] preconditions: - sh: go version msg: "Go must be installed" cmds: - - echo "๐Ÿ“ฆ Cleaning previous build artifacts..." - - rm -rf {{.BUILD_DIR}} + - echo "๐Ÿ“ฆ Building {{.BINARY_NAME}} for current platform..." - mkdir -p {{.BUILD_DIR}} - - echo "๐Ÿ“ฆ Building {{.BINARY_NAME}}..." - - go build -ldflags="{{.LDFLAGS}}" -o {{.BUILD_DIR}}/{{.BINARY_NAME}}{{.exeExt}} {{.MAIN_GO}} - - echo "๐Ÿ”— Adding {{.BINARY_NAME}} to PATH..." - - mkdir -p /home/codespace/.local/bin - - cp {{.BUILD_DIR}}/{{.BINARY_NAME}}{{.exeExt}} /home/codespace/.local/bin + - go build -trimpath -ldflags="{{.LDFLAGS}}" -o {{.BUILD_DIR}}/{{.BINARY_NAME}} + - echo "โœ… Build complete" + - mkdir -p $HOME/.local/bin + - cp {{.BUILD_DIR}}/{{.BINARY_NAME}} $HOME/.local/bin/{{.BINARY_NAME}} - agentexec version - run: - desc: Run the Go application - deps: [build] - interactive: true + build:check-deps: + desc: Check and install build dependencies + vars: + COMPRESS: gzip # Default to gzip cmds: - - echo "๐Ÿƒ Running {{.BINARY_NAME}}..." - - ./{{.BUILD_DIR}}/{{.BINARY_NAME}}{{.exeExt}} {{.CLI_ARGS}} + - | + # Check for pigz + if command -v pigz >/dev/null 2>&1; then + echo "โœ… pigz is available" + set -- -E COMPRESS=pigz + else + echo "โš ๏ธ pigz not found, installing..." + task install-tools:pigz + if command -v pigz >/dev/null 2>&1; then + set -- -E COMPRESS=pigz + else + echo "โš ๏ธ Using gzip as fallback" + set -- -E COMPRESS=gzip + fi + fi - test: - desc: Run tests with coverage report - deps: [test:run, test:coverage] - cmds: [] - - test:run: - desc: Execute Go tests - sources: - - "**/*.go" - - "**/*_test.go" + # Check for zip (needed for Windows builds) + if ! command -v zip >/dev/null 2>&1; then + echo "โš ๏ธ zip not found, installing..." + task install-tools:zip + fi + + build:cross: + desc: Build binaries for multiple platforms + deps: [clean:build, build:check-deps] + vars: + COMPRESS: + sh: command -v pigz >/dev/null 2>&1 && echo "pigz" || echo "gzip" cmds: - - echo "๐Ÿงช Running tests..." - - go test ./... -v -race + - mkdir -p {{.BUILD_DIR}} + - | + set -e # Exit immediately if a command exits with a non-zero status + echo "๐Ÿ“ฆ Using {{.COMPRESS}} for compression" + + # Create source archive first + echo "๐Ÿ“ฆ Creating source archive..." + tar --exclude='.git' \ + --exclude='{{.BUILD_DIR}}' \ + --exclude='dist' \ + -cf - . | {{.COMPRESS}} > "{{.BUILD_DIR}}/{{.BINARY_NAME}}-src.tar.gz" + + # Initialize SHASUMS256.txt with header + echo "# {{.BINARY_NAME}} checksums" > "{{.BUILD_DIR}}/SHASUMS256.txt" + + # Add checksum for source archive with only the filename + checksum=$(shasum -a 256 "{{.BUILD_DIR}}/{{.BINARY_NAME}}-src.tar.gz" | awk '{print $1}') + filename="{{.BINARY_NAME}}-src.tar.gz" + echo "$checksum $filename" >> "{{.BUILD_DIR}}/SHASUMS256.txt" + + # Define the build matrix + BUILDS=( + "darwin amd64 tar.gz" + "darwin arm64 tar.gz" + "linux 386 tar.gz" + "linux amd64 tar.gz" + "linux arm64 tar.gz" + "linux arm tar.gz" + "windows 386 zip" + "windows amd64 zip" + "windows arm64 zip" + ) + + for build in "${BUILDS[@]}"; do + read -r os arch format <<< "$build" + + echo "๐Ÿ”จ Building for $os/$arch..." + + # Set binary extension for Windows + bin_ext="" + if [ "$os" = "windows" ]; then + bin_ext=".exe" + fi + + # Determine output binary name and path + binary_name="{{.BINARY_NAME}}-${os}-${arch}${bin_ext}" + binary_path="{{.BUILD_DIR}}/${binary_name}" + + # Build the binary in the project root + GOOS=$os GOARCH=$arch go build \ + -trimpath \ + -mod=vendor \ + -ldflags="{{.LDFLAGS}}" \ + -o "$binary_path" \ + . + + # Create archive name + output_name="{{.BINARY_NAME}}-${os}-${arch}" + if [ "$os" = "linux" ] && [ "$arch" = "arm" ]; then + output_name="{{.BINARY_NAME}}-${os}-armv6l" + fi + + # Create archive path relative to BUILD_DIR + archive_name="${output_name}.${format}" + archive_path="{{.BUILD_DIR}}/${archive_name}" + + # Create archive + if [ "$format" = "zip" ]; then + zip -q "$archive_path" "$binary_path" + else + tar -cf - "$binary_path" | {{.COMPRESS}} > "$archive_path" + fi + + # Add to SHASUMS256.txt with only the filename + checksum=$(shasum -a 256 "$archive_path" | awk '{print $1}') + filename="${output_name}.${format}" + echo "$checksum $filename" >> "{{.BUILD_DIR}}/SHASUMS256.txt" + + # Clean up binary + rm "$binary_path" + done + + # Sort checksums (preserve header) + { + head -n1 "{{.BUILD_DIR}}/SHASUMS256.txt" + tail -n +2 "{{.BUILD_DIR}}/SHASUMS256.txt" | sort + } > "{{.BUILD_DIR}}/SHASUMS256.txt.tmp" + mv "{{.BUILD_DIR}}/SHASUMS256.txt.tmp" "{{.BUILD_DIR}}/SHASUMS256.txt" + + - echo "โœ… Cross-compilation complete. Archives are in {{.BUILD_DIR}}/" + - task: build:verify + + build:verify: + desc: Verify builds + cmds: + - | + set -e # Exit immediately if a command exits with a non-zero status + echo "๐Ÿ” Verifying checksums..." + + # Change directory to BUILD_DIR for relative checksum verification + cd "{{.BUILD_DIR}}" + + # Verify checksums + shasum -a 256 -c SHASUMS256.txt - test:coverage: - desc: Generate coverage report - deps: [test:run] + echo "๐Ÿ“ File listing in {{.BUILD_DIR}}:" + ls -lh + + + +# ============================================================================= +# Test Tasks +# ============================================================================= + test: + desc: Run tests with coverage cmds: - - mkdir -p {{.BUILD_DIR}}/coverage - - go test ./... -coverprofile={{.BUILD_DIR}}/coverage/coverage.out - - go tool cover -html={{.BUILD_DIR}}/coverage/coverage.out -o {{.BUILD_DIR}}/coverage/coverage.html + - echo "๐Ÿงช Running tests with coverage..." + - mkdir -p coverage + - | + go test ./... -race -coverprofile=coverage/coverage.out \ + && go tool cover -html=coverage/coverage.out -o coverage/coverage.html +# ============================================================================= +# Code Quality Tasks +# ============================================================================= lint: - desc: Lint the code using golangci-lint - deps: [lint:check, lint:run] - cmds: [] + desc: Run linters + deps: [lint:check] + cmds: + - echo "๐Ÿ” Linting code..." + - golangci-lint run ./... + - task: fmt lint:check: - desc: Ensure 'golangci-lint' is installed + desc: Ensure linter is installed preconditions: - sh: which golangci-lint msg: "golangci-lint is not installed. Run 'task deps' first." - cmds: [] - - lint:run: - desc: Execute golangci-lint - sources: - - "**/*.go" - - .golangci.yml - cmds: - - echo "๐Ÿ” Linting code..." - - golangci-lint run ./... fmt: - desc: Format the code - deps: [fmt:go, fmt:gofmt] - cmds: [] - - fmt:go: - desc: Run 'go fmt' - sources: - - "**/*.go" + desc: Format code cmds: - - echo "๐Ÿงน Formatting code with go fmt..." + - echo "๐Ÿงน Formatting code..." - go fmt ./... - - fmt:gofmt: - desc: Run 'gofmt -s -w .' - cmds: - - echo "๐Ÿงน Simplifying and formatting code with gofmt..." - gofmt -s -w . - autofix: - desc: Automatically fix formatting and linting issues - deps: [autofix:lint-fix, autofix:tidy] - cmds: [] - - autofix:lint-fix: - desc: Run golangci-lint with autofix - cmds: - - echo "๐Ÿ”ง Running golangci-lint with autofix..." - - golangci-lint run --fix ./... - - autofix:tidy: - desc: Tidy up dependencies - cmds: - - echo "๐Ÿ”„ Tidying up dependencies..." - - go mod tidy - +# ============================================================================= +# Cleanup Tasks +# ============================================================================= clean: - desc: Clean build artifacts - deps: [clean:build, clean:coverage] + desc: Clean all artifacts cmds: - - rm -f .godoc.pid + - echo "๐Ÿงน Cleaning all artifacts..." + - rm -rf {{.BUILD_DIR}} coverage dist clean:build: - desc: Remove build directory + desc: Clean build directory cmds: - - echo "๐Ÿงน Cleaning build directory..." - rm -rf {{.BUILD_DIR}} - clean:coverage: - desc: Remove coverage reports - cmds: - - echo "๐Ÿงน Cleaning coverage reports..." - - rm -rf {{.BUILD_DIR}}/coverage - +# ============================================================================= +# Installation & Setup Tasks +# ============================================================================= deps: - desc: Install project dependencies - deps: [deps:go-download, deps:install-tools] - cmds: [] + desc: Install all dependencies + cmds: + - task: deps:go-download + - task: install-tools deps:go-download: desc: Download Go modules @@ -169,116 +264,151 @@ tasks: - echo "๐Ÿ“ฆ Downloading Go modules..." - go mod download - deps:install-tools: - desc: Install development tools - deps: [install-tools:godoc, install-tools:golangci-lint, install-tools:check-curl] - cmds: [] - install-tools: desc: Install development tools - deps: [install-tools:godoc, install-tools:golangci-lint, install-tools:check-curl] - cmds: [] - - install-tools:golangci-lint: - desc: Install 'golangci-lint' cmds: - - echo "๐Ÿ”ง Installing 'golangci-lint'..." - - go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest - - install-tools:godoc: - desc: Install 'godoc' tool - cmds: - - echo "๐Ÿ”ง Installing 'godoc'..." - - go install golang.org/x/tools/cmd/godoc@latest - - install-tools:check-curl: - desc: Ensure 'curl' is installed + - | + declare -A tools=( + ["golangci-lint"]="github.com/golangci/golangci-lint/cmd/golangci-lint@latest" + ["godoc"]="golang.org/x/tools/cmd/godoc@latest" + ["goreleaser"]="github.com/goreleaser/goreleaser@latest" + ) + + for tool in "${!tools[@]}"; do + if ! command -v "$tool" >/dev/null 2>&1; then + echo "๐Ÿ”ง Installing $tool..." + go install "${tools[$tool]}" + else + echo "โœ… $tool already installed" + fi + done + - task: install-tools:system-deps + + install-tools:system-deps: + desc: Install system dependencies cmds: - | - if ! command -v curl >/dev/null 2>&1; then - echo "โŒ 'curl' is not installed. Please install it using your package manager." - exit 1 - else - echo "โœ… 'curl' is installed." - fi + for tool in "pigz" "zip"; do + if ! command -v $tool >/dev/null 2>&1; then + echo "๐Ÿ”ง Installing $tool..." + if command -v apt-get >/dev/null 2>&1; then + sudo apt-get update && sudo apt-get install -y $tool + elif command -v brew >/dev/null 2>&1; then + brew install $tool + elif command -v yum >/dev/null 2>&1; then + sudo yum install -y $tool + else + echo "โŒ Could not install $tool automatically. Please install manually." + exit 1 + fi + fi + done + +# ============================================================================= +# Release Tasks +# ============================================================================= + release: + desc: Create a new release + deps: [goreleaser:check] + cmds: + - task: goreleaser:snapshot - update-deps: - desc: Update project dependencies - prompt: This will update all dependencies. Continue? - deps: [update-deps:go-get, update-deps:mod-tidy] - cmds: [] + goreleaser:check: + desc: Check GoReleaser installation + preconditions: + - sh: command -v goreleaser + msg: "GoReleaser is not installed. Run 'task install-tools' first." - update-deps:go-get: - desc: Update Go modules + goreleaser:snapshot: + desc: Run GoReleaser in snapshot mode cmds: - - echo "๐Ÿ”„ Updating dependencies with go get..." - - go get -u ./... + - echo "๐Ÿš€ Testing GoReleaser build..." + - goreleaser release --snapshot --clean --skip-publish - update-deps:mod-tidy: - desc: Tidy Go modules +# ============================================================================= +# Development Tasks +# ============================================================================= + run: + desc: Run the application + deps: [build] + interactive: true cmds: - - echo "๐Ÿ”„ Tidy up Go modules..." - - go mod tidy + - echo "๐Ÿƒ Running {{.BINARY_NAME}}..." + - ./{{.BUILD_DIR}}/{{.BINARY_NAME}} - docker:build: - desc: Build Docker image with versioning - deps: [docker:check, docker:build-image] - cmds: [] - docker:check: - desc: Ensure Docker is installed - preconditions: - - sh: docker version - msg: "Docker must be installed" - cmds: [] - docker:build-image: - desc: Execute Docker build +# ============================================================================= +# Setup Tasks +# ============================================================================= + setup: + desc: Setup development environment cmds: - - echo "๐Ÿณ Building Docker image..." - - | - docker build \ - --build-arg VERSION={{.VERSION}} \ - --build-arg COMMIT={{.COMMIT}} \ - --build-arg BUILDTIME={{.BUILDTIME}} \ - -t {{.BINARY_NAME}}:{{.VERSION}} \ - -t {{.BINARY_NAME}}:latest . + - task: setup:golang + - task: setup:ensure-path + - task: deps - release: - desc: Create and push a new release - deps: [release:precheck, release:create-tag, release:push-tag] - cmds: [] - - release:precheck: - desc: Ensure working directory is clean - preconditions: - - sh: git diff-index --quiet HEAD - msg: "Working directory is not clean. Commit or stash changes first." - cmds: [] - - release:create-tag: - desc: Create Git tag + setup:golang: + desc: Install or update Go vars: - TAG: - sh: git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.1" + GO_VERSION: + sh: curl -sSL https://go.dev/VERSION?m=text | head -n1 cmds: - - echo "๐Ÿ”– Creating Git tag {{.TAG}}..." - - git tag {{.TAG}} + - | + install_go() { + OS="$(uname | tr '[:upper:]' '[:lower:]')" + ARCH="$(uname -m)" + case "${ARCH}" in + x86_64|amd64) ARCH="amd64" ;; + arm64|aarch64) ARCH="arm64" ;; + *) echo "Unsupported architecture: ${ARCH}"; exit 1 ;; + esac + + TMP_DIR=$(mktemp -d) + trap 'rm -rf "$TMP_DIR"' EXIT + cd "$TMP_DIR" + + curl -fsSL "https://go.dev/dl/{{.GO_VERSION}}.${OS}-${ARCH}.tar.gz" -o go.tar.gz + sudo rm -rf /usr/local/go + sudo tar -C /usr/local -xzf go.tar.gz + } + + if ! command -v go &>/dev/null; then + echo "Installing Go {{.GO_VERSION}}..." + install_go + else + CURRENT_VERSION="$(go version | awk '{print $3}')" + if [ "$CURRENT_VERSION" != "{{.GO_VERSION}}" ]; then + echo "Updating Go from $CURRENT_VERSION to {{.GO_VERSION}}..." + install_go + fi + fi + - task: setup:go-verify - release:push-tag: - desc: Push Git tag to origin - vars: - TAG: - sh: git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.1" + setup:go-verify: + desc: Verify Go installation cmds: - - git push origin {{.TAG}} - - watch: - desc: Watch for changes and rebuild - watch: true - sources: - - "**/*.go" - - go.mod - - go.sum + - | + if ! command -v go &>/dev/null; then + echo "โŒ Go installation failed" + exit 1 + fi + echo "โœ… Go $(go version) is installed" + + setup:ensure-path: + desc: "๐Ÿ”ง Ensure $HOME/.local/bin exists and is included in PATH" cmds: - - task: build + - mkdir -p "$HOME/.local/bin" + - | + for rc in ~/.bashrc ~/.zshrc; do + if [ -f "$rc" ]; then + if ! grep -Fxq 'export PATH="$HOME/.local/bin:$PATH"' "$rc"; then + echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$rc" + echo "โœ… Added $HOME/.local/bin to $rc" + else + echo "โœ… $HOME/.local/bin is already in $rc" + fi + else + echo "โš ๏ธ $rc does not exist. Skipping..." + fi + done diff --git a/bootstrap.sh b/bootstrap.sh index f9ef955..5459f02 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -1,414 +1,113 @@ #!/bin/bash # -# Bootstrap Script -# -# This script ensures that Go and Task are installed. +# Bootstrap script for installing Task (https://taskfile.dev). set -euo pipefail -####################################### -# Global Constants and Environment Variables -####################################### -readonly ORIGINAL_DIR="${PWD}" +readonly INSTALL_SCRIPT="https://raw.githubusercontent.com/go-task/task/main/install-task.sh" readonly SHELL_RC_FILES=("${HOME}/.bashrc" "${HOME}/.zshrc") -readonly DEFAULT_CURL_OPTS=(-fsSL --connect-timeout 10 --max-time 30) -readonly GO_VERSION_URL="https://go.dev/VERSION?m=text" -readonly GO_DOWNLOAD_URL="https://go.dev/dl" -readonly TASK_REPO_URL="https://api.github.com/repos/go-task/task" - -# Global variables (modified by functions) -declare PLATFORM="" -declare ARCH="" -declare -a TEMP_DIRS=() - -####################################### -# Error handler for unexpected failures -# Arguments: -# Exit code from failed command -# Line number of failure -####################################### -err_handler() { - local exit_code="${1}" - local line_no="${2}" - log_error "Error on line ${line_no}: Exit code ${exit_code}" -} -trap 'err_handler ${?} ${LINENO}' ERR ####################################### -# Cleanup handler for temporary resources +# Print information to stdout. # Globals: -# ORIGINAL_DIR -# TEMP_DIRS -####################################### -cleanup() { - local dir - for dir in "${TEMP_DIRS[@]}"; do - if [[ -d "${dir}" ]]; then - rm -rf "${dir}" - log_info "Cleaned up temporary directory: ${dir}" - fi - done - cd "${ORIGINAL_DIR}" || log_error "Failed to return to original directory" - log_info "Returned to original directory: ${ORIGINAL_DIR}" -} -trap cleanup EXIT - -####################################### -# Log informational messages +# None # Arguments: -# Message to log +# Any string to print as information. ####################################### -log_info() { - echo -e "[INFO] $(date +'%Y-%m-%dT%H:%M:%S%z') - ${*}" +info() { + echo "[INFO] $*" } ####################################### -# Log warning messages +# Add directory to PATH in shell rc files if not already added. +# Globals: +# SHELL_RC_FILES # Arguments: -# Message to log +# Directory to add to PATH. ####################################### -log_warn() { - echo -e "[WARN] $(date +'%Y-%m-%dT%H:%M:%S%z') - ${*}" >&2 -} +add_to_path() { + local dir="$1" + local rc_file -####################################### -# Log error messages and exit -# Arguments: -# Message to log -####################################### -log_error() { - echo -e "[ERROR] $(date +'%Y-%m-%dT%H:%M:%S%z') - ${*}" >&2 - exit 1 + for rc_file in "${SHELL_RC_FILES[@]}"; do + if [[ -f "$rc_file" ]] && ! grep -q "export PATH=\"$dir:\$PATH\"" "$rc_file"; then + echo "export PATH=\"$dir:\$PATH\"" >> "$rc_file" + info "Added $dir to PATH in $rc_file" + fi + done } ####################################### -# Version comparison +# Install Task and add it to PATH. +# Globals: +# INSTALL_SCRIPT # Arguments: -# $1: Version to check if greater than or equal to $2 -# $2: Reference version -# Returns: -# 0 if VERSION_A >= VERSION_B, 1 otherwise -####################################### -version_ge() { - [[ "$(printf '%s\n' "${2}" "${1}" | sort -V | head -n1)" == "${2}" ]] -} - -####################################### -# Get latest Task version -# Outputs: -# Latest Task version tag to STDOUT +# None ####################################### -get_latest_task_version() { - local version_json - local version_tag - - version_json="$(curl "${DEFAULT_CURL_OPTS[@]}" "${TASK_REPO_URL}/releases/latest")" - version_tag="$(echo "${version_json}" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')" +install_task() { + info "Installing Task..." + local install_dir="${HOME}/.local/bin" + mkdir -p "$install_dir" - if [[ -z "${version_tag}" ]]; then - log_error "Failed to get latest Task version" + if ! curl -fsSL "$INSTALL_SCRIPT" | sh -s -- -d -b "$install_dir"; then + info "Task installation failed." + exit 1 fi - echo "${version_tag}" -} - -####################################### -# Get Latest Go Version -# Returns: -# Latest Go version string. -####################################### -get_latest_go_version() { - local version_output - version_output="$(curl -s "${GO_VERSION_URL}" | head -n1)" - # Validate the version format - if [[ "${version_output}" =~ ^go[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "$version_output" - else - log_error "Invalid Go version format retrieved: ${version_output}" + if [[ ":$PATH:" != *":$install_dir:"* ]]; then + add_to_path "$install_dir" fi } ####################################### -# Detect system architecture and platform -# Globals Modified: -# PLATFORM -# ARCH +# Configure shell completions for Task. +# Globals: +# None +# Arguments: +# None ####################################### -detect_system() { - local os - local arch - - os="$(uname)" - arch="$(uname -m)" +setup_completions() { + local shell_type + shell_type="$(basename "$SHELL")" - case "${os}" in - Linux) - PLATFORM="linux" + case "$shell_type" in + bash) + echo 'eval "$(task --completion bash)"' >> "${HOME}/.bashrc" + info "Added bash completions" ;; - Darwin) - PLATFORM="darwin" + zsh) + echo 'eval "$(task --completion zsh)"' >> "${HOME}/.zshrc" + info "Added zsh completions" ;; - *) - log_error "Unsupported OS: ${os}" - ;; - esac - - case "${arch}" in - x86_64|amd64) - ARCH="amd64" - ;; - arm64|aarch64) - ARCH="arm64" + fish) + mkdir -p "${HOME}/.config/fish/completions" + task --completion fish > "${HOME}/.config/fish/completions/task.fish" + info "Added fish completions" ;; *) - log_error "Unsupported architecture: ${arch}" + info "Shell completions not configured for unsupported shell: $shell_type" ;; esac - - readonly PLATFORM - readonly ARCH } ####################################### -# Add directory to PATH in shell rc files -# Arguments: -# $1: Directory to add to PATH -####################################### -add_to_path() { - local dir="${1}" - local rc_file - - for rc_file in "${SHELL_RC_FILES[@]}"; do - if [[ -f "${rc_file}" ]]; then - if ! grep -Fxq "export PATH=\"${dir}:\$PATH\"" "${rc_file}"; then - echo "export PATH=\"${dir}:\$PATH\"" >> "${rc_file}" - log_info "Added ${dir} to PATH in ${rc_file}" - else - log_info "${dir} is already in PATH in ${rc_file}" - fi - fi - done -} - -####################################### -# Install Go -# Arguments: -# $1: Go version to install -####################################### -install_go() { - local go_version="${1}" - local temp_dir - local -a curl_cmd - local -a tar_cmd - - log_info "Installing Go ${go_version}..." - - temp_dir="$(mktemp -d)" - TEMP_DIRS+=("${temp_dir}") - - ( - if ! cd "${temp_dir}"; then - log_error "Failed to enter temporary directory: ${temp_dir}" - fi - - curl_cmd=("${DEFAULT_CURL_OPTS[@]}" - "${GO_DOWNLOAD_URL}/${go_version}.${PLATFORM}-${ARCH}.tar.gz" - -o "go.tar.gz") - if ! curl "${curl_cmd[@]}"; then - log_error "Failed to download Go" - fi - - tar_cmd=(sudo tar -C /usr/local -xzf "go.tar.gz") - if ! "${tar_cmd[@]}"; then - log_error "Failed to extract Go to /usr/local" - fi - - log_info "Extracted Go to /usr/local" - ) - - add_to_path "/usr/local/go/bin" - log_info "Go ${go_version} installation completed" -} - -####################################### -# Get Task download URL -# Arguments: -# $1: Task version -# Outputs: -# Download URL to STDOUT -####################################### -get_task_download_url() { - local task_version="${1}" - local assets_json - local download_url - - assets_json="$(curl "${DEFAULT_CURL_OPTS[@]}" "${TASK_REPO_URL}/releases/tags/${task_version}")" - download_url="$(echo "${assets_json}" | grep '"browser_download_url":' | \ - grep "${PLATFORM}" | grep "${ARCH}" | head -n1 | sed -E 's/.*"([^"]+)".*/\1/')" - - if [[ -z "${download_url}" || "${download_url}" == "null" ]]; then - log_error "Failed to find download URL for Task ${task_version}" - fi - - echo "${download_url}" -} - -####################################### -# Install Task -# Arguments: -# $1: Task version to install -####################################### -install_task() { - local task_version="${1}" - local download_url - local package_name - local temp_dir - local -a curl_cmd - local -a install_cmd - - log_info "Installing Task ${task_version}..." - - download_url="$(get_task_download_url "${task_version}")" - log_info "Downloading Task from ${download_url}..." - - temp_dir="$(mktemp -d)" - TEMP_DIRS+=("${temp_dir}") - - ( - if ! cd "${temp_dir}"; then - log_error "Failed to enter temporary directory: ${temp_dir}" - fi - - package_name="$(basename "${download_url}")" - curl_cmd=("${DEFAULT_CURL_OPTS[@]}" "${download_url}" -o "${package_name}") - - if ! curl "${curl_cmd[@]}"; then - log_error "Failed to download Task" - fi - - if [[ "${package_name}" == *.deb ]]; then - install_cmd=(sudo dpkg -i "${package_name}") - if ! "${install_cmd[@]}"; then - log_error "Failed to install Task .deb package" - fi - log_info "Installed Task using .deb package" - elif [[ "${package_name}" == *.tar.gz ]]; then - if ! tar -xzf "${package_name}"; then - log_error "Failed to extract Task archive" - fi - log_info "Extracted Task archive" - - if [[ -f "task" ]]; then - chmod +x task - if [[ -w "/usr/local/bin" ]]; then - sudo mv task "/usr/local/bin/" - log_info "Moved Task to /usr/local/bin" - else - mkdir -p "${HOME}/bin" - mv task "${HOME}/bin/" - log_info "Moved Task to ${HOME}/bin" - add_to_path "${HOME}/bin" - fi - else - log_error "Task binary not found in the archive" - fi - else - log_error "Unsupported package format: ${package_name}" - fi - ) - - log_info "Task ${task_version} installation completed" -} - -####################################### -# Check Go installation -####################################### -check_go_installation() { - local installed_version - local latest_go_version - - log_info "===========================================" - log_info "Checking for Go installation..." - log_info "===========================================" - - if ! command -v go &> /dev/null; then - log_info "Go not found" - latest_go_version="$(get_latest_go_version)" - install_go "${latest_go_version}" - else - installed_version="$(go version | awk '{print $3}' | sed 's/go//')" - latest_go_version="$(get_latest_go_version)" - - if version_ge "${installed_version}" "${latest_go_version}"; then - log_info "Go is up-to-date (version ${installed_version})" - else - log_info "Go version ${installed_version} is outdated. Updating to ${latest_go_version}" - install_go "${latest_go_version}" - fi - fi -} - -####################################### -# Check Task installation -####################################### -check_task_installation() { - local current_version - local latest_task_version - - log_info "===========================================" - log_info "Checking for Task installation..." - log_info "===========================================" - - if ! command -v task &> /dev/null; then - log_info "Task not found" - latest_task_version="$(get_latest_task_version)" - install_task "${latest_task_version}" - else - current_version="$(task --version | awk '{print $2}')" - latest_task_version="$(get_latest_task_version)" - - if version_ge "${current_version}" "${latest_task_version}"; then - log_info "Task is up-to-date (version ${current_version})" - else - log_info "Task version ${current_version} is outdated. Updating to ${latest_task_version}" - install_task "${latest_task_version}" - fi - fi -} - -####################################### -# Main function +# Main script execution. # Globals: -# ORIGINAL_DIR +# None # Arguments: -# Command line arguments +# None ####################################### main() { - detect_system - check_go_installation - check_task_installation - - log_info "===========================================" - log_info "Running 'go mod tidy' to ensure dependencies are up-to-date..." - log_info "===========================================" - - if ! cd "${ORIGINAL_DIR}"; then - log_error "Failed to return to original directory: ${ORIGINAL_DIR}" - fi - - if [[ -f "go.mod" ]]; then - if ! go mod tidy; then - log_error "'go mod tidy' failed" - fi - log_info "'go mod tidy' completed successfully" + if ! command -v task &>/dev/null; then + install_task + setup_completions else - log_error "go.mod file not found in the directory: ${ORIGINAL_DIR}" + info "Task is already installed." fi - log_info "===========================================" - log_info "Bootstrap complete!" - log_info "===========================================" + if [[ -f "Taskfile.yml" ]]; then + task setup + fi } -# Execute main with all script arguments main "$@" diff --git a/build/agentexec b/build/agentexec index 01033b7..2eacc6a 100755 Binary files a/build/agentexec and b/build/agentexec differ diff --git a/cmd/combine.go b/cmd/combine.go index 322641c..72571c5 100644 --- a/cmd/combine.go +++ b/cmd/combine.go @@ -1,4 +1,4 @@ -// Package cmd provides the root command and subcommands for the AgentExec CLI tool. +// File: cmd/combine.go package cmd import ( diff --git a/cmd/root.go b/cmd/root.go index 5dc57e3..cd401d0 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,4 +1,4 @@ -// Package cmd provides the root command and subcommands for the AgentExec CLI tool. +// File: cmd/root.go package cmd import ( diff --git a/cmd/version.go b/cmd/version.go index d424905..9e956e0 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -1,4 +1,4 @@ -// Package cmd provides the root command and subcommands for the AgentExec CLI tool. +// File: cmd/version.go package cmd import (