Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 32 additions & 54 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ on:
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
Expand Down Expand Up @@ -91,48 +87,38 @@ jobs:
retention-days: 7

publish-npm:
name: Publish to npm
name: Publish to npm (Trusted Publishing)
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() }}
# Publishes via npm Trusted Publishing (OIDC) — no NPM_TOKEN, no 2FA/OTP.
# Requires a GitHub-Actions trusted publisher configured on each package at
# npmjs.com (org/repo = ashlrai/ashlr-stack, workflow file = release.yml)
# and npm >= 11.5.1 (installed below). The workflow-level `id-token: write`
# permission supplies the OIDC token npm exchanges for short-lived publish
# rights. continue-on-error keeps the GitHub Release flowing even if a
# package's trusted publisher isn't configured yet.
if: ${{ !cancelled() && needs.build-binaries.result == 'success' }}
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
- uses: actions/checkout@v4
with:
fetch-depth: 0 # scripts/publish.sh uses git tag
fetch-depth: 0 # scripts/publish.sh inspects git tags

- if: steps.gate.outputs.enabled == 'true'
uses: oven-sh/setup-bun@v2
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest

- if: steps.gate.outputs.enabled == 'true'
uses: actions/setup-node@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
node-version: '22'
registry-url: 'https://registry.npmjs.org/'

- if: steps.gate.outputs.enabled == 'true'
name: Install deps
- name: Upgrade npm (Trusted Publishing needs npm >= 11.5.1)
run: npm install -g npm@latest

- name: Install deps
run: bun install --frozen-lockfile

- if: steps.gate.outputs.enabled == 'true'
name: Resolve version from tag
- name: Resolve version from tag
id: ver
run: |
if [ -n "${{ github.event.inputs.version }}" ]; then
Expand All @@ -142,41 +128,35 @@ jobs:
echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
fi

- if: steps.gate.outputs.enabled == 'true'
name: Inject OAuth client IDs
- 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
- name: Publish @ashlr/stack-core + ashlr-stack-mcp + @ashlr/stack
id: npm_publish
continue-on-error: true
# No NODE_AUTH_TOKEN — npm detects the GitHub OIDC environment and uses
# the per-package trusted publisher. Provenance is attached automatically.
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
run: bash scripts/publish.sh --version ${{ steps.ver.outputs.version }} --yes

- if: steps.gate.outputs.enabled == 'true' && steps.npm_publish.outcome == 'failure'
name: Warn — npm publish failed (likely EOTP)
- if: steps.npm_publish.outcome == 'failure'
name: Warn — npm publish failed
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.'
echo '::warning title=npm publish failed::Trusted Publishing failed. Confirm each package (@ashlr/stack, @ashlr/stack-core, ashlr-stack-mcp) has a GitHub-Actions trusted publisher at npmjs.com pointing at ashlrai/ashlr-stack with workflow file release.yml, and that npm >= 11.5.1 ran above.'

- if: always() && steps.gate.outputs.enabled == 'true'
- if: always()
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.
# publish-npm is continue-on-error, so even if Trusted Publishing isn't
# configured for a package yet, the binaries alone are enough to cut a
# GitHub Release and make install.sh work. Gated only on build success.
needs: [build-binaries, publish-npm]
if: ${{ !cancelled() && needs.build-binaries.result == 'success' }}
runs-on: ubuntu-latest
Expand Down Expand Up @@ -333,7 +313,5 @@ jobs:
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 commit -m "feat: stack v${VERSION}" -m "Assisted-By: ashlr-plugin <https://plugin.ashlr.ai>"
git push
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
- **Kebab-case flags now parse (`--dry-run`, `--keep-from`, `--all-orphans`, …)** — citty 0.1.6 silently dropped the documented kebab spelling of multi-word flags: a boolean arg with `default: false` shadowed the kebab-parsed value, so `--dry-run` was ignored while only `--dryRun` worked. For `stack add` this was high-severity — the documented "safe preview" form fell through to the **real** provisioning flow (live OAuth/network/vault writes). Raw argv is now normalized (`--dry-run` → `--dryRun`) before parsing, fixing every command at once while preserving camelCase, `--no-*` negation, and `--` passthrough. (`packages/cli/src/lib/normalize-args.ts`)
- **`stack add` no longer hangs on interactive credential entry** — the CLI started a @clack spinner, then a provider's PAT/key-paste path wrote its prompt to stderr and blocked on stdin while the spinner repainted over the (now invisible) prompt. With no cached credential — e.g. a fresh repo — `stack add` appeared to hang forever. The host now owns prompting: a new `ProviderContext.prompt` plus a `spinnerBridge` helper pause the spinner around a masked @clack prompt, and all nine interactive providers route credential entry through a shared `promptSecret`. GitHub's device-code message goes through `ctx.log` for the same reason. (`packages/core/src/providers/_helpers.ts`, `packages/cli/src/ui.ts`)

### CI / Release

- **Release workflow no longer fails at startup** — a multi-line `git commit -m` message in the Homebrew-tap job terminated the YAML `run:` block scalar early, leaking an `Assisted-By` stray top-level key that GitHub's schema validator rejected. Every push since the v0.2 polish produced a 0-second startup failure, and the v0.2.0 npm publish never landed (npm stayed at 0.1.1). Collapsed the message to two `-m` flags. (`.github/workflows/release.yml`)
- **npm publish migrated to Trusted Publishing (OIDC)** — replaces the long-lived `NPM_TOKEN` (which expired and hit EOTP under 2FA) with per-package GitHub-Actions trusted publishers. Nothing to rotate; provenance is attached automatically. Requires npm ≥ 11.5.1, installed in-workflow. (`.github/workflows/release.yml`)

### SEO + GEO content surface (major)

- **29 programmatic provider pages** — new dynamic route at `/providers/[slug]` driven by `packages/core/src/catalog.ts`. Adding a provider to catalog auto-mints a page at build time. Each page: hero with brand logo, auth-flow explainer, secret-slot breakdown, MCP wiring status, `stack add` snippet, `stack recommend` trigger, related providers, templates that include it, FAQ, outbound dashboard + docs links. JSON-LD: TechArticle + BreadcrumbList + FAQPage.
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ashlr/stack",
"version": "0.2.0",
"version": "0.2.1",
"description": "Ashlr Stack CLI — the control plane for your entire dev stack. One command to provision, wire, and operate every third-party service in your project. 29 providers, MCP-native.",
"license": "MIT",
"author": {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ashlr/stack-core",
"version": "0.2.0",
"version": "0.2.1",
"description": "Ashlr Stack core — provider adapters, .stack.toml config, Phantom integration. Shared logic behind the Stack CLI and MCP server.",
"license": "MIT",
"author": {
Expand Down
2 changes: 1 addition & 1 deletion packages/mcp/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ashlr-stack-mcp",
"version": "0.2.0",
"version": "0.2.1",
"description": "MCP server for Ashlr Stack. Exposes 19 tools and 3 resources so Claude Code / Cursor / Windsurf / Codex can provision every third-party service in a project with one call.",
"license": "MIT",
"author": {
Expand Down
Loading