From 15f6abccbd16f95fd1859e2b51a0dc91127ef2b6 Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Sat, 6 Sep 2025 14:06:22 +0200 Subject: [PATCH 1/2] feat: distribute binaries via npm platform packages Replace GitHub Releases / Homebrew distribution with the Sentry-style npm platform package pattern. Users now install via `npm install -g archgate`. - Add packages/archgate-darwin-arm64 and packages/archgate-linux-x64 with os/cpu fields and placeholder version 0.0.0 (CI sets from release tag) - Add bin/archgate.cjs CJS wrapper (required by "type": "module" root) - Update package.json: bin field, optionalDependencies, files, drop build scripts - Rewrite release-binaries.yml: drop update-homebrew job, publish to npm via OIDC (contents: read, id-token: write, setup-node@v4) - Remove scripts/install.sh, scripts/archgate.rb, scripts/build.ts Co-Authored-By: Claude Sonnet 4.6 --- .../agent-memory/archgate-developer/MEMORY.md | 8 ++ .github/workflows/release-binaries.yml | 90 +++----------- .github/workflows/release.yml | 2 +- .gitignore | 3 + .prettierignore | 1 + bin/archgate.cjs | 38 ++++++ package.json | 13 +- packages/archgate-darwin-arm64/bin/.gitkeep | 0 packages/archgate-darwin-arm64/package.json | 19 +++ packages/archgate-linux-x64/bin/.gitkeep | 0 packages/archgate-linux-x64/package.json | 19 +++ scripts/archgate.rb | 35 ------ scripts/build.ts | 87 ------------- scripts/install.sh | 114 ------------------ 14 files changed, 116 insertions(+), 313 deletions(-) create mode 100755 bin/archgate.cjs create mode 100644 packages/archgate-darwin-arm64/bin/.gitkeep create mode 100644 packages/archgate-darwin-arm64/package.json create mode 100644 packages/archgate-linux-x64/bin/.gitkeep create mode 100644 packages/archgate-linux-x64/package.json delete mode 100644 scripts/archgate.rb delete mode 100644 scripts/build.ts delete mode 100644 scripts/install.sh diff --git a/.claude/agent-memory/archgate-developer/MEMORY.md b/.claude/agent-memory/archgate-developer/MEMORY.md index f565b1a5..22f85a97 100644 --- a/.claude/agent-memory/archgate-developer/MEMORY.md +++ b/.claude/agent-memory/archgate-developer/MEMORY.md @@ -48,6 +48,14 @@ Skipping steps 2 or 3 is a workflow violation. The user should NEVER have to inv - **`archgate` command = `bun run cli`** — This is the CLI repo itself, so the `archgate` binary is not installed in PATH. Use `bun run cli ` (e.g., `bun run cli check`, `bun run cli adr list`) instead of `archgate `. The `bun run cli` script maps to `bun run src/cli.ts`. +## Distribution / Packaging + +- **npm binary distribution (Sentry pattern)** — Binaries are distributed as platform npm packages (`archgate-darwin-arm64`, `archgate-linux-x64`) listed as `optionalDependencies` of the main `archgate` package. Main package has a `bin` field pointing to `bin/archgate.cjs`. +- **`.cjs` extension is mandatory** — Root `package.json` has `"type": "module"`. Any Node.js CJS wrapper script placed at the package root MUST use `.cjs`, not `.js`, or Node.js will attempt to parse it as ESM and fail. +- **Platform package version placeholder** — Platform `package.json` files start at version `0.0.0`. CI runs `npm version "$VERSION" --no-git-tag-version` inside the package directory before publishing. +- **Gitignore and prettierignore rules** — `packages/*/bin/archgate` is gitignored (binary injected by CI); `packages/*/bin/` is prettier-ignored (binary files must not be formatted). +- **npm OIDC publishing** — Uses `id-token: write` permission + `actions/setup-node@v4` with `registry-url: 'https://registry.npmjs.org'`. No `NODE_AUTH_TOKEN` secret needed for OIDC trusted publishing — matches the pattern in `release.yml`. + ## MCP Tools Structure - MCP tools live in `src/mcp/tools/` — one file per tool (check, list-adrs, review-context, session-context) diff --git a/.github/workflows/release-binaries.yml b/.github/workflows/release-binaries.yml index 745ddc23..595c4a31 100644 --- a/.github/workflows/release-binaries.yml +++ b/.github/workflows/release-binaries.yml @@ -10,11 +10,12 @@ on: required: true permissions: - contents: write + contents: read + id-token: write jobs: - build: - name: Build ${{ matrix.artifact }} + build-and-publish: + name: Build and publish ${{ matrix.artifact }} timeout-minutes: 15 strategy: fail-fast: false @@ -29,21 +30,17 @@ jobs: runs-on: ${{ matrix.os }} steps: - - name: Checkout - uses: actions/checkout@v4 + - uses: actions/checkout@v4 with: ref: ${{ github.event.release.tag_name || inputs.tag }} - - name: Setup toolchain (proto) - uses: moonrepo/setup-toolchain@v0 + - uses: moonrepo/setup-toolchain@v0 with: auto-install: true - - name: Install dependencies - run: bun install --frozen-lockfile + - run: bun install --frozen-lockfile - - name: Validate - run: bun run validate + - run: bun run validate - name: Build binary run: | @@ -54,69 +51,18 @@ jobs: --target ${{ matrix.target }} \ --outfile dist/${{ matrix.artifact }} - - name: Generate checksum - run: | - cd dist - sha256sum ${{ matrix.artifact }} > ${{ matrix.artifact }}.sha256 - cat ${{ matrix.artifact }}.sha256 - - - name: Upload to release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ github.event.release.tag_name || inputs.tag }} - files: | - dist/${{ matrix.artifact }} - dist/${{ matrix.artifact }}.sha256 - - update-homebrew: - name: Update Homebrew tap - needs: build - timeout-minutes: 5 - runs-on: ubuntu-latest - - steps: - - name: Checkout CLI repo - uses: actions/checkout@v4 - with: - ref: ${{ github.event.release.tag_name || inputs.tag }} - - - name: Resolve tag - id: tag + - name: Copy binary into npm package run: | TAG="${{ github.event.release.tag_name || inputs.tag }}" - echo "tag=${TAG}" >> "$GITHUB_OUTPUT" - echo "version=${TAG#v}" >> "$GITHUB_OUTPUT" - - - name: Download checksums - run: | - BASE="https://github.com/archgate/cli/releases/download/${{ steps.tag.outputs.tag }}" - curl -fsSL "${BASE}/archgate-darwin-arm64.sha256" -o darwin-arm64.sha256 - curl -fsSL "${BASE}/archgate-linux-x64.sha256" -o linux-x64.sha256 - SHA_DARWIN=$(awk '{print $1}' darwin-arm64.sha256) - SHA_LINUX=$(awk '{print $1}' linux-x64.sha256) - echo "sha_darwin=${SHA_DARWIN}" >> "$GITHUB_ENV" - echo "sha_linux=${SHA_LINUX}" >> "$GITHUB_ENV" + VERSION="${TAG#v}" + cp dist/${{ matrix.artifact }} packages/${{ matrix.artifact }}/bin/archgate + chmod +x packages/${{ matrix.artifact }}/bin/archgate + cd packages/${{ matrix.artifact }} + npm version "$VERSION" --no-git-tag-version - - name: Checkout homebrew-tap - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: - repository: archgate/homebrew-tap - path: homebrew-tap - token: ${{ secrets.HOMEBREW_TAP_TOKEN }} + registry-url: "https://registry.npmjs.org" - - name: Update formula - run: | - VERSION="${{ steps.tag.outputs.version }}" - sed -i "s/PLACEHOLDER_VERSION/${VERSION}/g" scripts/archgate.rb - sed -i "s/PLACEHOLDER_SHA256_DARWIN_ARM64/${{ env.sha_darwin }}/g" scripts/archgate.rb - sed -i "s/PLACEHOLDER_SHA256_LINUX_X64/${{ env.sha_linux }}/g" scripts/archgate.rb - cp scripts/archgate.rb homebrew-tap/Formula/archgate.rb - - - name: Commit and push formula - run: | - cd homebrew-tap - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add Formula/archgate.rb - git commit -m "chore: update archgate to ${{ steps.tag.outputs.tag }}" - git push + - name: Publish platform package + run: npm publish packages/${{ matrix.artifact }} --access public diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5b78308b..c7b67d36 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -127,7 +127,7 @@ jobs: - name: Setup Node.js for npm publish uses: actions/setup-node@v4 with: - registry-url: 'https://registry.npmjs.org' + registry-url: "https://registry.npmjs.org" - name: Release uses: TrigenSoftware/simple-release-action@v1 with: diff --git a/.gitignore b/.gitignore index 3e96617f..684b65f7 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ coverage # Package manager *.tgz .npm + +# Platform package binaries (injected by CI) +packages/*/bin/archgate diff --git a/.prettierignore b/.prettierignore index 7f1836cb..0ad8ca06 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,3 +3,4 @@ dist coverage bun.lock CHANGELOG.md +packages/*/bin/ diff --git a/bin/archgate.cjs b/bin/archgate.cjs new file mode 100755 index 00000000..c06960cd --- /dev/null +++ b/bin/archgate.cjs @@ -0,0 +1,38 @@ +#!/usr/bin/env node +"use strict"; + +const { execFileSync } = require("child_process"); +const path = require("path"); +const fs = require("fs"); + +function getPlatformPackageName() { + const { platform, arch } = process; + if (platform === "darwin" && arch === "arm64") return "archgate-darwin-arm64"; + if (platform === "linux" && arch === "x64") return "archgate-linux-x64"; + throw new Error( + `Unsupported platform: ${platform}/${arch}\narchgate supports darwin/arm64 and linux/x64 only.` + ); +} + +function getBinaryPath() { + const pkgName = getPlatformPackageName(); + try { + const pkgDir = path.dirname(require.resolve(`${pkgName}/package.json`)); + const binaryPath = path.join(pkgDir, "bin", "archgate"); + if (fs.existsSync(binaryPath)) return binaryPath; + } catch { + /* platform package not installed */ + } + throw new Error( + `archgate binary not found. "${getPlatformPackageName()}" may not be installed.\nTry reinstalling: npm install -g archgate` + ); +} + +try { + const binary = getBinaryPath(); + execFileSync(binary, process.argv.slice(2), { stdio: "inherit" }); +} catch (e) { + if (typeof e.status === "number") process.exit(e.status); + console.error(e.message); + process.exit(2); +} diff --git a/package.json b/package.json index 4243ffc9..e29114b8 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,15 @@ "readme": "README.md", "license": "FSL-1.1-ALv2", "homepage": "https://archgate.dev", + "bin": { + "archgate": "bin/archgate.cjs" + }, "exports": { "./rules": "./src/formats/rules.ts" }, "files": [ - "src/formats/rules.ts" + "src/formats/rules.ts", + "bin/archgate.cjs" ], "author": { "name": "Archgate", @@ -43,9 +47,6 @@ "build:check": "bun build src/cli.ts --compile --bytecode --outfile dist/.build-check && rm -f dist/.build-check", "validate": "bun run lint && bun run typecheck && bun run format:check && bun test && bun run check && bun run build:check", "commit": "cz", - "build": "bun run scripts/build.ts", - "build:darwin": "bun run scripts/build.ts --target darwin-arm64", - "build:linux": "bun run scripts/build.ts --target linux-x64", "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0" }, "type": "module", @@ -69,5 +70,9 @@ }, "peerDependencies": { "typescript": "^5" + }, + "optionalDependencies": { + "archgate-darwin-arm64": ">=0.1.1", + "archgate-linux-x64": ">=0.1.1" } } diff --git a/packages/archgate-darwin-arm64/bin/.gitkeep b/packages/archgate-darwin-arm64/bin/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/packages/archgate-darwin-arm64/package.json b/packages/archgate-darwin-arm64/package.json new file mode 100644 index 00000000..ab0ceddb --- /dev/null +++ b/packages/archgate-darwin-arm64/package.json @@ -0,0 +1,19 @@ +{ + "name": "archgate-darwin-arm64", + "version": "0.0.0", + "description": "darwin arm64 binary for archgate", + "os": [ + "darwin" + ], + "cpu": [ + "arm64" + ], + "files": [ + "bin/archgate" + ], + "license": "FSL-1.1-ALv2", + "repository": { + "type": "git", + "url": "git+https://github.com/archgate/cli.git" + } +} diff --git a/packages/archgate-linux-x64/bin/.gitkeep b/packages/archgate-linux-x64/bin/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/packages/archgate-linux-x64/package.json b/packages/archgate-linux-x64/package.json new file mode 100644 index 00000000..89f14cb1 --- /dev/null +++ b/packages/archgate-linux-x64/package.json @@ -0,0 +1,19 @@ +{ + "name": "archgate-linux-x64", + "version": "0.0.0", + "description": "linux x64 binary for archgate", + "os": [ + "linux" + ], + "cpu": [ + "x64" + ], + "files": [ + "bin/archgate" + ], + "license": "FSL-1.1-ALv2", + "repository": { + "type": "git", + "url": "git+https://github.com/archgate/cli.git" + } +} diff --git a/scripts/archgate.rb b/scripts/archgate.rb deleted file mode 100644 index bb88e31f..00000000 --- a/scripts/archgate.rb +++ /dev/null @@ -1,35 +0,0 @@ -# Homebrew formula for Archgate CLI -# Hosted in archgate/homebrew-tap -# https://github.com/archgate/homebrew-tap -class Archgate < Formula - desc "AI governance for software development" - homepage "https://archgate.dev" - version "PLACEHOLDER_VERSION" - license "FSL-1.1-ALv2" - - on_macos do - if Hardware::CPU.arm? - url "https://github.com/archgate/cli/releases/download/vPLACEHOLDER_VERSION/archgate-darwin-arm64" - sha256 "PLACEHOLDER_SHA256_DARWIN_ARM64" - - def install - bin.install "archgate-darwin-arm64" => "archgate" - end - end - end - - on_linux do - if Hardware::CPU.intel? - url "https://github.com/archgate/cli/releases/download/vPLACEHOLDER_VERSION/archgate-linux-x64" - sha256 "PLACEHOLDER_SHA256_LINUX_X64" - - def install - bin.install "archgate-linux-x64" => "archgate" - end - end - end - - test do - assert_match version.to_s, shell_output("#{bin}/archgate --version") - end -end diff --git a/scripts/build.ts b/scripts/build.ts deleted file mode 100644 index f6c03e0e..00000000 --- a/scripts/build.ts +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env bun -/** - * Build script: compiles Archgate CLI to standalone binaries, generates SHA-256 checksums. - * - * Usage: - * bun run scripts/build.ts # Build all targets - * bun run scripts/build.ts --target darwin-arm64 - * bun run scripts/build.ts --target linux-x64 - */ -import { statSync } from "node:fs"; -import { join } from "node:path"; - -const ALL_TARGETS = [ - { flag: "bun-darwin-arm64", name: "archgate-darwin-arm64" }, - { flag: "bun-linux-x64", name: "archgate-linux-x64" }, -] as const; - -const outDir = "dist"; - -async function checksum(filePath: string): Promise { - const file = Bun.file(filePath); - const buf = await file.arrayBuffer(); - const hasher = new Bun.CryptoHasher("sha256"); - hasher.update(buf); - return hasher.digest("hex"); -} - -function formatBytes(bytes: number): string { - const mb = bytes / 1024 / 1024; - return `${mb.toFixed(1)} MB`; -} - -async function buildTarget(flag: string, name: string): Promise { - const outPath = join(outDir, name); - console.log(`Building ${name} (target: ${flag})...`); - - const result = - await $`bun build src/cli.ts --compile --bytecode --minify --target ${flag} --outfile ${outPath}` - .nothrow() - .quiet(); - - if (result.exitCode !== 0) { - console.error(`Build failed for ${name}:`); - console.error(result.stderr.toString()); - process.exit(2); - } - - const sha = await checksum(outPath); - const checksumPath = `${outPath}.sha256`; - await Bun.write(checksumPath, `${sha} ${name}\n`); - - const size = statSync(outPath).size; - console.log(` ${name}: ${formatBytes(size)}`); - console.log(` SHA-256: ${sha}`); -} - -async function main() { - const args = process.argv.slice(2); - const targetArg = args.indexOf("--target"); - const requestedTarget = targetArg === -1 ? null : args[targetArg + 1]; - - const targets = - requestedTarget === null - ? ALL_TARGETS - : ALL_TARGETS.filter((t) => t.name.endsWith(requestedTarget)); - - if (targets.length === 0) { - console.error( - `Unknown target: ${requestedTarget}. Available: darwin-arm64, linux-x64` - ); - process.exit(2); - } - - // Ensure output directory exists - await $`mkdir -p ${outDir}`.quiet(); - - console.log(`Building ${targets.length} target(s)...`); - for (const { flag, name } of targets) { - // oxlint-disable-next-line no-await-in-loop -- sequential builds are intentional for readable output - await buildTarget(flag, name); - } - console.log("Build complete."); -} - -import { $ } from "bun"; - -await main(); diff --git a/scripts/install.sh b/scripts/install.sh deleted file mode 100644 index ece6e404..00000000 --- a/scripts/install.sh +++ /dev/null @@ -1,114 +0,0 @@ -#!/bin/sh -# Archgate CLI install script -# Usage: curl -fsSL https://archgate.dev/install.sh | sh -# Usage: curl -fsSL https://archgate.dev/install.sh | sh -s -- --version v0.1.0 -set -e - -REPO="archgate/cli" -INSTALL_DIR="${HOME}/.archgate/bin" -BINARY_NAME="archgate" - -# --- Parse arguments --- -VERSION="" -while [ "$#" -gt 0 ]; do - case "$1" in - --version) - VERSION="$2" - shift 2 - ;; - *) - shift - ;; - esac -done - -# --- Detect OS and architecture --- -OS="$(uname -s)" -ARCH="$(uname -m)" - -case "$OS" in - Darwin) - case "$ARCH" in - arm64) ARTIFACT="archgate-darwin-arm64" ;; - *) - echo "Unsupported architecture: $ARCH on macOS. Only arm64 is supported." >&2 - exit 1 - ;; - esac - ;; - Linux) - case "$ARCH" in - x86_64) ARTIFACT="archgate-linux-x64" ;; - *) - echo "Unsupported architecture: $ARCH on Linux. Only x86_64 is supported." >&2 - exit 1 - ;; - esac - ;; - *) - echo "Unsupported OS: $OS. Archgate supports macOS and Linux only." >&2 - exit 1 - ;; -esac - -# --- Resolve version --- -if [ -z "$VERSION" ]; then - echo "Fetching latest release..." - VERSION="$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" \ - -H "Accept: application/vnd.github+json" \ - | grep '"tag_name"' \ - | head -1 \ - | sed 's/.*"tag_name": *"\([^"]*\)".*/\1/')" - - if [ -z "$VERSION" ]; then - echo "Failed to fetch latest version from GitHub." >&2 - exit 1 - fi -fi - -echo "Installing Archgate ${VERSION}..." - -# --- Download binary --- -DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${VERSION}/${ARTIFACT}" -INSTALL_PATH="${INSTALL_DIR}/${BINARY_NAME}" - -mkdir -p "$INSTALL_DIR" - -if command -v curl > /dev/null 2>&1; then - curl -fsSL "$DOWNLOAD_URL" -o "$INSTALL_PATH" -elif command -v wget > /dev/null 2>&1; then - wget -qO "$INSTALL_PATH" "$DOWNLOAD_URL" -else - echo "Error: curl or wget is required to install Archgate." >&2 - exit 1 -fi - -chmod +x "$INSTALL_PATH" - -echo "Archgate ${VERSION} installed to ${INSTALL_PATH}" - -# --- Add to PATH --- -add_to_path() { - PROFILE_FILE="$1" - EXPORT_LINE='export PATH="$HOME/.archgate/bin:$PATH"' - - if [ -f "$PROFILE_FILE" ] && grep -q '\.archgate/bin' "$PROFILE_FILE" 2>/dev/null; then - return 0 - fi - - if [ -f "$PROFILE_FILE" ]; then - printf '\n# Archgate CLI\n%s\n' "$EXPORT_LINE" >> "$PROFILE_FILE" - echo "Added ~/.archgate/bin to PATH in ${PROFILE_FILE}" - fi -} - -add_to_path "${HOME}/.bashrc" -add_to_path "${HOME}/.zshrc" -add_to_path "${HOME}/.profile" - -echo "" -echo "Installation complete." -echo "Restart your shell or run:" -echo ' export PATH="$HOME/.archgate/bin:$PATH"' -echo "" -echo "Then verify with: archgate --version" From d6ef572fc93daad25729e83f54f275489e0fa9f4 Mon Sep 17 00:00:00 2001 From: rhuanbarreto <283004+rhuanbarreto@users.noreply.github.com> Date: Mon, 23 Feb 2026 20:16:40 +0000 Subject: [PATCH 2/2] Apply automatic changes --- bun.lock | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bun.lock b/bun.lock index 474b235e..5f79ad47 100644 --- a/bun.lock +++ b/bun.lock @@ -22,6 +22,10 @@ "oxlint": "1.14.0", "prettier": "3.6.2", }, + "optionalDependencies": { + "archgate-darwin-arm64": ">=0.1.1", + "archgate-linux-x64": ">=0.1.1", + }, "peerDependencies": { "typescript": "^5", },