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 <noreply@anthropic.com>
This commit is contained in:
Snider 2026-02-01 11:36:59 +00:00
parent 3cbb04f512
commit e28fc52456
8 changed files with 463 additions and 135 deletions

18
.core/ci.yaml Normal file
View file

@ -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

View file

@ -6,8 +6,11 @@ on:
pull_request: pull_request:
branches: [dev, main] branches: [dev, main]
env:
CORE_VERSION: dev
jobs: jobs:
test: qa:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
@ -22,29 +25,27 @@ jobs:
sudo apt-get update sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev 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: | run: |
curl -sL "https://github.com/host-uk/core/releases/download/dev/core-linux-amd64" -o core curl -fsSL "https://github.com/host-uk/core/releases/download/${{ env.CORE_VERSION }}/core-linux-amd64" -o /tmp/core
chmod +x core chmod +x /tmp/core
sudo mv core /usr/local/bin/core sudo mv /tmp/core /usr/local/bin/core
core --version core --version
- name: Generate code
run: go generate ./pkg/updater/...
- name: Run QA - name: Run QA
run: core go qa run: core go qa
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: test needs: qa
strategy:
matrix:
include:
- goos: linux
goarch: amd64
- goos: darwin
goarch: arm64
- goos: windows
goarch: amd64
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
@ -53,21 +54,21 @@ jobs:
with: with:
go-version-file: 'go.mod' go-version-file: 'go.mod'
- name: Build CLI - name: Install core CLI
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: '0'
run: | run: |
EXT="" curl -fsSL "https://github.com/host-uk/core/releases/download/${{ env.CORE_VERSION }}/core-linux-amd64" -o /tmp/core
if [ "$GOOS" = "windows" ]; then EXT=".exe"; fi chmod +x /tmp/core
VERSION="${GITHUB_REF_NAME:-dev}-$(git rev-parse --short HEAD)" sudo mv /tmp/core /usr/local/bin/core
go build -trimpath \ core --version
-ldflags="-s -w -X github.com/host-uk/core/pkg/cli.AppVersion=${VERSION}" \
-o core-${GOOS}-${GOARCH}${EXT} . - name: Generate code
run: go generate ./pkg/updater/...
- name: Build
run: core build --targets=linux/amd64,darwin/arm64,windows/amd64 --ci
- name: Verify build - name: Verify build
if: matrix.goos == 'linux'
run: | run: |
chmod +x core-linux-amd64 ls -la dist/
./core-linux-amd64 --version chmod +x dist/linux_amd64/core
dist/linux_amd64/core --version

View file

@ -1,4 +1,4 @@
name: Go Test Coverage name: Coverage
on: on:
push: push:
@ -6,6 +6,9 @@ on:
pull_request: pull_request:
branches: [dev, main] branches: [dev, main]
env:
CORE_VERSION: dev
jobs: jobs:
coverage: coverage:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -17,25 +20,23 @@ jobs:
with: with:
go-version-file: 'go.mod' go-version-file: 'go.mod'
- name: Setup Task
uses: arduino/setup-task@v2
- name: Install system dependencies - name: Install system dependencies
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev
- name: Build CLI - name: Install core CLI
run: | run: |
go generate ./pkg/updater/... curl -fsSL "https://github.com/host-uk/core/releases/download/${{ env.CORE_VERSION }}/core-linux-amd64" -o /tmp/core
task cli:build chmod +x /tmp/core
echo "$(pwd)/bin" >> $GITHUB_PATH sudo mv /tmp/core /usr/local/bin/core
core --version
- name: Verify CLI - name: Generate code
run: bin/core --version run: go generate ./pkg/updater/...
- name: Run coverage - name: Run coverage
run: task cov run: core go cov
- name: Upload coverage reports to Codecov - name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5 uses: codecov/codecov-action@v5

View file

@ -8,25 +8,12 @@ on:
permissions: permissions:
contents: write contents: write
env:
CORE_VERSION: dev
jobs: jobs:
build: build:
runs-on: ubuntu-latest 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: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
@ -35,24 +22,24 @@ jobs:
with: with:
go-version-file: 'go.mod' go-version-file: 'go.mod'
- name: Build CLI - name: Install core CLI
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: '0'
run: | run: |
EXT="" curl -fsSL "https://github.com/host-uk/core/releases/download/${{ env.CORE_VERSION }}/core-linux-amd64" -o /tmp/core
if [ "$GOOS" = "windows" ]; then EXT=".exe"; fi chmod +x /tmp/core
VERSION="dev-$(git rev-parse --short HEAD)" sudo mv /tmp/core /usr/local/bin/core
go build -trimpath \ core --version
-ldflags="-s -w -X github.com/host-uk/core/pkg/cli.AppVersion=${VERSION}" \
-o core-${GOOS}-${GOARCH}${EXT} .
- 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 uses: actions/upload-artifact@v4
with: with:
name: core-${{ matrix.goos }}-${{ matrix.goarch }} name: binaries
path: core-* path: dist/
release: release:
needs: build needs: build
@ -60,17 +47,29 @@ jobs:
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
- name: Download all artifacts - name: Download artifacts
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
path: artifacts name: binaries
merge-multiple: true path: dist
- name: Generate checksums - name: Prepare release files
run: | run: |
cd artifacts mkdir -p release
sha256sum core-* > CHECKSUMS.txt cp dist/*.tar.gz dist/*.zip dist/CHECKSUMS.txt release/ 2>/dev/null || true
cat CHECKSUMS.txt # 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 - name: Delete existing dev release
env: env:
@ -91,7 +90,18 @@ jobs:
**Commit:** ${{ github.sha }} **Commit:** ${{ github.sha }}
**Built:** $(date -u +'%Y-%m-%d %H:%M:%S UTC') **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." \ This is a pre-release for testing. Use tagged releases for production." \
--prerelease \ --prerelease \
--target dev \ --target dev \
artifacts/* release/*

View file

@ -8,25 +8,12 @@ on:
permissions: permissions:
contents: write contents: write
env:
CORE_VERSION: dev
jobs: jobs:
build: build:
runs-on: ubuntu-latest 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: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
@ -35,24 +22,24 @@ jobs:
with: with:
go-version-file: 'go.mod' go-version-file: 'go.mod'
- name: Build CLI - name: Install core CLI
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: '0'
VERSION: ${{ github.ref_name }}
run: | run: |
EXT="" curl -fsSL "https://github.com/host-uk/core/releases/download/${{ env.CORE_VERSION }}/core-linux-amd64" -o /tmp/core
if [ "$GOOS" = "windows" ]; then EXT=".exe"; fi chmod +x /tmp/core
go build -trimpath \ sudo mv /tmp/core /usr/local/bin/core
-ldflags="-s -w -X github.com/host-uk/core/pkg/cli.AppVersion=${VERSION}" \ core --version
-o core-${GOOS}-${GOARCH}${EXT} .
- 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 uses: actions/upload-artifact@v4
with: with:
name: core-${{ matrix.goos }}-${{ matrix.goarch }} name: binaries
path: core-* path: dist/
release: release:
needs: build needs: build
@ -60,17 +47,29 @@ jobs:
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
- name: Download all artifacts - name: Download artifacts
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
path: artifacts name: binaries
merge-multiple: true path: dist
- name: Generate checksums - name: Prepare release files
run: | run: |
cd artifacts mkdir -p release
sha256sum core-* > CHECKSUMS.txt cp dist/*.tar.gz dist/*.zip dist/CHECKSUMS.txt release/ 2>/dev/null || true
cat CHECKSUMS.txt # 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 - name: Create release
env: env:
@ -79,4 +78,4 @@ jobs:
gh release create ${{ github.ref_name }} \ gh release create ${{ github.ref_name }} \
--title "Release ${{ github.ref_name }}" \ --title "Release ${{ github.ref_name }}" \
--generate-notes \ --generate-notes \
artifacts/* release/*

View file

@ -32,7 +32,7 @@ func AddGitCommands(root *cli.Command) {
root.AddCommand(gitCmd) root.AddCommand(gitCmd)
// Import git commands from dev package // Import git commands from dev package
dev.AddHealthCommand(gitCmd) // Shows repo status dev.AddHealthCommand(gitCmd) // Shows repo status
dev.AddCommitCommand(gitCmd) dev.AddCommitCommand(gitCmd)
dev.AddPushCommand(gitCmd) dev.AddPushCommand(gitCmd)
dev.AddPullCommand(gitCmd) dev.AddPullCommand(gitCmd)

View file

@ -16,24 +16,24 @@ import (
// QA command flags - comprehensive options for all agents // QA command flags - comprehensive options for all agents
var ( var (
qaFix bool qaFix bool
qaChanged bool qaChanged bool
qaAll bool qaAll bool
qaSkip string qaSkip string
qaOnly string qaOnly string
qaCoverage bool qaCoverage bool
qaThreshold float64 qaThreshold float64
qaDocblockThreshold float64 qaDocblockThreshold float64
qaJSON bool qaJSON bool
qaVerbose bool qaVerbose bool
qaQuiet bool qaQuiet bool
qaTimeout time.Duration qaTimeout time.Duration
qaShort bool qaShort bool
qaRace bool qaRace bool
qaBench bool qaBench bool
qaFailFast bool qaFailFast bool
qaMod bool qaMod bool
qaCI bool qaCI bool
) )
func addGoQACommand(parent *cli.Command) { func addGoQACommand(parent *cli.Command) {

299
pkg/setup/cmd_ci.go Normal file
View file

@ -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
}