chore: add Ashlr verification manifest #46
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | ||
| on: | ||
| push: | ||
| tags: ['v*.*.*'] | ||
| workflow_dispatch: | ||
| inputs: | ||
| version: | ||
| description: 'Version to release (no leading v, e.g. 0.1.0)' | ||
| required: true | ||
| npm_otp: | ||
| description: 'npm OTP (6-digit, optional — only needed when 2FA is required)' | ||
| required: false | ||
| default: '' | ||
| permissions: | ||
| contents: write # create GitHub Releases + upload assets | ||
| id-token: write # npm provenance | ||
| concurrency: | ||
| group: release-${{ github.ref }} | ||
| cancel-in-progress: false | ||
| jobs: | ||
| build-binaries: | ||
| name: Build ${{ matrix.asset }} | ||
| runs-on: ${{ matrix.os }} | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| include: | ||
| - target: bun-linux-x64 | ||
| os: ubuntu-latest | ||
| asset: stack-linux-x64 | ||
| - target: bun-linux-arm64 | ||
| os: ubuntu-latest | ||
| asset: stack-linux-arm64 | ||
| # GitHub's macos-13 (Intel) runner image is being phased out and | ||
| # the queue regularly sits for 30+ minutes. Bun cross-compiles | ||
| # cleanly from arm64 → x64 via `--target=bun-darwin-x64`, so we | ||
| # run both darwin builds on macos-latest (M1/M2) and skip the | ||
| # Intel-runner dependency entirely. | ||
| - target: bun-darwin-x64 | ||
| os: macos-latest | ||
| asset: stack-darwin-x64 | ||
| - target: bun-darwin-arm64 | ||
| os: macos-latest | ||
| asset: stack-darwin-arm64 | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: oven-sh/setup-bun@v2 | ||
| with: | ||
| bun-version: latest | ||
| - name: Install deps | ||
| run: bun install --frozen-lockfile | ||
| - name: Compile standalone binary | ||
| run: | | ||
| mkdir -p dist | ||
| bun build packages/cli/src/index.ts \ | ||
| --compile \ | ||
| --target=${{ matrix.target }} \ | ||
| --outfile=dist/${{ matrix.asset }} | ||
| chmod +x dist/${{ matrix.asset }} | ||
| - name: Generate SHA-256 | ||
| run: | | ||
| cd dist | ||
| if command -v sha256sum >/dev/null 2>&1; then | ||
| sha256sum ${{ matrix.asset }} > ${{ matrix.asset }}.sha256 | ||
| else | ||
| shasum -a 256 ${{ matrix.asset }} > ${{ matrix.asset }}.sha256 | ||
| fi | ||
| cat ${{ matrix.asset }}.sha256 | ||
| - name: Smoke test (native arch only) | ||
| # Only run the binary when it matches the runner arch — darwin-x64 | ||
| # is cross-compiled on an arm64 runner, so we can't execute it here | ||
| # without Rosetta; the linux-arm64 case is similar. | ||
| if: matrix.target == 'bun-linux-x64' || matrix.target == 'bun-darwin-arm64' | ||
| run: | | ||
| ./dist/${{ matrix.asset }} --help | head -20 | ||
| ./dist/${{ matrix.asset }} providers | head -5 | ||
| - uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: ${{ matrix.asset }} | ||
| path: dist/${{ matrix.asset }}* | ||
| retention-days: 7 | ||
| publish-npm: | ||
| name: Publish to npm | ||
| needs: build-binaries | ||
| runs-on: ubuntu-latest | ||
| # Skip cleanly if NPM_TOKEN isn't configured yet — the release still | ||
| # produces binaries + a GitHub Release so install.sh works. Re-tag a | ||
| # patch release once the token is set and this job fires. | ||
| if: ${{ !cancelled() }} | ||
| steps: | ||
| - name: Gate on NPM_TOKEN | ||
| id: gate | ||
| run: | | ||
| if [ -z "${{ secrets.NPM_TOKEN }}" ]; then | ||
| echo "⚠ NPM_TOKEN not configured — skipping npm publish." | ||
| echo " Set it with: gh secret set NPM_TOKEN" | ||
| echo " Binaries + GitHub Release will still be produced." | ||
| echo "enabled=false" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "enabled=true" >> "$GITHUB_OUTPUT" | ||
| fi | ||
| - if: steps.gate.outputs.enabled == 'true' | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 # scripts/publish.sh uses git tag | ||
| - if: steps.gate.outputs.enabled == 'true' | ||
| uses: oven-sh/setup-bun@v2 | ||
| with: | ||
| bun-version: latest | ||
| - if: steps.gate.outputs.enabled == 'true' | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '20' | ||
| registry-url: 'https://registry.npmjs.org/' | ||
| - if: steps.gate.outputs.enabled == 'true' | ||
| name: Install deps | ||
| run: bun install --frozen-lockfile | ||
| - if: steps.gate.outputs.enabled == 'true' | ||
| name: Resolve version from tag | ||
| id: ver | ||
| run: | | ||
| if [ -n "${{ github.event.inputs.version }}" ]; then | ||
| echo "version=${{ github.event.inputs.version }}" >> "$GITHUB_OUTPUT" | ||
| else | ||
| # tag is like refs/tags/v0.1.0 | ||
| echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" | ||
| fi | ||
| - if: steps.gate.outputs.enabled == 'true' | ||
| name: Inject OAuth client IDs | ||
| env: | ||
| OAUTH_SUPABASE_CLIENT_ID: ${{ secrets.OAUTH_SUPABASE_CLIENT_ID }} | ||
| OAUTH_GITHUB_CLIENT_ID: ${{ secrets.OAUTH_GITHUB_CLIENT_ID }} | ||
| run: node scripts/inject-client-ids.mjs | ||
| - if: steps.gate.outputs.enabled == 'true' | ||
| name: Publish @ashlr/stack-core + @ashlr/stack-mcp + @ashlr/stack | ||
| id: npm_publish | ||
| continue-on-error: true | ||
| env: | ||
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | ||
| NPM_CONFIG_PROVENANCE: 'true' | ||
| NPM_OTP: ${{ github.event.inputs.npm_otp }} | ||
| run: | | ||
| OTP_FLAG='' | ||
| if [ -n "$NPM_OTP" ]; then | ||
| OTP_FLAG="--otp=$NPM_OTP" | ||
| fi | ||
| bash scripts/publish.sh --version ${{ steps.ver.outputs.version }} --yes $OTP_FLAG | ||
| - if: steps.gate.outputs.enabled == 'true' && steps.npm_publish.outcome == 'failure' | ||
| name: Warn — npm publish failed (likely EOTP) | ||
| run: | | ||
| echo '::warning title=npm publish failed::npm publish step failed — this is often an OTP/EOTP error. Rotate to an Automation token (no 2FA required) at https://docs.npmjs.com/creating-and-viewing-access-tokens or re-run this workflow with a fresh OTP via the npm_otp input.' | ||
| - if: always() && steps.gate.outputs.enabled == 'true' | ||
| name: Restore env.ts (never commit injected client IDs) | ||
| run: git checkout -- packages/core/src/env.ts || true | ||
| create-release: | ||
| name: Create GitHub Release | ||
| # publish-npm skips gracefully when NPM_TOKEN is absent; binaries alone | ||
| # are enough to cut a GitHub Release and make install.sh work. | ||
| needs: [build-binaries, publish-npm] | ||
| if: ${{ !cancelled() && needs.build-binaries.result == 'success' }} | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/download-artifact@v4 | ||
| with: | ||
| path: dist | ||
| merge-multiple: true | ||
| - name: Resolve version | ||
| id: ver | ||
| run: | | ||
| if [ -n "${{ github.event.inputs.version }}" ]; then | ||
| echo "version=${{ github.event.inputs.version }}" >> "$GITHUB_OUTPUT" | ||
| echo "tag=v${{ github.event.inputs.version }}" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" | ||
| echo "tag=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT" | ||
| fi | ||
| - name: Write release body | ||
| id: body | ||
| run: | | ||
| cat > release-body.md <<'EOF' | ||
| Ashlr Stack ${{ steps.ver.outputs.tag }}. | ||
| ## Install | ||
| ```sh | ||
| curl -fsSL https://stack.ashlr.ai/install.sh | bash | ||
| ``` | ||
| Or via npm: | ||
| ```sh | ||
| npm install -g @ashlr/stack@${{ steps.ver.outputs.version }} | ||
| ``` | ||
| ## Binaries | ||
| Prebuilt for macOS (arm64 + x64) and Linux (arm64 + x64). SHA-256 | ||
| checksums are published alongside each asset. | ||
| ## What's next | ||
| ```sh | ||
| stack init | ||
| stack add supabase | ||
| stack doctor | ||
| ``` | ||
| EOF | ||
| echo "path=release-body.md" >> "$GITHUB_OUTPUT" | ||
| - uses: softprops/action-gh-release@v2 | ||
| with: | ||
| tag_name: ${{ steps.ver.outputs.tag }} | ||
| name: ${{ steps.ver.outputs.tag }} | ||
| body_path: ${{ steps.body.outputs.path }} | ||
| files: | | ||
| dist/stack-linux-x64 | ||
| dist/stack-linux-x64.sha256 | ||
| dist/stack-linux-arm64 | ||
| dist/stack-linux-arm64.sha256 | ||
| dist/stack-darwin-x64 | ||
| dist/stack-darwin-x64.sha256 | ||
| dist/stack-darwin-arm64 | ||
| dist/stack-darwin-arm64.sha256 | ||
| generate_release_notes: true | ||
| fail_on_unmatched_files: true | ||
| draft: false | ||
| prerelease: ${{ contains(steps.ver.outputs.version, '-') }} | ||
| update-homebrew-tap: | ||
| name: Update Homebrew tap | ||
| needs: create-release | ||
| # Only run on v*.*.* tag pushes (not workflow_dispatch version bumps) | ||
| if: | | ||
| !cancelled() && | ||
| needs.create-release.result == 'success' && | ||
| startsWith(github.ref, 'refs/tags/v') | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Check for tap-write credential | ||
| id: cred_check | ||
| # Accept either a fine-grained PAT (preferred — write to homebrew-ashlr only) | ||
| # or a deploy key SSH private key. PAT first because the ashlrai org has | ||
| # repo-level deploy keys disabled by policy. | ||
| run: | | ||
| if [ -n "${{ secrets.HOMEBREW_TAP_PAT }}" ]; then | ||
| echo "method=pat" >> "$GITHUB_OUTPUT" | ||
| elif [ -n "${{ secrets.HOMEBREW_TAP_DEPLOY_KEY }}" ]; then | ||
| echo "method=ssh" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "::warning title=Homebrew tap not updated::Neither HOMEBREW_TAP_PAT nor HOMEBREW_TAP_DEPLOY_KEY is set — skipping formula update. Create a fine-grained PAT with 'Contents: write' on ashlrai/homebrew-ashlr and add it as HOMEBREW_TAP_PAT." | ||
| echo "method=skip" >> "$GITHUB_OUTPUT" | ||
| fi | ||
| - if: steps.cred_check.outputs.method != 'skip' | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| path: stack-repo | ||
| - if: steps.cred_check.outputs.method != 'skip' | ||
| name: Download SHA-256 files from release | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| run: | | ||
| TAG="${GITHUB_REF_NAME}" | ||
| mkdir -p sha256s | ||
| gh release download "${TAG}" -R ashlrai/ashlr-stack -p "*.sha256" -D sha256s/ | ||
| - if: steps.cred_check.outputs.method == 'pat' | ||
| name: Checkout homebrew tap (PAT auth) | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| repository: ashlrai/homebrew-ashlr | ||
| token: ${{ secrets.HOMEBREW_TAP_PAT }} | ||
| path: homebrew-tap | ||
| - if: steps.cred_check.outputs.method == 'ssh' | ||
| name: Checkout homebrew tap (SSH auth) | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| repository: ashlrai/homebrew-ashlr | ||
| ssh-key: ${{ secrets.HOMEBREW_TAP_DEPLOY_KEY }} | ||
| path: homebrew-tap | ||
| - if: steps.cred_check.outputs.method != 'skip' | ||
| name: Generate Formula/stack.rb from template | ||
| run: | | ||
| VERSION="${GITHUB_REF_NAME#v}" | ||
| SHA_DARWIN_ARM64=$(awk '{print $1}' sha256s/stack-darwin-arm64.sha256) | ||
| SHA_DARWIN_X64=$(awk '{print $1}' sha256s/stack-darwin-x64.sha256) | ||
| SHA_LINUX_ARM64=$(awk '{print $1}' sha256s/stack-linux-arm64.sha256) | ||
| SHA_LINUX_X64=$(awk '{print $1}' sha256s/stack-linux-x64.sha256) | ||
| sed \ | ||
| -e "s/\$VERSION\$/${VERSION}/g" \ | ||
| -e "s/\$SHA_DARWIN_ARM64\$/${SHA_DARWIN_ARM64}/g" \ | ||
| -e "s/\$SHA_DARWIN_X64\$/${SHA_DARWIN_X64}/g" \ | ||
| -e "s/\$SHA_LINUX_ARM64\$/${SHA_LINUX_ARM64}/g" \ | ||
| -e "s/\$SHA_LINUX_X64\$/${SHA_LINUX_X64}/g" \ | ||
| stack-repo/.github/homebrew-formula.template.rb \ | ||
| > homebrew-tap/Formula/stack.rb | ||
| - if: steps.cred_check.outputs.method != 'skip' | ||
| name: Commit and push updated formula | ||
| run: | | ||
| VERSION="${GITHUB_REF_NAME#v}" | ||
| cd homebrew-tap | ||
| git config user.name "github-actions[bot]" | ||
| git config user.email "github-actions[bot]@users.noreply.github.com" | ||
| git add Formula/stack.rb | ||
| git diff --cached --quiet && echo "No formula changes — already up to date." && exit 0 | ||
| git commit -m "feat: stack v${VERSION} | ||
| Assisted-By: ashlr-plugin <https://plugin.ashlr.ai>" | ||
| git push | ||