diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 790ae7b..a270d63 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 @@ -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 @@ -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 @@ -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 " + git commit -m "feat: stack v${VERSION}" -m "Assisted-By: ashlr-plugin " git push diff --git a/CHANGELOG.md b/CHANGELOG.md index 5efebcb..ec4f9f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/packages/cli/package.json b/packages/cli/package.json index 197d8ca..277438a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -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": { diff --git a/packages/core/package.json b/packages/core/package.json index fcf993b..b045daa 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -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": { diff --git a/packages/mcp/package.json b/packages/mcp/package.json index 37b4e31..79d2a23 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -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": {