From 3cd8fd4f0bebfca8b4b96d04a89d1fe7abf57df7 Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Tue, 1 Sep 2026 08:48:02 +1000 Subject: [PATCH 1/4] docs: refresh the CLI reference for stash 1.1.1, and stop it going stale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The published CLI reached 1.1.1 on 2026-08-20. Production has been serving a reference generated from 1.0.0 ever since. It is in every build log: ⚠ Could not run stash@1.1.1; using cached fixture. ✓ Generated 14 CLI reference page(s) for stash v1.0.0 `npm view stash version` resolved 1.1.1 correctly; the next step, `npx --yes stash@1.1.1 manifest --json`, does not work in the Vercel build sandbox, and the generator fell back to the committed fixture and carried on. Eleven days of a version-old reference, and nothing failed, because a fallback that always works cannot fail. Two commands' worth of drift: 1.1.1 removed `db validate` and added `eql preflight` and `eql verify`, both of which carry substantial prose about managed-Postgres behaviour. The build no longer touches the network. `generate-docs:cli` reads the committed fixture and renders it — what deploys is exactly what is in the repo, and it is reviewable in the diff. Refreshing the fixture is now a separate, explicit step (`--refresh`, exposed as `generate-docs:cli:refresh`) that resolves the latest published CLI, runs it, and rewrites the fixture. It has no fallback: if it cannot run the CLI it fails, and it inherits stderr rather than discarding it, so the reason is visible. .github/workflows/cli-manifest.yml runs that refresh daily, and opens a PR when the published version has moved past the cached one — the same shape as the EQL pin workflow in #76. The refresh is not auto-merged: a release can remove commands as well as add them, and each removal is a section that disappears and an anchor that stops resolving, so the diff gets read. Also names the generic headings on the group pages, which #101 left alone because they come from a second render path. `eql.mdx` carried five identical `#### Examples` and eight identical `#### Flags` — eight duplicate anchors on one page, and eight chunks a retriever cannot tell apart. They now read `#### stash eql verify flags`, matching what #101 did for the single-command pages. Verified: `generate-docs:cli` with no network access reproduces the committed pages exactly; validate-links unchanged at its 9-failure baseline (all pre-existing, in generated integration api-reference dirs); types:check and biome clean. --- .github/workflows/cli-manifest.yml | 159 +++++++++++++++++++++++ content/docs/reference/cli/auth.mdx | 14 +- content/docs/reference/cli/db.mdx | 25 +--- content/docs/reference/cli/doctor.mdx | 6 +- content/docs/reference/cli/encrypt.mdx | 10 +- content/docs/reference/cli/env.mdx | 6 +- content/docs/reference/cli/eql.mdx | 152 ++++++++++++++++++++-- content/docs/reference/cli/impl.mdx | 8 +- content/docs/reference/cli/index.mdx | 16 ++- content/docs/reference/cli/init.mdx | 8 +- content/docs/reference/cli/manifest.mdx | 6 +- content/docs/reference/cli/plan.mdx | 8 +- content/docs/reference/cli/schema.mdx | 8 +- content/docs/reference/cli/status.mdx | 6 +- content/docs/reference/cli/telemetry.mdx | 6 +- content/docs/reference/cli/wizard.mdx | 6 +- package.json | 1 + scripts/fixtures/stash-manifest.json | 108 +++++++++++---- scripts/generate-cli-docs.ts | 120 +++++++++-------- 19 files changed, 509 insertions(+), 164 deletions(-) create mode 100644 .github/workflows/cli-manifest.yml diff --git a/.github/workflows/cli-manifest.yml b/.github/workflows/cli-manifest.yml new file mode 100644 index 0000000..d92701a --- /dev/null +++ b/.github/workflows/cli-manifest.yml @@ -0,0 +1,159 @@ +# Watches for new `stash` CLI releases and opens a PR refreshing the CLI +# reference. +# +# The reference is generated from the CLI's own `manifest --json`, cached in +# scripts/fixtures/stash-manifest.json and committed alongside the pages it +# produces. The build reads that fixture and never the network, so what deploys +# is exactly what is in the repo. +# +# That is the fix this workflow exists to complete. The generator used to +# resolve and invoke the published CLI on every build, falling back to the +# fixture if it could not. `npx stash@ manifest --json` does not work +# in the Vercel build sandbox, so from stash 1.1.1 (2026-08-20) every +# production build logged +# +# ⚠ Could not run stash@1.1.1; using cached fixture. +# ✓ Generated 14 CLI reference page(s) for stash v1.0.0 +# +# and shipped a version-old reference for eleven days. Nothing failed, because +# a fallback that always works cannot fail. Refreshing on a schedule moves that +# step somewhere a failure is a red workflow run rather than a quietly stale +# deploy. +# +# The refresh is not auto-merged. A CLI release can remove commands as well as +# add them — 1.1.1 dropped `db validate` and added `eql preflight` and +# `eql verify` — and hand-written pages link to command anchors, so the diff +# gets read. +name: CLI manifest + +on: + schedule: + # Daily. NOTE: GitHub only runs scheduled workflows from the DEFAULT + # branch, so this file must stay on the default branch to fire at all. + - cron: "30 6 * * *" + workflow_dispatch: + +env: + DOCS_BRANCH: main + +permissions: + contents: write + pull-requests: write + +concurrency: + group: cli-manifest + cancel-in-progress: false + +jobs: + refresh: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ env.DOCS_BRANCH }} + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - run: bun install --frozen-lockfile + + - name: Compare the published CLI against the cached manifest + id: check + run: | + set -euo pipefail + latest=$(npm view stash version) + cached=$(node -p "require('./scripts/fixtures/stash-manifest.json').version") + echo "latest=$latest" >> "$GITHUB_OUTPUT" + echo "cached=$cached" >> "$GITHUB_OUTPUT" + if [ "$latest" = "$cached" ]; then + echo "refresh=false" >> "$GITHUB_OUTPUT" + echo "CLI reference is current at $cached." + else + echo "refresh=true" >> "$GITHUB_OUTPUT" + echo "Published stash is $latest; the cached manifest is $cached." + fi + + # Fails the job if it fails. That is the point: this is the one place + # that runs the CLI, so a failure here is the signal that the refresh is + # broken, rather than a build silently serving last month's commands. + - name: Refresh the manifest and regenerate the pages + if: steps.check.outputs.refresh == 'true' + run: bun run generate-docs:cli:refresh + + # Named-anchor and cross-link breakage from removed commands shows up + # here. Reported, never fatal — a broken link is what the PR is for. + - name: Check links against the regenerated pages + if: steps.check.outputs.refresh == 'true' + id: links + run: | + set +e + out=$(bun run validate-links 2>&1) + code=$? + echo "$out" + { + echo "status=$([ $code -eq 0 ] && echo clean || echo failing)" + echo "log<> "$GITHUB_OUTPUT" + + - name: Open or update the refresh PR + if: steps.check.outputs.refresh == 'true' + env: + GH_TOKEN: ${{ github.token }} + FROM: ${{ steps.check.outputs.cached }} + TO: ${{ steps.check.outputs.latest }} + LINKS_STATUS: ${{ steps.links.outputs.status }} + LINKS_LOG: ${{ steps.links.outputs.log }} + run: | + set -euo pipefail + branch="chore/cli-manifest-${TO}" + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git checkout -b "$branch" + git add scripts/fixtures/stash-manifest.json content/docs/reference/cli + git commit -m "docs: refresh the CLI reference for stash ${TO}" + git push --force-with-lease -u origin "$branch" + + if [ "$LINKS_STATUS" = "clean" ]; then + verdict="**Links check passes.** Every internal link and anchor still resolves, so this is a read of the command diff rather than a repair job." + else + verdict="**Links check fails.** Something links to a command or anchor that \`${TO}\` no longer has; the failures are below." + fi + + body=$(cat <Links check output + + \`\`\` + ${LINKS_LOG} + \`\`\` + + + + --- + + Opened automatically by \`.github/workflows/cli-manifest.yml\`. + EOF + ) + + open=$(gh pr list --head "$branch" --state open --json number --jq 'length') + if [ "$open" != "0" ]; then + gh pr edit "$branch" --body "$body" + echo "Updated the existing PR for $branch" + else + gh pr create \ + --base "${DOCS_BRANCH}" \ + --head "$branch" \ + --title "docs: refresh the CLI reference for stash ${TO}" \ + --body "$body" + fi diff --git a/content/docs/reference/cli/auth.mdx b/content/docs/reference/cli/auth.mdx index c0963c0..2f3f80d 100644 --- a/content/docs/reference/cli/auth.mdx +++ b/content/docs/reference/cli/auth.mdx @@ -4,13 +4,13 @@ description: "Reference for the `stash auth` commands." type: reference components: [cli] verifiedAgainst: - cli: "1.0.0" + cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.0.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} -Generated from **`stash` v1.0.0** via `npx stash@1.0.0 manifest --json`. Run `npx stash@1.0.0 --help` to see the live command surface. +Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. The `stash auth` command group. @@ -28,7 +28,7 @@ Runs the OAuth 2.0 device authorization flow: npx stash auth login [flags] ``` -#### Flags +#### stash auth login flags | Flag | Description | | --- | --- | @@ -40,7 +40,7 @@ npx stash auth login [flags] | `--prisma` | Track Prisma as the referrer. | -#### Examples +#### stash auth login examples ```bash npx stash auth login @@ -57,14 +57,14 @@ List the regions you can authenticate against npx stash auth regions [flags] ``` -#### Flags +#### stash auth regions flags | Flag | Description | | --- | --- | | `--json` | Emit machine-readable [\{ slug, label \}] instead of a text list. | -#### Examples +#### stash auth regions examples ```bash npx stash auth regions diff --git a/content/docs/reference/cli/db.mdx b/content/docs/reference/cli/db.mdx index 0d0e6ca..e88d4a9 100644 --- a/content/docs/reference/cli/db.mdx +++ b/content/docs/reference/cli/db.mdx @@ -4,34 +4,17 @@ description: "Reference for the `stash db` commands." type: reference components: [cli, eql] verifiedAgainst: - cli: "1.0.0" + cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.0.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} -Generated from **`stash` v1.0.0** via `npx stash@1.0.0 manifest --json`. Run `npx stash@1.0.0 --help` to see the live command surface. +Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. The `stash db` command group. -### `db validate` - -Validate encryption schema - -```bash -npx stash db validate [flags] -``` - -#### Flags - -| Flag | Description | -| --- | --- | -| `--supabase` | Use Supabase-compatible mode. | -| `--exclude-operator-family` | Skip operator family creation. | -| `--database-url ` | Database URL for this run only — never written to disk. Highest precedence in the resolution order: --database-url flag → DATABASE_URL env → supabase status → interactive prompt. A stash.config.ts is not a separate tier (its default databaseUrl re-runs this same chain); a hand-set literal databaseUrl in the config bypasses the resolver and wins over all of these. (env: `DATABASE_URL`) | - - ### `db migrate` Run pending encrypt config migrations (not yet implemented) @@ -48,7 +31,7 @@ Test database connectivity npx stash db test-connection [flags] ``` -#### Flags +#### stash db test-connection flags | Flag | Description | | --- | --- | diff --git a/content/docs/reference/cli/doctor.mdx b/content/docs/reference/cli/doctor.mdx index 176e425..0c8144a 100644 --- a/content/docs/reference/cli/doctor.mdx +++ b/content/docs/reference/cli/doctor.mdx @@ -4,13 +4,13 @@ description: "Diagnose install problems (native binaries, runtime)" type: reference components: [cli] verifiedAgainst: - cli: "1.0.0" + cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.0.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} -Generated from **`stash` v1.0.0** via `npx stash@1.0.0 manifest --json`. Run `npx stash@1.0.0 --help` to see the live command surface. +Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. Diagnose install problems (native binaries, runtime) diff --git a/content/docs/reference/cli/encrypt.mdx b/content/docs/reference/cli/encrypt.mdx index 126a6c8..9d2f327 100644 --- a/content/docs/reference/cli/encrypt.mdx +++ b/content/docs/reference/cli/encrypt.mdx @@ -4,13 +4,13 @@ description: "Reference for the `stash encrypt` commands." type: reference components: [cli, eql] verifiedAgainst: - cli: "1.0.0" + cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.0.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} -Generated from **`stash` v1.0.0** via `npx stash@1.0.0 manifest --json`. Run `npx stash@1.0.0 --help` to see the live command surface. +Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. The `stash encrypt` command group. @@ -39,7 +39,7 @@ Resumably encrypt plaintext into the encrypted column npx stash encrypt backfill [flags] ``` -#### Flags +#### stash encrypt backfill flags | Flag | Description | | --- | --- | @@ -61,7 +61,7 @@ Generate a migration to drop the plaintext column npx stash encrypt drop [flags] ``` -#### Flags +#### stash encrypt drop flags | Flag | Description | | --- | --- | diff --git a/content/docs/reference/cli/env.mdx b/content/docs/reference/cli/env.mdx index e489e33..41271d9 100644 --- a/content/docs/reference/cli/env.mdx +++ b/content/docs/reference/cli/env.mdx @@ -4,13 +4,13 @@ description: "Mint deployment credentials and print them as env vars" type: reference components: [cli] verifiedAgainst: - cli: "1.0.0" + cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.0.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} -Generated from **`stash` v1.0.0** via `npx stash@1.0.0 manifest --json`. Run `npx stash@1.0.0 --help` to see the live command surface. +Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. Mints a fresh ZeroKMS client and a CipherStash access key from your diff --git a/content/docs/reference/cli/eql.mdx b/content/docs/reference/cli/eql.mdx index 813e1bb..4c5ed81 100644 --- a/content/docs/reference/cli/eql.mdx +++ b/content/docs/reference/cli/eql.mdx @@ -4,17 +4,49 @@ description: "Reference for the `stash eql` commands." type: reference components: [cli, eql] verifiedAgainst: - cli: "1.0.0" + cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.0.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} -Generated from **`stash` v1.0.0** via `npx stash@1.0.0 manifest --json`. Run `npx stash@1.0.0 --help` to see the live command surface. +Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. The `stash eql` command group. +### `eql preflight` + +Read-only: probes the connected role — superuser, membership of +`postgres`, CREATE on the database and on `public`, pgcrypto, and +whether the EQL v3 schemas already exist — and names the statement +each gap blocks. Exits 1 when a gap would abort `eql install`. + +Membership of `postgres` is reported but never blocks: on managed +platforms whose role is not a member (e.g. Lovable), `eql install` +skips the optional owner-scoped ALTER DEFAULT PRIVILEGES statements +— the install is complete without them, since stash re-grants every +object on each install/upgrade. + +```bash +npx stash eql preflight [flags] +``` + +#### stash eql preflight flags + +| Flag | Description | +| --- | --- | +| `--json` | Emit the machine-readable preflight result instead of the table. | +| `--database-url ` | Database URL for this run only — never written to disk. Highest precedence in the resolution order: --database-url flag → DATABASE_URL env → supabase status → interactive prompt. A stash.config.ts is not a separate tier (its default databaseUrl re-runs this same chain); a hand-set literal databaseUrl in the config bypasses the resolver and wins over all of these. (env: `DATABASE_URL`) | + + +#### stash eql preflight examples + +```bash +npx stash eql preflight +npx stash eql preflight --json +``` + ### `eql install` Scaffold stash.config.ts (if missing) and install EQL extensions @@ -23,7 +55,7 @@ Scaffold stash.config.ts (if missing) and install EQL extensions npx stash eql install [flags] ``` -#### Flags +#### stash eql install flags | Flag | Description | | --- | --- | @@ -33,31 +65,85 @@ npx stash eql install [flags] | `--database-url ` | Database URL for this run only — never written to disk. Highest precedence in the resolution order: --database-url flag → DATABASE_URL env → supabase status → interactive prompt. A stash.config.ts is not a separate tier (its default databaseUrl re-runs this same chain); a hand-set literal databaseUrl in the config bypasses the resolver and wins over all of these. (env: `DATABASE_URL`) | +### `eql verify` + +Compare what the database actually has against everything the pinned +EQL v3 bundle installs — every domain, function overload, operator, +cast, and the ORE operator class — via read-only catalog queries. A +partial install (domains present, some comparison functions or +operators absent) reports success at install time and fails at query +time on a specific predicate; this is the check that catches it. + +Expected absences read as such: on managed Postgres the bundle +legitimately skips the ORE operator class (creating it requires +superuser) and poisons the `_ord_ore` domains to fail loudly — that +is a supported configuration, reported as info. + +Exit 0 means exactly one thing: the surface was checked and found +complete. Damage, EQL not installed, and a version mismatch with +the pinned bundle all exit 1 — on a mismatch the object-level diff +is skipped (the pinned bundle is the wrong manifest to compare +against) and the command suggests `eql upgrade` (or a one-shot +`eql install --force --database-url ...` where no stash.config.ts +exists — `eql upgrade` requires one). + +Runs automatically at the end of `stash eql install`, on the +fresh-install path and the already-installed early exit alike. +There, only damage fails the install — a version mismatch warns +and continues, keeping a no-op re-run over an older EQL exit 0 +for idempotent provisioning scripts. + +```bash +npx stash eql verify [flags] +``` + +#### stash eql verify flags + +| Flag | Description | +| --- | --- | +| `--json` | Emit the machine-readable verification report instead of the table. | +| `--database-url ` | One-shot, like `eql install`'s: bypasses config loading entirely, so the database you name is the database that gets judged. Also settable via DATABASE_URL. (env: `DATABASE_URL`) | + + +#### stash eql verify examples + +```bash +npx stash eql verify +npx stash eql verify --json +``` + ### `eql migration` -Generate an EQL v3 install migration for your ORM (Drizzle; Prisma Next installs EQL through its own migrations) +Migration-first is the preferred way to install EQL: it lands in your +migration history and ships to every environment through the same +migrate step as the rest of your schema. On Supabase it is the only +durable way — `supabase db reset` replays the migrations directory, so +a direct `eql install` is wiped by the next reset. ```bash npx stash eql migration [flags] ``` -#### Flags +#### stash eql migration flags | Flag | Description | | --- | --- | | `--drizzle` | Emit a Drizzle custom migration containing the EQL v3 install SQL. | | `--prisma` | Not needed: Prisma Next installs EQL through its own migration framework — run `prisma-next migrate` instead. | -| `--supabase` | Append the Supabase role grants (eql_v3 + eql_v3_internal for anon/authenticated/service_role). | +| `--supabase` | On its own, write the install into supabase/migrations/ so it survives `supabase db reset`. With --drizzle, instead append the Supabase role grants (eql_v3 + eql_v3_internal for anon/authenticated/service_role) to the Drizzle migration. | | `--name ` | Name for the generated migration (Drizzle). Letters, numbers, dashes, underscores only. Defaults to `install-eql`. | -| `--out ` | Directory drizzle-kit writes the migration into (passed to `drizzle-kit generate --out`). Defaults to `drizzle`; set it to match your drizzle.config.ts. | +| `--out ` | Where the migration is written. Drizzle: your drizzle.config.ts `out` decides that, and stash follows the path drizzle-kit reports — this is only the fallback directory to look in (defaults to `drizzle`) if it reports none. Supabase: leave it alone. The Supabase CLI replays `supabase/migrations` and has no setting to move it, so pointing elsewhere means `supabase db reset` / `db push` never apply the install; the command warns when you do. | +| `--force` | Write a Supabase install migration even though one already exists. Not needed for --drizzle (drizzle-kit numbers each generated migration). | | `--dry-run` | Show what would happen without making changes. | -#### Examples +#### stash eql migration examples ```bash npx stash eql migration --drizzle npx stash eql migration --drizzle --supabase +npx stash eql migration --supabase +npx stash eql migration --supabase --force ``` ### `eql repair` @@ -83,7 +169,7 @@ relation, finds nothing, and reports applied state as unverified. npx stash eql repair [flags] ``` -#### Flags +#### stash eql repair flags | Flag | Description | | --- | --- | @@ -94,7 +180,7 @@ npx stash eql repair [flags] | `--database-url ` | Database URL for this run only — never written to disk. Highest precedence in the resolution order: --database-url flag → DATABASE_URL env → supabase status → interactive prompt. A stash.config.ts is not a separate tier (its default databaseUrl re-runs this same chain); a hand-set literal databaseUrl in the config bypasses the resolver and wins over all of these. (env: `DATABASE_URL`) | -#### Examples +#### stash eql repair examples ```bash npx stash eql repair --drizzle @@ -110,7 +196,7 @@ Upgrade EQL extensions to the latest version npx stash eql upgrade [flags] ``` -#### Flags +#### stash eql upgrade flags | Flag | Description | | --- | --- | @@ -127,8 +213,48 @@ Show EQL installation status npx stash eql status [flags] ``` -#### Flags +#### stash eql status flags + +| Flag | Description | +| --- | --- | +| `--database-url ` | Database URL for this run only — never written to disk. Highest precedence in the resolution order: --database-url flag → DATABASE_URL env → supabase status → interactive prompt. A stash.config.ts is not a separate tier (its default databaseUrl re-runs this same chain); a hand-set literal databaseUrl in the config bypasses the resolver and wins over all of these. (env: `DATABASE_URL`) | + + +### `eql validate` + +Read the tables passed to `Encryption({ schemas })` and check each encrypted +column against the EQL v3 domain vocabulary — then, if a database is +reachable, against what that database actually has. + +Schema checks (no database needed): an `_ord_ore` domain, whose ORE operator +class only a superuser can create; storage-only columns, reported so an +unsearchable column is a decision rather than a surprise; and hand-authored +configs that ask for free-text match on a non-text domain, encrypted-JSONB +search without `types.Json`, or a searchable boolean. + +Database checks (skipped with a notice when no database is reachable): +declared columns missing from the database, a column whose domain has drifted +from the schema, an `_ord_ore` column on a database whose EQL install could +not create the ORE operator class, and queryable columns with no functional +index over their term extractor. + +Exits 1 on errors only — warnings and info do not fail the command. + +```bash +npx stash eql validate [flags] +``` + +#### stash eql validate flags | Flag | Description | | --- | --- | +| `--supabase` | Use Supabase-compatible mode. | | `--database-url ` | Database URL for this run only — never written to disk. Highest precedence in the resolution order: --database-url flag → DATABASE_URL env → supabase status → interactive prompt. A stash.config.ts is not a separate tier (its default databaseUrl re-runs this same chain); a hand-set literal databaseUrl in the config bypasses the resolver and wins over all of these. (env: `DATABASE_URL`) | + + +#### stash eql validate examples + +```bash +npx stash eql validate +npx stash eql validate --database-url postgres://… +``` diff --git a/content/docs/reference/cli/impl.mdx b/content/docs/reference/cli/impl.mdx index 6ca64d3..f0905fa 100644 --- a/content/docs/reference/cli/impl.mdx +++ b/content/docs/reference/cli/impl.mdx @@ -4,13 +4,13 @@ description: "Execute the plan with a local agent" type: reference components: [cli] verifiedAgainst: - cli: "1.0.0" + cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.0.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} -Generated from **`stash` v1.0.0** via `npx stash@1.0.0 manifest --json`. Run `npx stash@1.0.0 --help` to see the live command surface. +Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. Execute the plan with a local agent @@ -24,7 +24,7 @@ npx stash impl [flags] | Flag | Description | | --- | --- | | `--continue-without-plan` | Skip planning and go straight to implementation (interactively confirms before proceeding). | -| `--target ` | Skip the agent-target picker and hand off directly to one of claude-code \| codex \| agents-md \| wizard. Safe in non-TTY contexts. | +| `--target ` | Skip the agent-target picker and hand off directly to one of claude-code \| codex \| agents-md \| lovable \| wizard. Safe in non-TTY contexts. | ## stash impl examples diff --git a/content/docs/reference/cli/index.mdx b/content/docs/reference/cli/index.mdx index eadd67e..b8d3b56 100644 --- a/content/docs/reference/cli/index.mdx +++ b/content/docs/reference/cli/index.mdx @@ -1,20 +1,20 @@ --- title: CLI navTitle: Overview -description: "Command reference for the stash CLI, generated from v1.0.0." +description: "Command reference for the stash CLI, generated from v1.1.1." type: reference components: [cli] verifiedAgainst: - cli: "1.0.0" + cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.0.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} -Generated from **`stash` v1.0.0** via `npx stash@1.0.0 manifest --json`. Run `npx stash@1.0.0 --help` to see the live command surface. +Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. -The `stash` CLI. Install with `npx stash@1.0.0`. Every command accepts `--help` and `--version`. +The `stash` CLI. Install with `npx stash@1.1.1`. Every command accepts `--help` and `--version`. ### Setup & workflow @@ -40,17 +40,19 @@ The `stash` CLI. Install with `npx stash@1.0.0`. Every command accepts `--help` | Command | Description | | --- | --- | +| [`eql preflight`](/reference/cli/eql#eql-preflight) | Report whether this database role can install EQL, before trying | | [`eql install`](/reference/cli/eql#eql-install) | Scaffold stash.config.ts (if missing) and install EQL extensions | -| [`eql migration`](/reference/cli/eql#eql-migration) | Generate an EQL v3 install migration for your ORM (Drizzle; Prisma Next installs EQL through its own migrations) | +| [`eql verify`](/reference/cli/eql#eql-verify) | Check the installed EQL surface is complete, not just present | +| [`eql migration`](/reference/cli/eql#eql-migration) | Generate an EQL v3 install migration (Drizzle, or supabase/migrations/; Prisma Next installs EQL through its own migrations) | | [`eql repair`](/reference/cli/eql#eql-repair) | Repair migrations drizzle-kit generated with an un-runnable ALTER COLUMN to an encrypted type | | [`eql upgrade`](/reference/cli/eql#eql-upgrade) | Upgrade EQL extensions to the latest version | | [`eql status`](/reference/cli/eql#eql-status) | Show EQL installation status | +| [`eql validate`](/reference/cli/eql#eql-validate) | Validate your encryption schema against EQL v3 | ### Database | Command | Description | | --- | --- | -| [`db validate`](/reference/cli/db#db-validate) | Validate encryption schema | | [`db migrate`](/reference/cli/db#db-migrate) | Run pending encrypt config migrations (not yet implemented) | | [`db test-connection`](/reference/cli/db#db-test-connection) | Test database connectivity | diff --git a/content/docs/reference/cli/init.mdx b/content/docs/reference/cli/init.mdx index 472ac10..338c50b 100644 --- a/content/docs/reference/cli/init.mdx +++ b/content/docs/reference/cli/init.mdx @@ -4,13 +4,13 @@ description: "Initialize CipherStash for your project" type: reference components: [cli] verifiedAgainst: - cli: "1.0.0" + cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.0.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} -Generated from **`stash` v1.0.0** via `npx stash@1.0.0 manifest --json`. Run `npx stash@1.0.0 --help` to see the live command surface. +Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. Set up CipherStash end-to-end: authenticate, introspect your database, @@ -30,6 +30,7 @@ npx stash init [flags] | `--drizzle` | Use Drizzle-specific setup flow. | | `--prisma` | Use Prisma Next-specific setup flow (EQL bundle installed via prisma-next migrate). | | `--region ` | Region to authenticate against (e.g. us-east-1). Skips the interactive region picker. Required for non-interactive init when not already logged in. (env: `STASH_REGION`) | +| `--target ` | Which agent to install the bundled skills for: claude-code (.claude/skills) or codex (.codex/skills). Skips agent detection. Unlike `plan --target` and `impl --target`, this selects the skills destination only — init performs no handoff. agents-md, lovable and wizard install no skill directories (those handoffs inline the skills instead), so passing one here installs nothing. | ## stash init examples @@ -39,4 +40,5 @@ npx stash init npx stash init --supabase npx stash init --prisma npx stash init --region us-east-1 +npx stash init --target claude-code ``` diff --git a/content/docs/reference/cli/manifest.mdx b/content/docs/reference/cli/manifest.mdx index 767b098..6182ebf 100644 --- a/content/docs/reference/cli/manifest.mdx +++ b/content/docs/reference/cli/manifest.mdx @@ -4,13 +4,13 @@ description: "Print the structured, versioned command surface" type: reference components: [cli] verifiedAgainst: - cli: "1.0.0" + cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.0.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} -Generated from **`stash` v1.0.0** via `npx stash@1.0.0 manifest --json`. Run `npx stash@1.0.0 --help` to see the live command surface. +Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. Emit the CLI command surface as data. `--json` produces the machine- diff --git a/content/docs/reference/cli/plan.mdx b/content/docs/reference/cli/plan.mdx index e3dc7a3..18e2df0 100644 --- a/content/docs/reference/cli/plan.mdx +++ b/content/docs/reference/cli/plan.mdx @@ -4,13 +4,13 @@ description: "Draft a reviewable encryption plan at .cipherstash/plan.md" type: reference components: [cli] verifiedAgainst: - cli: "1.0.0" + cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.0.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} -Generated from **`stash` v1.0.0** via `npx stash@1.0.0 manifest --json`. Run `npx stash@1.0.0 --help` to see the live command surface. +Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. Draft a reviewable encryption plan at .cipherstash/plan.md @@ -25,7 +25,7 @@ npx stash plan [flags] | --- | --- | | `--complete-rollout` | Plan the entire encryption lifecycle (schema-add through drop) in one document. Skips the production-deploy gate; only safe when this database is not backing a deployed application. Needs confirmation — an interactive prompt, or --yes non-interactively (else it exits non-zero without drafting). | | `--yes` | Confirm --complete-rollout's gate-skip without a prompt (for automation / CI). No effect without --complete-rollout. | -| `--target ` | Skip the agent-target picker and hand off directly to one of claude-code \| codex \| agents-md \| wizard. Safe in non-TTY contexts. | +| `--target ` | Skip the agent-target picker and hand off directly to one of claude-code \| codex \| agents-md \| lovable \| wizard. Safe in non-TTY contexts. | ## stash plan examples diff --git a/content/docs/reference/cli/schema.mdx b/content/docs/reference/cli/schema.mdx index ada1471..2536213 100644 --- a/content/docs/reference/cli/schema.mdx +++ b/content/docs/reference/cli/schema.mdx @@ -4,13 +4,13 @@ description: "Reference for the `stash schema` commands." type: reference components: [cli, eql] verifiedAgainst: - cli: "1.0.0" + cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.0.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} -Generated from **`stash` v1.0.0** via `npx stash@1.0.0 manifest --json`. Run `npx stash@1.0.0 --help` to see the live command surface. +Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. The `stash schema` command group. @@ -23,7 +23,7 @@ Build an encryption schema from your database npx stash schema build [flags] ``` -#### Flags +#### stash schema build flags | Flag | Description | | --- | --- | diff --git a/content/docs/reference/cli/status.mdx b/content/docs/reference/cli/status.mdx index 9fcd239..d68ee5e 100644 --- a/content/docs/reference/cli/status.mdx +++ b/content/docs/reference/cli/status.mdx @@ -4,13 +4,13 @@ description: "Displays implementation status" type: reference components: [cli] verifiedAgainst: - cli: "1.0.0" + cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.0.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} -Generated from **`stash` v1.0.0** via `npx stash@1.0.0 manifest --json`. Run `npx stash@1.0.0 --help` to see the live command surface. +Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. Displays implementation status diff --git a/content/docs/reference/cli/telemetry.mdx b/content/docs/reference/cli/telemetry.mdx index afebed6..ff39174 100644 --- a/content/docs/reference/cli/telemetry.mdx +++ b/content/docs/reference/cli/telemetry.mdx @@ -4,13 +4,13 @@ description: "Manage anonymous usage analytics" type: reference components: [cli] verifiedAgainst: - cli: "1.0.0" + cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.0.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} -Generated from **`stash` v1.0.0** via `npx stash@1.0.0 manifest --json`. Run `npx stash@1.0.0 --help` to see the live command surface. +Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. Manage the anonymous, opt-out usage analytics the CLI collects to diff --git a/content/docs/reference/cli/wizard.mdx b/content/docs/reference/cli/wizard.mdx index f3e6956..e858a6f 100644 --- a/content/docs/reference/cli/wizard.mdx +++ b/content/docs/reference/cli/wizard.mdx @@ -4,13 +4,13 @@ description: "AI-guided encryption setup (reads your codebase)" type: reference components: [cli] verifiedAgainst: - cli: "1.0.0" + cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.0.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} -Generated from **`stash` v1.0.0** via `npx stash@1.0.0 manifest --json`. Run `npx stash@1.0.0 --help` to see the live command surface. +Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. AI-guided encryption setup (reads your codebase) diff --git a/package.json b/package.json index f851fd1..28258d6 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "generate-docs:eql": "tsx scripts/generate-eql-docs.ts", "generate-docs:eql-api": "tsx scripts/generate-eql-api-docs.ts", "generate-docs:cli": "tsx scripts/generate-cli-docs.ts", + "generate-docs:cli:refresh": "tsx scripts/generate-cli-docs.ts --refresh", "check-eql-pin": "tsx scripts/check-eql-pin.ts", "validate-links": "tsx scripts/validate-links.ts", "validate-redirects": "tsx scripts/validate-v2-redirects.ts", diff --git a/scripts/fixtures/stash-manifest.json b/scripts/fixtures/stash-manifest.json index 9c54546..5dd077c 100644 --- a/scripts/fixtures/stash-manifest.json +++ b/scripts/fixtures/stash-manifest.json @@ -1,6 +1,6 @@ { "name": "stash", - "version": "1.0.0", + "version": "1.1.1", "groups": [ { "title": "Setup & workflow", @@ -13,7 +13,8 @@ "init", "init --supabase", "init --prisma", - "init --region us-east-1" + "init --region us-east-1", + "init --target claude-code" ], "flags": [ { @@ -33,6 +34,11 @@ "value": "", "description": "Region to authenticate against (e.g. us-east-1). Skips the interactive region picker. Required for non-interactive init when not already logged in.", "env": "STASH_REGION" + }, + { + "name": "--target", + "value": "", + "description": "Which agent to install the bundled skills for: claude-code (.claude/skills) or codex (.codex/skills). Skips agent detection. Unlike `plan --target` and `impl --target`, this selects the skills destination only — init performs no handoff. agents-md, lovable and wizard install no skill directories (those handoffs inline the skills instead), so passing one here installs nothing." } ] }, @@ -56,7 +62,7 @@ { "name": "--target", "value": "", - "description": "Skip the agent-target picker and hand off directly to one of claude-code | codex | agents-md | wizard. Safe in non-TTY contexts." + "description": "Skip the agent-target picker and hand off directly to one of claude-code | codex | agents-md | lovable | wizard. Safe in non-TTY contexts." } ] }, @@ -76,7 +82,7 @@ { "name": "--target", "value": "", - "description": "Skip the agent-target picker and hand off directly to one of claude-code | codex | agents-md | wizard. Safe in non-TTY contexts." + "description": "Skip the agent-target picker and hand off directly to one of claude-code | codex | agents-md | lovable | wizard. Safe in non-TTY contexts." } ] }, @@ -110,7 +116,10 @@ "name": "manifest", "summary": "Print the structured, versioned command surface", "long": "Emit the CLI command surface as data. `--json` produces the machine-\nreadable manifest the docs generator and agents consume; without it a\ngrouped command list is printed. The manifest is stamped with the CLI\nversion, so a page generated from it always names the version it describes.", - "examples": ["manifest --json", "manifest"], + "examples": [ + "manifest --json", + "manifest" + ], "flags": [ { "name": "--json", @@ -175,7 +184,10 @@ { "name": "auth regions", "summary": "List the regions you can authenticate against", - "examples": ["auth regions", "auth regions --json"], + "examples": [ + "auth regions", + "auth regions --json" + ], "flags": [ { "name": "--json", @@ -188,6 +200,27 @@ { "title": "EQL", "commands": [ + { + "name": "eql preflight", + "summary": "Report whether this database role can install EQL, before trying", + "long": "Read-only: probes the connected role — superuser, membership of\n`postgres`, CREATE on the database and on `public`, pgcrypto, and\nwhether the EQL v3 schemas already exist — and names the statement\neach gap blocks. Exits 1 when a gap would abort `eql install`.\n\nMembership of `postgres` is reported but never blocks: on managed\nplatforms whose role is not a member (e.g. Lovable), `eql install`\nskips the optional owner-scoped ALTER DEFAULT PRIVILEGES statements\n— the install is complete without them, since stash re-grants every\nobject on each install/upgrade.", + "examples": [ + "eql preflight", + "eql preflight --json" + ], + "flags": [ + { + "name": "--json", + "description": "Emit the machine-readable preflight result instead of the table." + }, + { + "name": "--database-url", + "value": "", + "description": "Database URL for this run only — never written to disk. Highest precedence in the resolution order: --database-url flag → DATABASE_URL env → supabase status → interactive prompt. A stash.config.ts is not a separate tier (its default databaseUrl re-runs this same chain); a hand-set literal databaseUrl in the config bypasses the resolver and wins over all of these.", + "env": "DATABASE_URL" + } + ] + }, { "name": "eql install", "summary": "Scaffold stash.config.ts (if missing) and install EQL extensions", @@ -212,12 +245,36 @@ } ] }, + { + "name": "eql verify", + "summary": "Check the installed EQL surface is complete, not just present", + "long": "Compare what the database actually has against everything the pinned\nEQL v3 bundle installs — every domain, function overload, operator,\ncast, and the ORE operator class — via read-only catalog queries. A\npartial install (domains present, some comparison functions or\noperators absent) reports success at install time and fails at query\ntime on a specific predicate; this is the check that catches it.\n\nExpected absences read as such: on managed Postgres the bundle\nlegitimately skips the ORE operator class (creating it requires\nsuperuser) and poisons the `_ord_ore` domains to fail loudly — that\nis a supported configuration, reported as info.\n\nExit 0 means exactly one thing: the surface was checked and found\ncomplete. Damage, EQL not installed, and a version mismatch with\nthe pinned bundle all exit 1 — on a mismatch the object-level diff\nis skipped (the pinned bundle is the wrong manifest to compare\nagainst) and the command suggests `eql upgrade` (or a one-shot\n`eql install --force --database-url ...` where no stash.config.ts\nexists — `eql upgrade` requires one).\n\nRuns automatically at the end of `stash eql install`, on the\nfresh-install path and the already-installed early exit alike.\nThere, only damage fails the install — a version mismatch warns\nand continues, keeping a no-op re-run over an older EQL exit 0\nfor idempotent provisioning scripts.", + "examples": [ + "eql verify", + "eql verify --json" + ], + "flags": [ + { + "name": "--json", + "description": "Emit the machine-readable verification report instead of the table." + }, + { + "name": "--database-url", + "value": "", + "description": "One-shot, like `eql install`'s: bypasses config loading entirely, so the database you name is the database that gets judged. Also settable via DATABASE_URL.", + "env": "DATABASE_URL" + } + ] + }, { "name": "eql migration", - "summary": "Generate an EQL v3 install migration for your ORM (Drizzle; Prisma Next installs EQL through its own migrations)", + "summary": "Generate an EQL v3 install migration (Drizzle, or supabase/migrations/; Prisma Next installs EQL through its own migrations)", + "long": "Migration-first is the preferred way to install EQL: it lands in your\nmigration history and ships to every environment through the same\nmigrate step as the rest of your schema. On Supabase it is the only\ndurable way — `supabase db reset` replays the migrations directory, so\na direct `eql install` is wiped by the next reset.", "examples": [ "eql migration --drizzle", - "eql migration --drizzle --supabase" + "eql migration --drizzle --supabase", + "eql migration --supabase", + "eql migration --supabase --force" ], "flags": [ { @@ -230,7 +287,7 @@ }, { "name": "--supabase", - "description": "Append the Supabase role grants (eql_v3 + eql_v3_internal for anon/authenticated/service_role)." + "description": "On its own, write the install into supabase/migrations/ so it survives `supabase db reset`. With --drizzle, instead append the Supabase role grants (eql_v3 + eql_v3_internal for anon/authenticated/service_role) to the Drizzle migration." }, { "name": "--name", @@ -240,7 +297,11 @@ { "name": "--out", "value": "", - "description": "Directory drizzle-kit writes the migration into (passed to `drizzle-kit generate --out`). Defaults to `drizzle`; set it to match your drizzle.config.ts." + "description": "Where the migration is written. Drizzle: your drizzle.config.ts `out` decides that, and stash follows the path drizzle-kit reports — this is only the fallback directory to look in (defaults to `drizzle`) if it reports none. Supabase: leave it alone. The Supabase CLI replays `supabase/migrations` and has no setting to move it, so pointing elsewhere means `supabase db reset` / `db push` never apply the install; the command warns when you do." + }, + { + "name": "--force", + "description": "Write a Supabase install migration even though one already exists. Not needed for --drizzle (drizzle-kit numbers each generated migration)." }, { "name": "--dry-run", @@ -315,24 +376,20 @@ "env": "DATABASE_URL" } ] - } - ] - }, - { - "title": "Database", - "commands": [ + }, { - "name": "db validate", - "summary": "Validate encryption schema", + "name": "eql validate", + "summary": "Validate your encryption schema against EQL v3", + "long": "Read the tables passed to `Encryption({ schemas })` and check each encrypted\ncolumn against the EQL v3 domain vocabulary — then, if a database is\nreachable, against what that database actually has.\n\nSchema checks (no database needed): an `_ord_ore` domain, whose ORE operator\nclass only a superuser can create; storage-only columns, reported so an\nunsearchable column is a decision rather than a surprise; and hand-authored\nconfigs that ask for free-text match on a non-text domain, encrypted-JSONB\nsearch without `types.Json`, or a searchable boolean.\n\nDatabase checks (skipped with a notice when no database is reachable):\ndeclared columns missing from the database, a column whose domain has drifted\nfrom the schema, an `_ord_ore` column on a database whose EQL install could\nnot create the ORE operator class, and queryable columns with no functional\nindex over their term extractor.\n\nExits 1 on errors only — warnings and info do not fail the command.", + "examples": [ + "eql validate", + "eql validate --database-url postgres://…" + ], "flags": [ { "name": "--supabase", "description": "Use Supabase-compatible mode." }, - { - "name": "--exclude-operator-family", - "description": "Skip operator family creation." - }, { "name": "--database-url", "value": "", @@ -340,7 +397,12 @@ "env": "DATABASE_URL" } ] - }, + } + ] + }, + { + "title": "Database", + "commands": [ { "name": "db migrate", "summary": "Run pending encrypt config migrations (not yet implemented)" diff --git a/scripts/generate-cli-docs.ts b/scripts/generate-cli-docs.ts index 84be754..5534450 100644 --- a/scripts/generate-cli-docs.ts +++ b/scripts/generate-cli-docs.ts @@ -14,12 +14,27 @@ * projection of the real command set rather than a scrape of `--help`. * * ── Versioning ──────────────────────────────────────────────────────────── - * Always generated from the LATEST published `stash` on npm (resolved via - * `npm view stash version`), so a new release plus a run of this script — it - * runs in `prebuild` — refreshes the docs automatically. Every page carries - * `verifiedAgainst.cli` and a visible banner, so readers and agents always - * know which version the docs describe. Offline, it falls back to the cached - * `scripts/fixtures/stash-manifest.json`. + * The manifest is read from the committed `scripts/fixtures/stash-manifest.json`, + * NOT from the network. Every page carries `verifiedAgainst.cli` and a visible + * banner, so readers and agents always know which version the docs describe. + * + * `--refresh` re-resolves the latest published `stash` (`npm view stash + * version`), runs its `manifest --json`, and rewrites the fixture. That is a + * deliberate, separate step, run by .github/workflows/cli-manifest.yml on a + * schedule and by hand via `bun run generate-docs:cli:refresh`. + * + * This used to resolve and invoke the CLI on every build, falling back to the + * fixture when that failed. The fallback was silent, and it fired: from stash + * 1.1.1 (2026-08-20) every production build logged + * + * ⚠ Could not run stash@1.1.1; using cached fixture. + * ✓ Generated 14 CLI reference page(s) for stash v1.0.0 + * + * and shipped a version-old reference for eleven days without failing anything. + * `npx stash@ manifest --json` does not work in the Vercel build + * sandbox, and a build is the wrong place to find that out. Refreshing on a + * schedule instead means a failure surfaces in a workflow run rather than + * degrading a deploy, and every build renders exactly what is in the repo. */ import { execSync } from "node:child_process"; import fs from "node:fs"; @@ -27,6 +42,8 @@ import os from "node:os"; import path from "node:path"; const CLI_NAME = "stash"; +// Refresh the fixture from npm instead of reading it. CI and humans only. +const REFRESH = process.argv.includes("--refresh"); let CLI_VERSION = ""; // resolved to the latest published npm version at run time const RUNNER = "npx"; // normalized invocation shown in docs const FIXTURE = path.join( @@ -99,56 +116,41 @@ const componentsFor = (base: string): string[] => ["eql", "db", "schema", "encrypt"].includes(base) ? ["cli", "eql"] : ["cli"]; // ── Source ────────────────────────────────────────────────────────────────── -// Resolve the latest published version so the docs track releases automatically. +// Resolve the latest published version. Only reached under --refresh. function latestVersion(): string { - try { - return execSync(`npm view ${CLI_NAME} version`, { - encoding: "utf8", - }).trim(); - } catch { - const cached = fs.existsSync(FIXTURE) - ? (JSON.parse(fs.readFileSync(FIXTURE, "utf8")) as CliManifest).version - : undefined; - if (cached) { - console.warn(`⚠ npm unreachable; using cached stash v${cached}.`); - return cached; - } + return execSync(`npm view ${CLI_NAME} version`, { encoding: "utf8" }).trim(); +} + +function readFixture(): CliManifest { + if (!fs.existsSync(FIXTURE)) { throw new Error( - "Cannot resolve latest stash version (offline, no fixture).", + `No cached manifest at ${path.relative(process.cwd(), FIXTURE)}. Run \`bun run generate-docs:cli:refresh\` to create it.`, ); } + return JSON.parse(fs.readFileSync(FIXTURE, "utf8")) as CliManifest; } -// Run the resolved CLI and read its `manifest --json`, caching to a fixture for -// offline builds. dotenvx (the CLI's launcher) may print tips before the JSON, -// so slice from the first `{` to the last `}` defensively. -function loadRawManifest(version: string): CliManifest { - try { - const out = execSync(`npx --yes ${CLI_NAME}@${version} manifest --json`, { - encoding: "utf8", - cwd: os.tmpdir(), - stdio: ["ignore", "pipe", "ignore"], - }); - const start = out.indexOf("{"); - const end = out.lastIndexOf("}"); - if (start === -1 || end < start) { - throw new Error( - `\`${CLI_NAME}@${version} manifest --json\` did not emit a JSON object (got: ${out.trim().slice(0, 120)}…)`, - ); - } - const manifest = JSON.parse(out.slice(start, end + 1)) as CliManifest; - fs.mkdirSync(path.dirname(FIXTURE), { recursive: true }); - fs.writeFileSync(FIXTURE, `${JSON.stringify(manifest, null, 2)}\n`); - return manifest; - } catch { - if (fs.existsSync(FIXTURE)) { - console.warn(`⚠ Could not run stash@${version}; using cached fixture.`); - return JSON.parse(fs.readFileSync(FIXTURE, "utf8")) as CliManifest; - } +// Run the published CLI and read its `manifest --json`, rewriting the fixture. +// dotenvx (the CLI's launcher) may print tips before the JSON, so slice from +// the first `{` to the last `}` defensively. stderr is inherited rather than +// discarded: when this fails, the reason is the only useful output. +function refreshFixture(version: string): CliManifest { + const out = execSync(`npx --yes ${CLI_NAME}@${version} manifest --json`, { + encoding: "utf8", + cwd: os.tmpdir(), + stdio: ["ignore", "pipe", "inherit"], + }); + const start = out.indexOf("{"); + const end = out.lastIndexOf("}"); + if (start === -1 || end < start) { throw new Error( - `Could not run stash@${version} manifest --json and no cached fixture exists.`, + `\`${CLI_NAME}@${version} manifest --json\` did not emit a JSON object (got: ${out.trim().slice(0, 120)}…)`, ); } + const manifest = JSON.parse(out.slice(start, end + 1)) as CliManifest; + fs.mkdirSync(path.dirname(FIXTURE), { recursive: true }); + fs.writeFileSync(FIXTURE, `${JSON.stringify(manifest, null, 2)}\n`); + return manifest; } // Fold the manifest's richer flag metadata (default + env) into the description @@ -238,11 +240,21 @@ function commandSection(cmd: Command, level: "##" | "###"): string { synopsis, "```", ]; + // Named after the command, for the same reason the single-command page names + // its examples section: a chunk lifted out of the page has to carry its own + // subject. On a group page it also stops the headings colliding — eql.mdx + // carried five identical "Examples" and eight identical "Flags", which is + // five and eight duplicate anchors as well as eight indistinguishable chunks. if (cmd.flags.length) - parts.push(flagsTable(cmd.flags).replace("### Flags", `${level}# Flags`)); + parts.push( + flagsTable(cmd.flags).replace( + "### Flags", + `${level}# ${CLI_NAME} ${cmd.path} flags`, + ), + ); if (cmd.examples.length) { parts.push( - `\n${level}# Examples\n`, + `\n${level}# ${CLI_NAME} ${cmd.path} examples\n`, "```bash", cmd.examples.join("\n"), "```", @@ -374,15 +386,13 @@ function renderMeta(manifest: Manifest, groups: Map): string { // ── Main ───────────────────────────────────────────────────────────────────── function loadManifest(): Manifest { - return toManifest(loadRawManifest(CLI_VERSION)); + // latestVersion() picks which published CLI to invoke; the manifest it + // returns is authoritative for what to stamp, so the version is read back + // off the manifest rather than assumed. + return toManifest(REFRESH ? refreshFixture(latestVersion()) : readFixture()); } function main() { - // latestVersion() picks which published CLI to invoke; the manifest we get - // back is authoritative for what to stamp. Reconcile CLI_VERSION to it so - // pages never claim a version different from the data they were built from - // (e.g. when the live run fails and we fall back to an older cached fixture). - CLI_VERSION = latestVersion(); const manifest = loadManifest(); CLI_VERSION = manifest.version; From b936107e55064cbc4b651ad3d984736b69a7dd24 Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Tue, 1 Sep 2026 09:25:01 +1000 Subject: [PATCH 2/4] docs: correct the account of why the CLI reference went stale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review question: what actually goes wrong with `npx stash@1.1.1 manifest --json` on Vercel? Checking older build logs answers a different and more useful question first. The npx step has never once succeeded on Vercel: 2026-08-16 ⚠ Could not run stash@1.0.0; using cached fixture. 2026-08-18 ⚠ Could not run stash@1.1.0; using cached fixture. 2026-08-31 ⚠ Could not run stash@1.1.1; using cached fixture. The first of those is a build where the fixture already matched the latest release, so the output was correct and nothing looked wrong. The docs were right by coincidence — the committed fixture happened to be current — and 1.1.x did not break anything, it made a pre-existing failure visible. So the previous framing here ("from stash 1.1.1 every production build logged…") understated it. Corrected in both the generator header and the workflow. The cause is still unknown, and honestly labelled as such rather than asserted. stderr was discarded on every one of those runs (`stdio: [..., "ignore"]`). What is known: it fails in a consistent ~8 seconds, and `npm view` in the same script succeeds, so the registry is reachable. The workflow is now also the diagnostic. It is the one step that runs the CLI, it inherits stderr, and it fails loudly — so its first run either succeeds on Ubuntu, which localises the fault to the Vercel builder, or prints the error eight months of build logs never captured. --- .github/workflows/cli-manifest.yml | 19 ++++++++++--------- scripts/generate-cli-docs.ts | 26 +++++++++++++++++--------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/.github/workflows/cli-manifest.yml b/.github/workflows/cli-manifest.yml index d92701a..07de679 100644 --- a/.github/workflows/cli-manifest.yml +++ b/.github/workflows/cli-manifest.yml @@ -8,17 +8,18 @@ # # That is the fix this workflow exists to complete. The generator used to # resolve and invoke the published CLI on every build, falling back to the -# fixture if it could not. `npx stash@ manifest --json` does not work -# in the Vercel build sandbox, so from stash 1.1.1 (2026-08-20) every -# production build logged +# fixture if it could not. On Vercel it has never once succeeded — every +# production build log going back as far as they are retained carries the +# fallback, including builds where the fixture already matched the latest +# release and nothing looked wrong. # -# ⚠ Could not run stash@1.1.1; using cached fixture. -# ✓ Generated 14 CLI reference page(s) for stash v1.0.0 +# Nothing failed, because a fallback that always works cannot fail. The first +# visible symptom was eleven days of a version-old reference after 1.1.1 +# shipped. Why the `npx` invocation fails on Vercel is still unknown: stderr +# was discarded on every one of those runs. # -# and shipped a version-old reference for eleven days. Nothing failed, because -# a fallback that always works cannot fail. Refreshing on a schedule moves that -# step somewhere a failure is a red workflow run rather than a quietly stale -# deploy. +# This is also the place that answers that question. It is the one step that +# runs the CLI, it inherits stderr, and it fails loudly. # # The refresh is not auto-merged. A CLI release can remove commands as well as # add them — 1.1.1 dropped `db validate` and added `eql preflight` and diff --git a/scripts/generate-cli-docs.ts b/scripts/generate-cli-docs.ts index 5534450..a8e729b 100644 --- a/scripts/generate-cli-docs.ts +++ b/scripts/generate-cli-docs.ts @@ -24,17 +24,25 @@ * schedule and by hand via `bun run generate-docs:cli:refresh`. * * This used to resolve and invoke the CLI on every build, falling back to the - * fixture when that failed. The fallback was silent, and it fired: from stash - * 1.1.1 (2026-08-20) every production build logged + * fixture when that failed. It has never once succeeded on Vercel. Every + * production build log carries the fallback, going back as far as the logs are + * retained: * - * ⚠ Could not run stash@1.1.1; using cached fixture. - * ✓ Generated 14 CLI reference page(s) for stash v1.0.0 + * 2026-08-16 ⚠ Could not run stash@1.0.0; using cached fixture. + * 2026-08-18 ⚠ Could not run stash@1.1.0; using cached fixture. + * 2026-08-31 ⚠ Could not run stash@1.1.1; using cached fixture. * - * and shipped a version-old reference for eleven days without failing anything. - * `npx stash@ manifest --json` does not work in the Vercel build - * sandbox, and a build is the wrong place to find that out. Refreshing on a - * schedule instead means a failure surfaces in a workflow run rather than - * degrading a deploy, and every build renders exactly what is in the repo. + * The docs looked right for as long as the committed fixture happened to be + * the latest published version. When 1.1.x shipped, a failure that was already + * there simply became visible — as eleven days of a version-old reference. + * + * WHY the `npx` invocation fails there is still unknown: it was run with + * `stdio: [..., "ignore"]`, so stderr was discarded on every one of those + * runs. It fails in a consistent ~8 seconds, and `npm view` in the same script + * succeeds, so the registry is reachable. The build is the wrong place to be + * finding this out either way: refreshing on a schedule means a failure + * surfaces as a red workflow run with its stderr intact, rather than as a + * quietly degraded deploy, and every build renders exactly what is in the repo. */ import { execSync } from "node:child_process"; import fs from "node:fs"; From 08a5a0477970b6ec758a0132d18e3f84fd542e16 Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Tue, 1 Sep 2026 11:35:32 +1000 Subject: [PATCH 3/4] docs: pin the CLI version and refresh from an installed binary Review question: could stash be a dependency of the docs site itself? Tried it, and it cannot be. `stash` depends on zod 3 and fumadocs on zod 4; adding it hoists zod 3 to the root, the frontmatter schema in source.config.ts stops inferring, and the docs stop type-checking: src/lib/source.ts(62,43): error TS2345: Argument of type '{}' is not assignable to parameter of type 'string'. Which is the blast radius that argument had in the abstract, arriving on day one instead of on some future CLI release. A docs build should not be breakable by the CLI's dependency tree. Everything else about the idea holds, so this takes it without the dependency: - The version is PINNED in this script, exactly as EQL_RELEASE_TAG pins EQL in generate-eql-docs.ts. What produced the fixture is recorded in the repo and moves by reviewable commit, not by whatever npm serves that morning. - Rendering asserts the fixture's version equals the pin, and fails otherwise. Bumping one without regenerating the other is the exact drift that shipped a version-old reference, and it is now caught with no network access at all. - `--refresh` installs the pinned CLI into a temp directory and runs it from there. No `npx`, which is the thing that never once worked on Vercel, so the undiagnosed failure stops being load-bearing. The workflow bumps the pin, regenerates, and opens a PR with both. It does the two together deliberately: the gate means a PR that bumped only the pin would be red on arrival. Verified: both paths render byte-identical pages; the gate fires on a simulated bump (`Cached manifest is stash v1.1.1 but the pin is v9.9.9`); the workflow's pin-extraction and pin-rewrite steps both run correctly against the real file; types:check and biome clean, with no new dependencies. --- .github/workflows/cli-manifest.yml | 60 ++++++++++----- scripts/generate-cli-docs.ts | 115 +++++++++++++++++++---------- 2 files changed, 119 insertions(+), 56 deletions(-) diff --git a/.github/workflows/cli-manifest.yml b/.github/workflows/cli-manifest.yml index 07de679..09879e0 100644 --- a/.github/workflows/cli-manifest.yml +++ b/.github/workflows/cli-manifest.yml @@ -3,8 +3,10 @@ # # The reference is generated from the CLI's own `manifest --json`, cached in # scripts/fixtures/stash-manifest.json and committed alongside the pages it -# produces. The build reads that fixture and never the network, so what deploys -# is exactly what is in the repo. +# produces. The version is pinned in that script, and rendering asserts the +# fixture matches the pin, so a bumped pin with a stale fixture fails the build +# rather than shipping. The build reads the fixture and never the network: what +# deploys is exactly what is in the repo. # # That is the fix this workflow exists to complete. The generator used to # resolve and invoke the published CLI on every build, falling back to the @@ -18,8 +20,14 @@ # shipped. Why the `npx` invocation fails on Vercel is still unknown: stderr # was discarded on every one of those runs. # -# This is also the place that answers that question. It is the one step that -# runs the CLI, it inherits stderr, and it fails loudly. +# Nothing runs `npx` any more. The refresh installs the pinned CLI into a temp +# directory and runs it from there, so the question stops mattering — and it +# inherits stderr, so if the equivalent ever fails it says why. +# +# A temp directory rather than a devDependency of this app, which was tried: +# `stash` needs zod 3, fumadocs needs zod 4, and adding it hoists zod 3 to the +# root, at which point the frontmatter schema stops inferring and the docs stop +# type-checking. A docs build should not be breakable by the CLI's dep tree. # # The refresh is not auto-merged. A CLI release can remove commands as well as # add them — 1.1.1 dropped `db validate` and added `eql preflight` and @@ -59,28 +67,44 @@ jobs: - run: bun install --frozen-lockfile - - name: Compare the published CLI against the cached manifest + - name: Compare the published CLI against the pinned one id: check run: | set -euo pipefail latest=$(npm view stash version) - cached=$(node -p "require('./scripts/fixtures/stash-manifest.json').version") + pinned=$(node -p "/STASH_VERSION \?\? \"([^\"]+)\"/.exec(require('fs').readFileSync('scripts/generate-cli-docs.ts','utf8'))[1]") echo "latest=$latest" >> "$GITHUB_OUTPUT" - echo "cached=$cached" >> "$GITHUB_OUTPUT" - if [ "$latest" = "$cached" ]; then + echo "pinned=$pinned" >> "$GITHUB_OUTPUT" + if [ "$latest" = "$pinned" ]; then echo "refresh=false" >> "$GITHUB_OUTPUT" - echo "CLI reference is current at $cached." + echo "CLI reference is current at $pinned." else echo "refresh=true" >> "$GITHUB_OUTPUT" - echo "Published stash is $latest; the cached manifest is $cached." + echo "Published stash is $latest; the pin is $pinned." fi - # Fails the job if it fails. That is the point: this is the one place - # that runs the CLI, so a failure here is the signal that the refresh is - # broken, rather than a build silently serving last month's commands. - - name: Refresh the manifest and regenerate the pages + # Bumping the pin and regenerating are one action, not two: the generator + # refuses to render a fixture whose version disagrees with the pin, so a + # PR that did only the first would be red on arrival. + - name: Bump the pin and regenerate if: steps.check.outputs.refresh == 'true' - run: bun run generate-docs:cli:refresh + env: + FROM: ${{ steps.check.outputs.pinned }} + TO: ${{ steps.check.outputs.latest }} + run: | + set -euo pipefail + node -e ' + const fs = require("fs"); + const file = "scripts/generate-cli-docs.ts"; + const before = fs.readFileSync(file, "utf8"); + const after = before.replace( + `STASH_VERSION ?? "${process.env.FROM}"`, + `STASH_VERSION ?? "${process.env.TO}"`, + ); + if (after === before) throw new Error("Pin not found in " + file); + fs.writeFileSync(file, after); + ' + bun run generate-docs:cli:refresh # Named-anchor and cross-link breakage from removed commands shows up # here. Reported, never fatal — a broken link is what the PR is for. @@ -103,7 +127,7 @@ jobs: if: steps.check.outputs.refresh == 'true' env: GH_TOKEN: ${{ github.token }} - FROM: ${{ steps.check.outputs.cached }} + FROM: ${{ steps.check.outputs.pinned }} TO: ${{ steps.check.outputs.latest }} LINKS_STATUS: ${{ steps.links.outputs.status }} LINKS_LOG: ${{ steps.links.outputs.log }} @@ -114,7 +138,7 @@ jobs: git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git checkout -b "$branch" - git add scripts/fixtures/stash-manifest.json content/docs/reference/cli + git add scripts/generate-cli-docs.ts scripts/fixtures/stash-manifest.json content/docs/reference/cli git commit -m "docs: refresh the CLI reference for stash ${TO}" git push --force-with-lease -u origin "$branch" @@ -127,7 +151,7 @@ jobs: body=$(cat < ["eql", "db", "schema", "encrypt"].includes(base) ? ["cli", "eql"] : ["cli"]; // ── Source ────────────────────────────────────────────────────────────────── -// Resolve the latest published version. Only reached under --refresh. -function latestVersion(): string { - return execSync(`npm view ${CLI_NAME} version`, { encoding: "utf8" }).trim(); -} - function readFixture(): CliManifest { if (!fs.existsSync(FIXTURE)) { throw new Error( `No cached manifest at ${path.relative(process.cwd(), FIXTURE)}. Run \`bun run generate-docs:cli:refresh\` to create it.`, ); } - return JSON.parse(fs.readFileSync(FIXTURE, "utf8")) as CliManifest; -} + const manifest = JSON.parse(fs.readFileSync(FIXTURE, "utf8")) as CliManifest; -// Run the published CLI and read its `manifest --json`, rewriting the fixture. -// dotenvx (the CLI's launcher) may print tips before the JSON, so slice from -// the first `{` to the last `}` defensively. stderr is inherited rather than -// discarded: when this fails, the reason is the only useful output. -function refreshFixture(version: string): CliManifest { - const out = execSync(`npx --yes ${CLI_NAME}@${version} manifest --json`, { - encoding: "utf8", - cwd: os.tmpdir(), - stdio: ["ignore", "pipe", "inherit"], - }); - const start = out.indexOf("{"); - const end = out.lastIndexOf("}"); - if (start === -1 || end < start) { + // The gate. Bumping the pin without regenerating would ship a reference + // describing a version nobody is running. Costs no network. + if (manifest.version !== CLI_VERSION_PIN) { throw new Error( - `\`${CLI_NAME}@${version} manifest --json\` did not emit a JSON object (got: ${out.trim().slice(0, 120)}…)`, + `Cached manifest is ${CLI_NAME} v${manifest.version} but the pin is v${CLI_VERSION_PIN}. Run \`bun run generate-docs:cli:refresh\` and commit the result.`, ); } - const manifest = JSON.parse(out.slice(start, end + 1)) as CliManifest; - fs.mkdirSync(path.dirname(FIXTURE), { recursive: true }); - fs.writeFileSync(FIXTURE, `${JSON.stringify(manifest, null, 2)}\n`); return manifest; } +// Install the pinned CLI into a throwaway directory and read its +// `manifest --json` from there, rewriting the fixture. +// +// Installed rather than run through `npx --yes stash@`, which is what +// this used to do and what never once worked on Vercel. dotenvx (the CLI's +// launcher) may print tips before the JSON, so slice from the first `{` to the +// last `}` defensively. stderr is inherited rather than discarded: when this +// fails, the reason is the only useful output. +function refreshFixture(): CliManifest { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "stash-manifest-")); + try { + fs.writeFileSync( + path.join(dir, "package.json"), + `${JSON.stringify({ name: "stash-manifest-probe", private: true })}\n`, + ); + execSync(`bun add ${CLI_NAME}@${CLI_VERSION_PIN}`, { + cwd: dir, + stdio: ["ignore", "ignore", "inherit"], + }); + const out = execSync( + `${path.join(dir, "node_modules", ".bin", CLI_NAME)} manifest --json`, + { encoding: "utf8", cwd: dir, stdio: ["ignore", "pipe", "inherit"] }, + ); + const start = out.indexOf("{"); + const end = out.lastIndexOf("}"); + if (start === -1 || end < start) { + throw new Error( + `\`${CLI_NAME} manifest --json\` did not emit a JSON object (got: ${out.trim().slice(0, 120)}…)`, + ); + } + const manifest = JSON.parse(out.slice(start, end + 1)) as CliManifest; + if (manifest.version !== CLI_VERSION_PIN) { + throw new Error( + `Installed ${CLI_NAME}@${CLI_VERSION_PIN} reported v${manifest.version} in its manifest.`, + ); + } + fs.mkdirSync(path.dirname(FIXTURE), { recursive: true }); + fs.writeFileSync(FIXTURE, `${JSON.stringify(manifest, null, 2)}\n`); + return manifest; + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } +} + // Fold the manifest's richer flag metadata (default + env) into the description // column so the page format (Flag | Description) stays a single table. function mapFlag(f: CliFlag): Flag { @@ -394,10 +434,9 @@ function renderMeta(manifest: Manifest, groups: Map): string { // ── Main ───────────────────────────────────────────────────────────────────── function loadManifest(): Manifest { - // latestVersion() picks which published CLI to invoke; the manifest it - // returns is authoritative for what to stamp, so the version is read back - // off the manifest rather than assumed. - return toManifest(REFRESH ? refreshFixture(latestVersion()) : readFixture()); + // The manifest is authoritative for what to stamp, so the version is read + // back off it rather than assumed. + return toManifest(REFRESH ? refreshFixture() : readFixture()); } function main() { From 52f0e70f20b6639fa6fd883b1826805feca1b7ae Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Tue, 1 Sep 2026 12:49:39 +1000 Subject: [PATCH 4/4] docs: address Copilot review on the CLI reference pin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three comments, all correct. The inline comment on CLI_VERSION still described it as "resolved to the latest published npm version at run time", which stopped being true in this PR. It now says what it is: read back off the manifest in main() rather than assumed from the pin, because the manifest is what the pages are built from and readFixture() already guarantees the two agree. RUNNER gets the same treatment — it is how a *reader* invokes the CLI, not how this script obtains it, and the two were easy to conflate. The generated marker and the on-page callout still told readers the pages came from `npx stash@ manifest --json` and that re-running `generate-docs:cli` would refresh from the latest published CLI. Neither is true now: rendering is offline from the committed fixture, and refreshing is a separate script behind a pin. Both strings corrected and the 14 pages regenerated. The callout keeps `npx stash@1.1.1 --help`, which is still exactly how a reader checks the live surface. The workflow used `bun-version: latest` while docs.yml pins 1.3.13, with a comment saying why. Pinned to match. A workflow that commits regenerated content is the last place to let a toolchain float. types:check clean; validate-links unchanged at its 9-failure baseline. --- .github/workflows/cli-manifest.yml | 4 +++- content/docs/reference/cli/auth.mdx | 4 ++-- content/docs/reference/cli/db.mdx | 4 ++-- content/docs/reference/cli/doctor.mdx | 4 ++-- content/docs/reference/cli/encrypt.mdx | 4 ++-- content/docs/reference/cli/env.mdx | 4 ++-- content/docs/reference/cli/eql.mdx | 4 ++-- content/docs/reference/cli/impl.mdx | 4 ++-- content/docs/reference/cli/index.mdx | 4 ++-- content/docs/reference/cli/init.mdx | 4 ++-- content/docs/reference/cli/manifest.mdx | 4 ++-- content/docs/reference/cli/plan.mdx | 4 ++-- content/docs/reference/cli/schema.mdx | 4 ++-- content/docs/reference/cli/status.mdx | 4 ++-- content/docs/reference/cli/telemetry.mdx | 4 ++-- content/docs/reference/cli/wizard.mdx | 4 ++-- scripts/generate-cli-docs.ts | 11 +++++++---- 17 files changed, 40 insertions(+), 35 deletions(-) diff --git a/.github/workflows/cli-manifest.yml b/.github/workflows/cli-manifest.yml index 09879e0..3e87404 100644 --- a/.github/workflows/cli-manifest.yml +++ b/.github/workflows/cli-manifest.yml @@ -63,7 +63,9 @@ jobs: - uses: oven-sh/setup-bun@v2 with: - bun-version: latest + # Pinned rather than `latest`, matching docs.yml: a bun release + # should not change what this workflow commits on its own. + bun-version: 1.3.13 - run: bun install --frozen-lockfile diff --git a/content/docs/reference/cli/auth.mdx b/content/docs/reference/cli/auth.mdx index 2f3f80d..4d4c52a 100644 --- a/content/docs/reference/cli/auth.mdx +++ b/content/docs/reference/cli/auth.mdx @@ -7,10 +7,10 @@ verifiedAgainst: cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from the committed `stash manifest --json` fixture (v1.1.1). To describe a newer release: bump CLI_VERSION_PIN in that script, run `bun run generate-docs:cli:refresh`, and commit the result. */} -Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. +Generated from **`stash` v1.1.1**, via that release's own `manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. The `stash auth` command group. diff --git a/content/docs/reference/cli/db.mdx b/content/docs/reference/cli/db.mdx index e88d4a9..a1e312b 100644 --- a/content/docs/reference/cli/db.mdx +++ b/content/docs/reference/cli/db.mdx @@ -7,10 +7,10 @@ verifiedAgainst: cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from the committed `stash manifest --json` fixture (v1.1.1). To describe a newer release: bump CLI_VERSION_PIN in that script, run `bun run generate-docs:cli:refresh`, and commit the result. */} -Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. +Generated from **`stash` v1.1.1**, via that release's own `manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. The `stash db` command group. diff --git a/content/docs/reference/cli/doctor.mdx b/content/docs/reference/cli/doctor.mdx index 0c8144a..4ab7b54 100644 --- a/content/docs/reference/cli/doctor.mdx +++ b/content/docs/reference/cli/doctor.mdx @@ -7,10 +7,10 @@ verifiedAgainst: cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from the committed `stash manifest --json` fixture (v1.1.1). To describe a newer release: bump CLI_VERSION_PIN in that script, run `bun run generate-docs:cli:refresh`, and commit the result. */} -Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. +Generated from **`stash` v1.1.1**, via that release's own `manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. Diagnose install problems (native binaries, runtime) diff --git a/content/docs/reference/cli/encrypt.mdx b/content/docs/reference/cli/encrypt.mdx index 9d2f327..d63d353 100644 --- a/content/docs/reference/cli/encrypt.mdx +++ b/content/docs/reference/cli/encrypt.mdx @@ -7,10 +7,10 @@ verifiedAgainst: cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from the committed `stash manifest --json` fixture (v1.1.1). To describe a newer release: bump CLI_VERSION_PIN in that script, run `bun run generate-docs:cli:refresh`, and commit the result. */} -Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. +Generated from **`stash` v1.1.1**, via that release's own `manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. The `stash encrypt` command group. diff --git a/content/docs/reference/cli/env.mdx b/content/docs/reference/cli/env.mdx index 41271d9..f3d37c9 100644 --- a/content/docs/reference/cli/env.mdx +++ b/content/docs/reference/cli/env.mdx @@ -7,10 +7,10 @@ verifiedAgainst: cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from the committed `stash manifest --json` fixture (v1.1.1). To describe a newer release: bump CLI_VERSION_PIN in that script, run `bun run generate-docs:cli:refresh`, and commit the result. */} -Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. +Generated from **`stash` v1.1.1**, via that release's own `manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. Mints a fresh ZeroKMS client and a CipherStash access key from your diff --git a/content/docs/reference/cli/eql.mdx b/content/docs/reference/cli/eql.mdx index 4c5ed81..b9a2c22 100644 --- a/content/docs/reference/cli/eql.mdx +++ b/content/docs/reference/cli/eql.mdx @@ -7,10 +7,10 @@ verifiedAgainst: cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from the committed `stash manifest --json` fixture (v1.1.1). To describe a newer release: bump CLI_VERSION_PIN in that script, run `bun run generate-docs:cli:refresh`, and commit the result. */} -Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. +Generated from **`stash` v1.1.1**, via that release's own `manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. The `stash eql` command group. diff --git a/content/docs/reference/cli/impl.mdx b/content/docs/reference/cli/impl.mdx index f0905fa..7a1b9c3 100644 --- a/content/docs/reference/cli/impl.mdx +++ b/content/docs/reference/cli/impl.mdx @@ -7,10 +7,10 @@ verifiedAgainst: cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from the committed `stash manifest --json` fixture (v1.1.1). To describe a newer release: bump CLI_VERSION_PIN in that script, run `bun run generate-docs:cli:refresh`, and commit the result. */} -Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. +Generated from **`stash` v1.1.1**, via that release's own `manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. Execute the plan with a local agent diff --git a/content/docs/reference/cli/index.mdx b/content/docs/reference/cli/index.mdx index b8d3b56..e13f7d3 100644 --- a/content/docs/reference/cli/index.mdx +++ b/content/docs/reference/cli/index.mdx @@ -8,10 +8,10 @@ verifiedAgainst: cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from the committed `stash manifest --json` fixture (v1.1.1). To describe a newer release: bump CLI_VERSION_PIN in that script, run `bun run generate-docs:cli:refresh`, and commit the result. */} -Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. +Generated from **`stash` v1.1.1**, via that release's own `manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. The `stash` CLI. Install with `npx stash@1.1.1`. Every command accepts `--help` and `--version`. diff --git a/content/docs/reference/cli/init.mdx b/content/docs/reference/cli/init.mdx index 338c50b..80297bb 100644 --- a/content/docs/reference/cli/init.mdx +++ b/content/docs/reference/cli/init.mdx @@ -7,10 +7,10 @@ verifiedAgainst: cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from the committed `stash manifest --json` fixture (v1.1.1). To describe a newer release: bump CLI_VERSION_PIN in that script, run `bun run generate-docs:cli:refresh`, and commit the result. */} -Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. +Generated from **`stash` v1.1.1**, via that release's own `manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. Set up CipherStash end-to-end: authenticate, introspect your database, diff --git a/content/docs/reference/cli/manifest.mdx b/content/docs/reference/cli/manifest.mdx index 6182ebf..aa005ce 100644 --- a/content/docs/reference/cli/manifest.mdx +++ b/content/docs/reference/cli/manifest.mdx @@ -7,10 +7,10 @@ verifiedAgainst: cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from the committed `stash manifest --json` fixture (v1.1.1). To describe a newer release: bump CLI_VERSION_PIN in that script, run `bun run generate-docs:cli:refresh`, and commit the result. */} -Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. +Generated from **`stash` v1.1.1**, via that release's own `manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. Emit the CLI command surface as data. `--json` produces the machine- diff --git a/content/docs/reference/cli/plan.mdx b/content/docs/reference/cli/plan.mdx index 18e2df0..6a5dac8 100644 --- a/content/docs/reference/cli/plan.mdx +++ b/content/docs/reference/cli/plan.mdx @@ -7,10 +7,10 @@ verifiedAgainst: cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from the committed `stash manifest --json` fixture (v1.1.1). To describe a newer release: bump CLI_VERSION_PIN in that script, run `bun run generate-docs:cli:refresh`, and commit the result. */} -Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. +Generated from **`stash` v1.1.1**, via that release's own `manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. Draft a reviewable encryption plan at .cipherstash/plan.md diff --git a/content/docs/reference/cli/schema.mdx b/content/docs/reference/cli/schema.mdx index 2536213..fea644c 100644 --- a/content/docs/reference/cli/schema.mdx +++ b/content/docs/reference/cli/schema.mdx @@ -7,10 +7,10 @@ verifiedAgainst: cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from the committed `stash manifest --json` fixture (v1.1.1). To describe a newer release: bump CLI_VERSION_PIN in that script, run `bun run generate-docs:cli:refresh`, and commit the result. */} -Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. +Generated from **`stash` v1.1.1**, via that release's own `manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. The `stash schema` command group. diff --git a/content/docs/reference/cli/status.mdx b/content/docs/reference/cli/status.mdx index d68ee5e..5d3c002 100644 --- a/content/docs/reference/cli/status.mdx +++ b/content/docs/reference/cli/status.mdx @@ -7,10 +7,10 @@ verifiedAgainst: cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from the committed `stash manifest --json` fixture (v1.1.1). To describe a newer release: bump CLI_VERSION_PIN in that script, run `bun run generate-docs:cli:refresh`, and commit the result. */} -Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. +Generated from **`stash` v1.1.1**, via that release's own `manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. Displays implementation status diff --git a/content/docs/reference/cli/telemetry.mdx b/content/docs/reference/cli/telemetry.mdx index ff39174..e06868c 100644 --- a/content/docs/reference/cli/telemetry.mdx +++ b/content/docs/reference/cli/telemetry.mdx @@ -7,10 +7,10 @@ verifiedAgainst: cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from the committed `stash manifest --json` fixture (v1.1.1). To describe a newer release: bump CLI_VERSION_PIN in that script, run `bun run generate-docs:cli:refresh`, and commit the result. */} -Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. +Generated from **`stash` v1.1.1**, via that release's own `manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. Manage the anonymous, opt-out usage analytics the CLI collects to diff --git a/content/docs/reference/cli/wizard.mdx b/content/docs/reference/cli/wizard.mdx index e858a6f..1226c07 100644 --- a/content/docs/reference/cli/wizard.mdx +++ b/content/docs/reference/cli/wizard.mdx @@ -7,10 +7,10 @@ verifiedAgainst: cli: "1.1.1" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v1.1.1). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from the committed `stash manifest --json` fixture (v1.1.1). To describe a newer release: bump CLI_VERSION_PIN in that script, run `bun run generate-docs:cli:refresh`, and commit the result. */} -Generated from **`stash` v1.1.1** via `npx stash@1.1.1 manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. +Generated from **`stash` v1.1.1**, via that release's own `manifest --json`. Run `npx stash@1.1.1 --help` to see the live command surface. AI-guided encryption setup (reads your codebase) diff --git a/scripts/generate-cli-docs.ts b/scripts/generate-cli-docs.ts index 8e8857c..4300037 100644 --- a/scripts/generate-cli-docs.ts +++ b/scripts/generate-cli-docs.ts @@ -67,8 +67,11 @@ const CLI_NAME = "stash"; const CLI_VERSION_PIN = process.env.STASH_VERSION ?? "1.1.1"; // Refresh the fixture from npm instead of reading it. CI and humans only. const REFRESH = process.argv.includes("--refresh"); -let CLI_VERSION = ""; // resolved to the latest published npm version at run time -const RUNNER = "npx"; // normalized invocation shown in docs +// Read back off the manifest in main(), not assumed from CLI_VERSION_PIN: the +// manifest is what the pages are actually built from, and readFixture() +// guarantees the two agree. +let CLI_VERSION = ""; +const RUNNER = "npx"; // how a READER runs the CLI, shown in docs. Not how this script obtains it. const FIXTURE = path.join( process.cwd(), "scripts/fixtures", @@ -239,11 +242,11 @@ function toManifest(m: CliManifest): Manifest { // ── Render ─────────────────────────────────────────────────────────────────── const generatedMarker = (): string => - `{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from \`${CLI_NAME} manifest --json\` (v${CLI_VERSION}). Re-run \`bun run generate-docs:cli\` to refresh from the latest published CLI. */}`; + `{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from the committed \`${CLI_NAME} manifest --json\` fixture (v${CLI_VERSION}). To describe a newer release: bump CLI_VERSION_PIN in that script, run \`bun run generate-docs:cli:refresh\`, and commit the result. */}`; function banner(): string { return ` -Generated from **\`${CLI_NAME}\` v${CLI_VERSION}** via \`${RUNNER} ${CLI_NAME}@${CLI_VERSION} manifest --json\`. Run \`${RUNNER} ${CLI_NAME}@${CLI_VERSION} --help\` to see the live command surface. +Generated from **\`${CLI_NAME}\` v${CLI_VERSION}**, via that release's own \`manifest --json\`. Run \`${RUNNER} ${CLI_NAME}@${CLI_VERSION} --help\` to see the live command surface. `; }