From a422c18c0e8c0db07183686cbe4f10b736abb1b0 Mon Sep 17 00:00:00 2001 From: Snider Date: Sun, 1 Feb 2026 11:36:59 +0000 Subject: [PATCH] feat(ci): add `core setup ci` and dogfood CLI in workflows - Add `core setup ci` command for generating installation scripts - Supports bash, powershell, and GitHub Actions YAML output - Configurable via .core/ci.yaml - Auto-detects platform and uses Homebrew/Scoop/direct download - Update all GitHub workflows to use global `core` binary: - ci.yml: Uses `core go qa` for all quality checks - coverage.yml: Uses `core go cov` for coverage - release.yml: Uses `core build --ci` for cross-compilation - dev-release.yml: Uses `core build --ci` for all targets - Add .core/ci.yaml with default configuration This ensures the CLI dogfoods itself across all CI operations, validating the framework that the Web3 ecosystem builds from. Co-Authored-By: Claude Opus 4.5 --- .core/ci.yaml | 18 ++ .github/workflows/ci.yml | 61 +++--- .github/workflows/coverage.yml | 23 +-- .github/workflows/dev-release.yml | 86 +++++---- .github/workflows/release.yml | 75 ++++---- pkg/gitcmd/cmd_git.go | 2 +- pkg/go/cmd_qa.go | 34 ++-- pkg/setup/cmd_ci.go | 299 ++++++++++++++++++++++++++++++ 8 files changed, 463 insertions(+), 135 deletions(-) create mode 100644 .core/ci.yaml create mode 100644 pkg/setup/cmd_ci.go diff --git a/.core/ci.yaml b/.core/ci.yaml new file mode 100644 index 00000000..c4d705e9 --- /dev/null +++ b/.core/ci.yaml @@ -0,0 +1,18 @@ +# CI configuration for core CLI installation +# Used by: core setup ci + +# Homebrew (macOS/Linux) +tap: host-uk/tap +formula: core + +# Scoop (Windows) +scoop_bucket: https://github.com/host-uk/scoop-bucket.git + +# Chocolatey (Windows) +chocolatey_pkg: core-cli + +# GitHub releases (fallback for all platforms) +repository: host-uk/core + +# Default version to install (use 'dev' for latest development build) +default_version: dev diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 76931245..08d31dba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,8 +6,11 @@ on: pull_request: branches: [dev, main] +env: + CORE_VERSION: dev + jobs: - test: + qa: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -22,29 +25,27 @@ jobs: sudo apt-get update sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev - - name: Download core CLI + - name: Install golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + install-only: true + + - name: Install core CLI run: | - curl -sL "https://github.com/host-uk/core/releases/download/dev/core-linux-amd64" -o core - chmod +x core - sudo mv core /usr/local/bin/core + curl -fsSL "https://github.com/host-uk/core/releases/download/${{ env.CORE_VERSION }}/core-linux-amd64" -o /tmp/core + chmod +x /tmp/core + sudo mv /tmp/core /usr/local/bin/core core --version + - name: Generate code + run: go generate ./pkg/updater/... + - name: Run QA run: core go qa build: runs-on: ubuntu-latest - needs: test - strategy: - matrix: - include: - - goos: linux - goarch: amd64 - - goos: darwin - goarch: arm64 - - goos: windows - goarch: amd64 - + needs: qa steps: - uses: actions/checkout@v6 @@ -53,21 +54,21 @@ jobs: with: go-version-file: 'go.mod' - - name: Build CLI - env: - GOOS: ${{ matrix.goos }} - GOARCH: ${{ matrix.goarch }} - CGO_ENABLED: '0' + - name: Install core CLI run: | - EXT="" - if [ "$GOOS" = "windows" ]; then EXT=".exe"; fi - VERSION="${GITHUB_REF_NAME:-dev}-$(git rev-parse --short HEAD)" - go build -trimpath \ - -ldflags="-s -w -X github.com/host-uk/core/pkg/cli.AppVersion=${VERSION}" \ - -o core-${GOOS}-${GOARCH}${EXT} . + curl -fsSL "https://github.com/host-uk/core/releases/download/${{ env.CORE_VERSION }}/core-linux-amd64" -o /tmp/core + chmod +x /tmp/core + sudo mv /tmp/core /usr/local/bin/core + core --version + + - name: Generate code + run: go generate ./pkg/updater/... + + - name: Build + run: core build --targets=linux/amd64,darwin/arm64,windows/amd64 --ci - name: Verify build - if: matrix.goos == 'linux' run: | - chmod +x core-linux-amd64 - ./core-linux-amd64 --version + ls -la dist/ + chmod +x dist/linux_amd64/core + dist/linux_amd64/core --version diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 65b567c7..636eca15 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -1,4 +1,4 @@ -name: Go Test Coverage +name: Coverage on: push: @@ -6,6 +6,9 @@ on: pull_request: branches: [dev, main] +env: + CORE_VERSION: dev + jobs: coverage: runs-on: ubuntu-latest @@ -17,25 +20,23 @@ jobs: with: go-version-file: 'go.mod' - - name: Setup Task - uses: arduino/setup-task@v2 - - name: Install system dependencies run: | sudo apt-get update sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev - - name: Build CLI + - name: Install core CLI run: | - go generate ./pkg/updater/... - task cli:build - echo "$(pwd)/bin" >> $GITHUB_PATH + curl -fsSL "https://github.com/host-uk/core/releases/download/${{ env.CORE_VERSION }}/core-linux-amd64" -o /tmp/core + chmod +x /tmp/core + sudo mv /tmp/core /usr/local/bin/core + core --version - - name: Verify CLI - run: bin/core --version + - name: Generate code + run: go generate ./pkg/updater/... - name: Run coverage - run: task cov + run: core go cov - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v5 diff --git a/.github/workflows/dev-release.yml b/.github/workflows/dev-release.yml index 8a460c3d..16c22f69 100644 --- a/.github/workflows/dev-release.yml +++ b/.github/workflows/dev-release.yml @@ -8,25 +8,12 @@ on: permissions: contents: write +env: + CORE_VERSION: dev + jobs: build: runs-on: ubuntu-latest - strategy: - matrix: - include: - - goos: linux - goarch: amd64 - - goos: linux - goarch: arm64 - - goos: darwin - goarch: amd64 - - goos: darwin - goarch: arm64 - - goos: windows - goarch: amd64 - - goos: windows - goarch: arm64 - steps: - uses: actions/checkout@v6 @@ -35,24 +22,24 @@ jobs: with: go-version-file: 'go.mod' - - name: Build CLI - env: - GOOS: ${{ matrix.goos }} - GOARCH: ${{ matrix.goarch }} - CGO_ENABLED: '0' + - name: Install core CLI run: | - EXT="" - if [ "$GOOS" = "windows" ]; then EXT=".exe"; fi - VERSION="dev-$(git rev-parse --short HEAD)" - go build -trimpath \ - -ldflags="-s -w -X github.com/host-uk/core/pkg/cli.AppVersion=${VERSION}" \ - -o core-${GOOS}-${GOARCH}${EXT} . + curl -fsSL "https://github.com/host-uk/core/releases/download/${{ env.CORE_VERSION }}/core-linux-amd64" -o /tmp/core + chmod +x /tmp/core + sudo mv /tmp/core /usr/local/bin/core + core --version - - name: Upload artifact + - name: Generate code + run: go generate ./pkg/updater/... + + - name: Build all targets + run: core build --targets=linux/amd64,linux/arm64,darwin/amd64,darwin/arm64,windows/amd64,windows/arm64 --ci + + - name: Upload artifacts uses: actions/upload-artifact@v4 with: - name: core-${{ matrix.goos }}-${{ matrix.goarch }} - path: core-* + name: binaries + path: dist/ release: needs: build @@ -60,17 +47,29 @@ jobs: steps: - uses: actions/checkout@v6 - - name: Download all artifacts + - name: Download artifacts uses: actions/download-artifact@v4 with: - path: artifacts - merge-multiple: true + name: binaries + path: dist - - name: Generate checksums + - name: Prepare release files run: | - cd artifacts - sha256sum core-* > CHECKSUMS.txt - cat CHECKSUMS.txt + mkdir -p release + cp dist/*.tar.gz dist/*.zip dist/CHECKSUMS.txt release/ 2>/dev/null || true + # Also copy raw binaries for direct download + for dir in dist/*/; do + if [ -d "$dir" ]; then + platform=$(basename "$dir") + for bin in "$dir"*; do + if [ -f "$bin" ]; then + name=$(basename "$bin") + cp "$bin" "release/core-${platform//_/-}${name##core}" + fi + done + fi + done + ls -la release/ - name: Delete existing dev release env: @@ -91,7 +90,18 @@ jobs: **Commit:** ${{ github.sha }} **Built:** $(date -u +'%Y-%m-%d %H:%M:%S UTC') + ## Installation + + \`\`\`bash + # macOS/Linux + curl -fsSL https://github.com/host-uk/core/releases/download/dev/core-linux-amd64 -o core + chmod +x core && sudo mv core /usr/local/bin/ + + # Or with Homebrew + brew tap host-uk/tap && brew install host-uk/tap/core + \`\`\` + This is a pre-release for testing. Use tagged releases for production." \ --prerelease \ --target dev \ - artifacts/* + release/* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d6e140ec..1d98ef27 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,25 +8,12 @@ on: permissions: contents: write +env: + CORE_VERSION: dev + jobs: build: runs-on: ubuntu-latest - strategy: - matrix: - include: - - goos: linux - goarch: amd64 - - goos: linux - goarch: arm64 - - goos: darwin - goarch: amd64 - - goos: darwin - goarch: arm64 - - goos: windows - goarch: amd64 - - goos: windows - goarch: arm64 - steps: - uses: actions/checkout@v6 @@ -35,24 +22,24 @@ jobs: with: go-version-file: 'go.mod' - - name: Build CLI - env: - GOOS: ${{ matrix.goos }} - GOARCH: ${{ matrix.goarch }} - CGO_ENABLED: '0' - VERSION: ${{ github.ref_name }} + - name: Install core CLI run: | - EXT="" - if [ "$GOOS" = "windows" ]; then EXT=".exe"; fi - go build -trimpath \ - -ldflags="-s -w -X github.com/host-uk/core/pkg/cli.AppVersion=${VERSION}" \ - -o core-${GOOS}-${GOARCH}${EXT} . + curl -fsSL "https://github.com/host-uk/core/releases/download/${{ env.CORE_VERSION }}/core-linux-amd64" -o /tmp/core + chmod +x /tmp/core + sudo mv /tmp/core /usr/local/bin/core + core --version - - name: Upload artifact + - name: Generate code + run: go generate ./pkg/updater/... + + - name: Build all targets + run: core build --targets=linux/amd64,linux/arm64,darwin/amd64,darwin/arm64,windows/amd64,windows/arm64 --ci + + - name: Upload artifacts uses: actions/upload-artifact@v4 with: - name: core-${{ matrix.goos }}-${{ matrix.goarch }} - path: core-* + name: binaries + path: dist/ release: needs: build @@ -60,17 +47,29 @@ jobs: steps: - uses: actions/checkout@v6 - - name: Download all artifacts + - name: Download artifacts uses: actions/download-artifact@v4 with: - path: artifacts - merge-multiple: true + name: binaries + path: dist - - name: Generate checksums + - name: Prepare release files run: | - cd artifacts - sha256sum core-* > CHECKSUMS.txt - cat CHECKSUMS.txt + mkdir -p release + cp dist/*.tar.gz dist/*.zip dist/CHECKSUMS.txt release/ 2>/dev/null || true + # Also copy raw binaries for direct download + for dir in dist/*/; do + if [ -d "$dir" ]; then + platform=$(basename "$dir") + for bin in "$dir"*; do + if [ -f "$bin" ]; then + name=$(basename "$bin") + cp "$bin" "release/core-${platform//_/-}${name##core}" + fi + done + fi + done + ls -la release/ - name: Create release env: @@ -79,4 +78,4 @@ jobs: gh release create ${{ github.ref_name }} \ --title "Release ${{ github.ref_name }}" \ --generate-notes \ - artifacts/* + release/* diff --git a/pkg/gitcmd/cmd_git.go b/pkg/gitcmd/cmd_git.go index c54d62af..7c6d3697 100644 --- a/pkg/gitcmd/cmd_git.go +++ b/pkg/gitcmd/cmd_git.go @@ -32,7 +32,7 @@ func AddGitCommands(root *cli.Command) { root.AddCommand(gitCmd) // Import git commands from dev package - dev.AddHealthCommand(gitCmd) // Shows repo status + dev.AddHealthCommand(gitCmd) // Shows repo status dev.AddCommitCommand(gitCmd) dev.AddPushCommand(gitCmd) dev.AddPullCommand(gitCmd) diff --git a/pkg/go/cmd_qa.go b/pkg/go/cmd_qa.go index 24501208..51af1b81 100644 --- a/pkg/go/cmd_qa.go +++ b/pkg/go/cmd_qa.go @@ -16,24 +16,24 @@ import ( // QA command flags - comprehensive options for all agents var ( - qaFix bool - qaChanged bool - qaAll bool - qaSkip string - qaOnly string - qaCoverage bool - qaThreshold float64 + qaFix bool + qaChanged bool + qaAll bool + qaSkip string + qaOnly string + qaCoverage bool + qaThreshold float64 qaDocblockThreshold float64 - qaJSON bool - qaVerbose bool - qaQuiet bool - qaTimeout time.Duration - qaShort bool - qaRace bool - qaBench bool - qaFailFast bool - qaMod bool - qaCI bool + qaJSON bool + qaVerbose bool + qaQuiet bool + qaTimeout time.Duration + qaShort bool + qaRace bool + qaBench bool + qaFailFast bool + qaMod bool + qaCI bool ) func addGoQACommand(parent *cli.Command) { diff --git a/pkg/setup/cmd_ci.go b/pkg/setup/cmd_ci.go new file mode 100644 index 00000000..cad86332 --- /dev/null +++ b/pkg/setup/cmd_ci.go @@ -0,0 +1,299 @@ +package setup + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + + "github.com/host-uk/core/pkg/cli" + "github.com/spf13/cobra" + "gopkg.in/yaml.v3" +) + +// CIConfig holds CI setup configuration from .core/ci.yaml +type CIConfig struct { + // Homebrew tap (e.g., "host-uk/tap") + Tap string `yaml:"tap"` + // Formula name (defaults to "core") + Formula string `yaml:"formula"` + // Scoop bucket URL + ScoopBucket string `yaml:"scoop_bucket"` + // Chocolatey package name + ChocolateyPkg string `yaml:"chocolatey_pkg"` + // GitHub repository for direct downloads + Repository string `yaml:"repository"` + // Default version to install + DefaultVersion string `yaml:"default_version"` +} + +// DefaultCIConfig returns the default CI configuration. +func DefaultCIConfig() *CIConfig { + return &CIConfig{ + Tap: "host-uk/tap", + Formula: "core", + ScoopBucket: "https://github.com/host-uk/scoop-bucket.git", + ChocolateyPkg: "core-cli", + Repository: "host-uk/core", + DefaultVersion: "dev", + } +} + +// LoadCIConfig loads CI configuration from .core/ci.yaml +func LoadCIConfig() *CIConfig { + cfg := DefaultCIConfig() + + // Try to find .core/ci.yaml in current directory or parents + dir, err := os.Getwd() + if err != nil { + return cfg + } + + for { + configPath := filepath.Join(dir, ".core", "ci.yaml") + data, err := os.ReadFile(configPath) + if err == nil { + if err := yaml.Unmarshal(data, cfg); err == nil { + return cfg + } + } + + parent := filepath.Dir(dir) + if parent == dir { + break + } + dir = parent + } + + return cfg +} + +// CI setup command flags +var ( + ciShell string + ciVersion string +) + +func init() { + ciCmd := &cobra.Command{ + Use: "ci", + Short: "Output CI installation commands for core CLI", + Long: `Output installation commands for the core CLI in CI environments. + +Generates shell commands to install the core CLI using the appropriate +package manager for each platform: + + macOS/Linux: Homebrew (brew install host-uk/tap/core) + Windows: Scoop or Chocolatey, or direct download + +Configuration can be customized via .core/ci.yaml: + + tap: host-uk/tap # Homebrew tap + formula: core # Homebrew formula name + scoop_bucket: https://... # Scoop bucket URL + chocolatey_pkg: core-cli # Chocolatey package name + repository: host-uk/core # GitHub repo for direct downloads + default_version: dev # Default version to install + +Examples: + # Output installation commands for current platform + core setup ci + + # Output for specific shell (bash, powershell, yaml) + core setup ci --shell=bash + core setup ci --shell=powershell + core setup ci --shell=yaml + + # Install specific version + core setup ci --version=v1.0.0 + + # Use in GitHub Actions (pipe to shell) + eval "$(core setup ci --shell=bash)"`, + RunE: runSetupCI, + } + + ciCmd.Flags().StringVar(&ciShell, "shell", "", "Output format: bash, powershell, yaml (auto-detected if not specified)") + ciCmd.Flags().StringVar(&ciVersion, "version", "", "Version to install (tag name or 'dev' for latest dev build)") + + setupCmd.AddCommand(ciCmd) +} + +func runSetupCI(cmd *cobra.Command, args []string) error { + cfg := LoadCIConfig() + + // Use flag version or config default + version := ciVersion + if version == "" { + version = cfg.DefaultVersion + } + + // Auto-detect shell if not specified + shell := ciShell + if shell == "" { + if runtime.GOOS == "windows" { + shell = "powershell" + } else { + shell = "bash" + } + } + + switch shell { + case "bash", "sh": + return outputBashInstall(cfg, version) + case "powershell", "pwsh", "ps1": + return outputPowershellInstall(cfg, version) + case "yaml", "yml", "gha", "github": + return outputGitHubActionsYAML(cfg, version) + default: + return cli.Err("unsupported shell: %s (use bash, powershell, or yaml)", shell) + } +} + +func outputBashInstall(cfg *CIConfig, version string) error { + script := fmt.Sprintf(`#!/bin/bash +set -e + +VERSION="%s" +REPO="%s" +TAP="%s" +FORMULA="%s" + +# Detect OS and architecture +OS="$(uname -s | 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 + +# Try Homebrew first on macOS/Linux +if command -v brew &>/dev/null; then + echo "Installing via Homebrew..." + brew tap "$TAP" 2>/dev/null || true + if [ "$VERSION" = "dev" ]; then + brew install "${TAP}/${FORMULA}" --HEAD 2>/dev/null || brew upgrade "${TAP}/${FORMULA}" --fetch-HEAD 2>/dev/null || brew install "${TAP}/${FORMULA}" + else + brew install "${TAP}/${FORMULA}" + fi + %s --version + exit 0 +fi + +# Fall back to direct download +echo "Installing %s CLI ${VERSION} for ${OS}/${ARCH}..." + +DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${VERSION}/%s-${OS}-${ARCH}" + +# Download binary +curl -fsSL "$DOWNLOAD_URL" -o /tmp/%s +chmod +x /tmp/%s + +# Install to /usr/local/bin (requires sudo on most systems) +if [ -w /usr/local/bin ]; then + mv /tmp/%s /usr/local/bin/%s +else + sudo mv /tmp/%s /usr/local/bin/%s +fi + +echo "Installed:" +%s --version +`, version, cfg.Repository, cfg.Tap, cfg.Formula, + cfg.Formula, cfg.Formula, cfg.Formula, + cfg.Formula, cfg.Formula, cfg.Formula, cfg.Formula, cfg.Formula, cfg.Formula, cfg.Formula) + + fmt.Print(script) + return nil +} + +func outputPowershellInstall(cfg *CIConfig, version string) error { + script := fmt.Sprintf(`# PowerShell installation script for %s CLI +$ErrorActionPreference = "Stop" + +$Version = "%s" +$Repo = "%s" +$ScoopBucket = "%s" +$ChocoPkg = "%s" +$BinaryName = "%s" +$Arch = if ([Environment]::Is64BitOperatingSystem) { "amd64" } else { "386" } + +# Try Scoop first +if (Get-Command scoop -ErrorAction SilentlyContinue) { + Write-Host "Installing via Scoop..." + scoop bucket add host-uk $ScoopBucket 2>$null + scoop install "host-uk/$BinaryName" + & $BinaryName --version + exit 0 +} + +# Try Chocolatey +if (Get-Command choco -ErrorAction SilentlyContinue) { + Write-Host "Installing via Chocolatey..." + choco install $ChocoPkg -y + & $BinaryName --version + exit 0 +} + +# Fall back to direct download +Write-Host "Installing $BinaryName CLI $Version for windows/$Arch..." + +$DownloadUrl = "https://github.com/$Repo/releases/download/$Version/$BinaryName-windows-$Arch.exe" +$InstallDir = "$env:LOCALAPPDATA\Programs\$BinaryName" +$BinaryPath = "$InstallDir\$BinaryName.exe" + +# Create install directory +New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null + +# Download binary +Invoke-WebRequest -Uri $DownloadUrl -OutFile $BinaryPath + +# Add to PATH if not already there +$CurrentPath = [Environment]::GetEnvironmentVariable("Path", "User") +if ($CurrentPath -notlike "*$InstallDir*") { + [Environment]::SetEnvironmentVariable("Path", "$CurrentPath;$InstallDir", "User") + $env:Path = "$env:Path;$InstallDir" +} + +Write-Host "Installed:" +& $BinaryPath --version +`, cfg.Formula, version, cfg.Repository, cfg.ScoopBucket, cfg.ChocolateyPkg, cfg.Formula) + + fmt.Print(script) + return nil +} + +func outputGitHubActionsYAML(cfg *CIConfig, version string) error { + yaml := fmt.Sprintf(`# GitHub Actions steps to install %s CLI +# Add these to your workflow file + +# Option 1: Direct download (fastest, no extra dependencies) +- name: Install %s CLI + shell: bash + run: | + VERSION="%s" + REPO="%s" + BINARY="%s" + OS="$(uname -s | tr '[:upper:]' '[:lower:]')" + ARCH="$(uname -m)" + case "$ARCH" in + x86_64|amd64) ARCH="amd64" ;; + arm64|aarch64) ARCH="arm64" ;; + esac + curl -fsSL "https://github.com/${REPO}/releases/download/${VERSION}/${BINARY}-${OS}-${ARCH}" -o "${BINARY}" + chmod +x "${BINARY}" + sudo mv "${BINARY}" /usr/local/bin/ + %s --version + +# Option 2: Homebrew (better for caching, includes dependencies) +- name: Install %s CLI (Homebrew) + run: | + brew tap %s + brew install %s/%s + %s --version +`, cfg.Formula, cfg.Formula, version, cfg.Repository, cfg.Formula, cfg.Formula, + cfg.Formula, cfg.Tap, cfg.Tap, cfg.Formula, cfg.Formula) + + fmt.Print(yaml) + return nil +}