Skip to content

feat: multi-provider orchestration groups with dependency ordering #25

feat: multi-provider orchestration groups with dependency ordering

feat: multi-provider orchestration groups with dependency ordering #25

Workflow file for this run

name: Release

Check failure on line 1 in .github/workflows/release.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/release.yml

Invalid workflow file

(Line: 338, Col: 1): Unexpected value 'Assisted-By'
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