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

View file

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

View file

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

View file

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

View file

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

View file

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

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
}