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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/cli-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Generate CLI docs
run: pnpm --filter sentry run generate:schema && pnpm --filter sentry run generate:docs
run: pnpm --filter sentry run generate:docs
- name: Set docs release
run: |
release=$(node -p 'require("./packages/cli/package.json").version')
Expand Down
2 changes: 1 addition & 1 deletion apps/cli-docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"dependencies": ["sentry"]
},
"scripts": {
"ci:prepare": "pnpm --filter sentry run generate:schema && pnpm --filter sentry run generate:docs",
"ci:prepare": "pnpm --filter sentry run generate:docs",
"dev": "astro dev",
"build": "astro build",
"test": "node --test test/*.test.mjs",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"build": "dotenv -e .env -e .env.local -- pnpm -r --filter '!sentry' --filter '!sentry-cli-docs' --if-present run build",
"build:cli": "dotenv -e .env -e .env.local -- pnpm --filter sentry run build && pnpm --filter sentry-cli-docs run build",
"check:generated": "pnpm --filter @sentry/mcp-core generate-definitions && git diff --exit-code -- packages/mcp-core/src/toolDefinitions.json packages/mcp-core/src/skillDefinitions.json plugins/sentry-mcp/agents/sentry-mcp.md plugins/sentry-mcp-experimental/agents/sentry-mcp.md",
"deploy:docs": "pnpm --filter sentry run generate:schema && pnpm --filter sentry run generate:docs && pnpm --filter sentry-cli-docs run build && pnpm --filter sentry-cli-docs run deploy",
"deploy:docs": "pnpm --filter sentry run generate:docs && pnpm --filter sentry-cli-docs run build && pnpm --filter sentry-cli-docs run deploy",
"eval": "pnpm --filter '@sentry/mcp-server-evals...' --if-present run build && dotenv -e .env -e .env.local -- pnpm --filter @sentry/mcp-server-evals run eval",
"eval:ci": "pnpm --filter '@sentry/mcp-server-evals...' --if-present run build && CI=true dotenv -e .env -e .env.local -- pnpm --stream -r run eval:ci",
"format": "oxfmt --write",
Expand Down
5 changes: 4 additions & 1 deletion packages/cli/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -820,7 +820,10 @@ Use `"date"` for timestamp-based sort (not `"time"`). Export sort types from the

### Generated Docs & Skills

All command docs and skill files are generated via `pnpm run generate:docs` (which runs `generate:banner`, `generate:completions`, `generate:command-docs`, `generate:skill`, then `generate:docs-sections`). This runs automatically as part of `dev`, `build`, `typecheck`, and `test` scripts.
All command docs and skill files are generated via `pnpm run generate:docs`. This runs automatically as part of `dev`, `build`, `typecheck`, and `test` scripts.

- **One process**: the `generate:*` scripts (except `generate:grammar`, a plain Node script) call `script/generate.ts`, which runs the requested steps in one process so the route tree loads once. Phase 1 writes what the tree imports (API schema, grammars, output-schema field data, banner), runs those steps concurrently, and must not load the tree; its steps must not read each other's outputs. Phase 2 stubs the generated files the tree imports (`script/lib/route-tree-stubs.ts`), then runs each step's exported `emit()`. Scripts that load the tree without generating, such as `check-fragments`, leave those stubs behind, so `ensure-generated.mjs` treats a file that still holds its stub (`script/lib/route-tree-stub-files.mjs`) as missing. Phase-2 modules may import the tree statically, because the runner imports them only after the stubs exist. A new generator exports `emit()` and gets a step in `script/generate.ts`, with no stub code of its own. A phase-2 step may also declare `prepare`, which the runner calls as the run begins: `command-api` uses it to parse `src/` in a worker thread (`script/lib/command-api-analysis.ts`) during phase 1 and the tree load. The worker reads the API schema once the `schema` step finishes, and `command-api` runs last because its `emit` waits for that worker. In `package.json`, call the runner (`tsx script/generate.ts <steps>`) rather than chaining `pnpm run generate:*`. `docs` names every step but `sdk`, including `schema`, so callers need not run `generate:schema` first.
- **Skipped steps**: a phase-1 step with `files` in `script/generate.ts` runs only when the content hash of its inputs or outputs differs from its entry in `src/generated/.stamps.json` (`script/lib/step-stamp.ts`). When such a step starts reading a new file or package, add it to that step's inputs. `output-schemas` and the phase-2 steps have no stamp: their inputs are the source they import, which is no short list.

- **Command docs** (`apps/cli-docs/src/content/docs/commands/*.md`) and the configuration page (`apps/cli-docs/src/content/docs/configuration.md`) are **gitignored** and generated from CLI metadata + hand-written fragments in `apps/cli-docs/src/fragments/` (paths live in `script/paths.ts`).
- **Skill files** (`plugins/sentry-cli/skills/sentry-cli/`) are **committed** (consumed by external plugin systems) and auto-committed by CI when stale.
Expand Down
39 changes: 20 additions & 19 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,30 +56,31 @@
"scripts": {
"tsx": "tsx --env-file-if-exists=.env.local --import ./script/require-shim.mjs",
"cli": "node script/ensure-generated.mjs && tsx --env-file-if-exists=.env.local --import ./script/require-shim.mjs --import ./script/strip-arg-separator.mjs src/bin.ts",
"dev": "pnpm run generate:schema && pnpm run generate:docs && pnpm run generate:sdk && tsx --import ./script/require-shim.mjs --import ./script/strip-arg-separator.mjs src/bin.ts",
"build": "pnpm --filter @sentry/mcp-core run build && pnpm run generate:schema && pnpm run generate:docs && pnpm run generate:sdk && pnpm tsx script/build.ts --single",
"build:all": "pnpm --filter @sentry/mcp-core run build && pnpm run generate:schema && pnpm run generate:docs && pnpm run generate:sdk && pnpm tsx script/build.ts",
"bundle": "pnpm --filter @sentry/mcp-core run build && pnpm run generate:schema && pnpm run generate:docs && pnpm run generate:sdk && pnpm tsx script/bundle.ts",
"typecheck": "pnpm --filter @sentry/mcp-core run build && pnpm run generate:docs && pnpm run generate:sdk && tsc --noEmit",
"dev": "tsx script/generate.ts docs sdk && tsx --import ./script/require-shim.mjs --import ./script/strip-arg-separator.mjs src/bin.ts",
"build": "pnpm --filter @sentry/mcp-core run build && tsx script/generate.ts docs sdk && pnpm tsx script/build.ts --single",
"build:all": "pnpm --filter @sentry/mcp-core run build && tsx script/generate.ts docs sdk && pnpm tsx script/build.ts",
"bundle": "pnpm --filter @sentry/mcp-core run build && tsx script/generate.ts docs sdk && pnpm tsx script/bundle.ts",
"typecheck": "pnpm --filter @sentry/mcp-core run build && tsx script/generate.ts docs sdk && tsc --noEmit",
"lint": "pnpm -w exec oxlint --report-unused-disable-directives packages/cli && pnpm -w exec oxfmt --check packages/cli",
"lint:fix": "pnpm -w exec oxlint --fix packages/cli && pnpm -w exec oxfmt --write packages/cli",
"test": "pnpm run test:unit",
"test:unit": "pnpm run generate:docs && pnpm run generate:sdk && vitest run test/lib test/commands test/types test/script --coverage",
"test:changed": "pnpm run generate:docs && pnpm run generate:sdk && vitest run --changed",
"test:unit": "tsx script/generate.ts docs sdk && vitest run test/lib test/commands test/types test/script --coverage",
"test:changed": "tsx script/generate.ts docs sdk && vitest run --changed",
"test:e2e": "pnpm tsx script/prepare-e2e-bundle.ts && vitest run test/e2e",
"test:init-eval": "vitest run test/init-eval --testTimeout 600000",
"generate:sdk": "pnpm tsx script/generate-sdk.ts",
"generate:skill": "pnpm tsx script/generate-skill.ts",
"generate:banner": "pnpm tsx script/generate-banner-image.ts",
"generate:docs": "node script/ensure-generated.mjs schema && pnpm run generate:grammar && pnpm run generate:output-schemas && pnpm run generate:command-api && pnpm run generate:banner && pnpm run generate:completions && pnpm run generate:top-level-routes && pnpm run generate:command-docs && pnpm run generate:docs-sections && pnpm run generate:skill",
"generate": "tsx script/generate.ts docs sdk",
"generate:sdk": "tsx script/generate.ts sdk",
"generate:skill": "tsx script/generate.ts skill",
"generate:banner": "tsx script/generate.ts banner",
"generate:docs": "tsx script/generate.ts docs",
"generate:grammar": "node ../../scripts/generate-parsers.mjs .",
"generate:output-schemas": "pnpm tsx script/generate-output-schemas.ts",
"generate:completions": "pnpm tsx script/generate-completion-targets.ts",
"generate:top-level-routes": "pnpm tsx script/generate-top-level-routes.ts",
"generate:docs-sections": "pnpm tsx script/generate-docs-sections.ts",
"generate:schema": "pnpm tsx script/generate-api-schema.ts",
"generate:command-api": "pnpm tsx script/generate-command-api.ts",
"generate:command-docs": "pnpm tsx script/generate-command-docs.ts",
"generate:output-schemas": "tsx script/generate.ts output-schemas",
"generate:completions": "tsx script/generate.ts completions",
"generate:top-level-routes": "tsx script/generate.ts top-level-routes",
"generate:docs-sections": "tsx script/generate.ts docs-sections",
"generate:schema": "tsx script/generate.ts schema",
"generate:command-api": "tsx script/generate.ts command-api",
"generate:command-docs": "tsx script/generate.ts command-docs",
"eval:skill": "pnpm tsx script/eval-skill.ts",
"bench": "pnpm tsx script/bench.ts",
"bench:save": "pnpm tsx script/bench.ts --save-baseline",
Expand All @@ -89,7 +90,7 @@
"check:deps": "pnpm tsx script/check-no-deps.ts",
"check:errors": "pnpm tsx script/check-error-patterns.ts",
"check:patches": "pnpm tsx script/check-patches.ts",
"check:docs-sections": "pnpm tsx script/generate-docs-sections.ts --check",
"check:docs-sections": "tsx script/generate.ts docs-sections --check",
"check:env-coverage": "pnpm tsx script/check-env-coverage.ts",
"check:stale-refs": "pnpm tsx script/check-stale-references.ts",
"check:doc-examples": "pnpm tsx script/check-doc-examples.ts",
Expand Down
19 changes: 4 additions & 15 deletions packages/cli/script/check-doc-examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
* 1 - At least one example names a missing command, flag, or ambiguous target
*/

import { mkdirSync, readdirSync } from "node:fs";
import { access, readFile, writeFile } from "node:fs/promises";
import { readdirSync } from "node:fs";
import { readFile } from "node:fs/promises";
import { join } from "node:path";
import { exampleSteps } from "../src/lib/command-example.js";
import type {
Expand All @@ -37,6 +37,7 @@ import type {
RouteMap,
RouteTarget,
} from "../src/lib/introspect.js";
import { writeRouteTreeStubs } from "./lib/route-tree-stubs.js";
import { blankCodeBlocks, markdownCodeBlocks } from "./markdown.js";
import { DOCS_CONTENT, DOCS_FRAGMENTS } from "./paths.js";
import {
Expand All @@ -46,19 +47,7 @@ import {
} from "./sdk-run-calls.js";
import { shellCommands } from "./shell-words.js";

// Bootstrap: skill-content stub (same pattern as generate-docs-sections.ts)
const SKILL_CONTENT_PATH = "src/generated/skill-content.ts";
const skillContentExists = await access(SKILL_CONTENT_PATH).then(
() => true,
() => false,
);
if (!skillContentExists) {
mkdirSync("src/generated", { recursive: true });
await writeFile(
SKILL_CONTENT_PATH,
"export const SKILL_FILES: [string, string][] = [];\n",
);
}
await writeRouteTreeStubs();

const { routes } = await import("../src/app.js");
const { isCommand, isRouteMap } = await import("../src/lib/introspect.js");
Expand Down
19 changes: 4 additions & 15 deletions packages/cli/script/check-fragments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,15 @@
* tsx script/check-fragments.ts --strict # Errors for missing subcommands
*/

import { mkdirSync, readdirSync } from "node:fs";
import { access, readFile, writeFile } from "node:fs/promises";
import { readdirSync } from "node:fs";
import { access, readFile } from "node:fs/promises";
import type { RouteInfo, RouteMap } from "../src/lib/introspect.js";
import { findFragmentCommandIssues } from "./fragment-commands.js";
import { writeRouteTreeStubs } from "./lib/route-tree-stubs.js";
import { markdownHeadings } from "./markdown.js";
import { DOCS_FRAGMENTS } from "./paths.js";

// Ensure skill-content stub exists (see generate-command-docs.ts for rationale)
const SKILL_CONTENT_PATH = "src/generated/skill-content.ts";
const skillContentExists = await access(SKILL_CONTENT_PATH).then(
() => true,
() => false,
);
if (!skillContentExists) {
mkdirSync("src/generated", { recursive: true });
await writeFile(
SKILL_CONTENT_PATH,
"export const SKILL_FILES: [string, string][] = [];\n",
);
}
await writeRouteTreeStubs();

const { routes } = await import("../src/app.js");
const { extractAllRoutes } = await import("../src/lib/introspect.js");
Expand Down
25 changes: 23 additions & 2 deletions packages/cli/script/ensure-generated.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
*/

import { execSync } from "node:child_process";
import { existsSync } from "node:fs";
import { existsSync, readFileSync, statSync } from "node:fs";
import { ROUTE_TREE_STUBS } from "./lib/route-tree-stub-files.mjs";

/** Produced by `generate:schema`; imported at runtime by `src/`. */
const SCHEMA_OUTPUTS = [
Expand All @@ -40,6 +41,26 @@ const DOCS_OUTPUTS = [
"src/generated/top-level-routes.json",
];

const STUBS = new Map(ROUTE_TREE_STUBS);

/**
* Whether `path` holds generated output. A file that still holds its
* route-tree stub does not: scripts that load the tree without generating,
* such as `check-fragments`, write stubs and leave them behind.
*/
function isGenerated(path) {
if (!existsSync(path)) {
return false;
}
const stub = STUBS.get(path);
// Compare sizes first so generated files are not read.
return (
stub === undefined ||
statSync(path).size !== Buffer.byteLength(stub) ||
readFileSync(path, "utf8") !== stub
);
}

function run(script) {
console.error(`Missing generated files; running \`pnpm run ${script}\`...`);
// Send generator output to stderr: `pnpm run cli mcp` serves JSON-RPC on
Expand All @@ -59,6 +80,6 @@ if (!SCHEMA_OUTPUTS.every(existsSync)) {
if (wants("grammar") && !GRAMMAR_OUTPUTS.every(existsSync)) {
run("generate:grammar");
}
if (wants("docs") && !DOCS_OUTPUTS.every(existsSync)) {
if (wants("docs") && !DOCS_OUTPUTS.every(isGenerated)) {
run("generate:docs");
}
35 changes: 19 additions & 16 deletions packages/cli/script/generate-api-schema.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
#!/usr/bin/env tsx
/**
* Generate the CLI discovery index from the installed SDK's operation catalog.
*
* Usage: tsx script/generate-api-schema.ts
* Usage: tsx script/generate.ts schema
* Outputs: src/generated/api-schema.json and src/generated/idempotent-operations.json
*/

Expand All @@ -14,22 +13,26 @@ import { projectApiOperations } from "./lib/api-schema.js";

const OUTPUT_PATH = "src/generated/api-schema.json";
const IDEMPOTENT_OUTPUT_PATH = "src/generated/idempotent-operations.json";
const endpoints = projectApiOperations(operations);

await mkdir(dirname(OUTPUT_PATH), { recursive: true });
await writeFile(OUTPUT_PATH, JSON.stringify(endpoints, null, 2));
/** Write the endpoint index and the idempotent write operations. */
export async function emit(): Promise<void> {
const endpoints = projectApiOperations(operations);

console.log(
`Generated ${OUTPUT_PATH} (${endpoints.length} endpoints, ${Math.round(JSON.stringify(endpoints).length / 1024)}KB)`,
);
await mkdir(dirname(OUTPUT_PATH), { recursive: true });
await writeFile(OUTPUT_PATH, JSON.stringify(endpoints, null, 2));

const idempotentOperations = selectIdempotentWrites(operations);
console.log(
`Generated ${OUTPUT_PATH} (${endpoints.length} endpoints, ${Math.round(JSON.stringify(endpoints).length / 1024)}KB)`,
);

await writeFile(
IDEMPOTENT_OUTPUT_PATH,
JSON.stringify(idempotentOperations, null, 2),
);
const idempotentOperations = selectIdempotentWrites(operations);

console.log(
`Generated ${IDEMPOTENT_OUTPUT_PATH} (${idempotentOperations.length} operations)`,
);
await writeFile(
IDEMPOTENT_OUTPUT_PATH,
JSON.stringify(idempotentOperations, null, 2),
);

console.log(
`Generated ${IDEMPOTENT_OUTPUT_PATH} (${idempotentOperations.length} operations)`,
);
}
11 changes: 3 additions & 8 deletions packages/cli/script/generate-banner-image.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#!/usr/bin/env tsx
/**
* Generate the Graphics Banner
*
Expand All @@ -16,7 +15,7 @@
* Output is a committed module (like `src/generated/skill-content.ts`) so the
* runtime never needs to read the asset from disk.
*
* Usage: tsx script/generate-banner-image.ts
* Usage: tsx script/generate.ts banner
* Output: src/generated/banner-image.ts
*/

Expand Down Expand Up @@ -119,7 +118,8 @@ function encodePngBase64(mask: PNG): string {
return PNG.sync.write(out, { deflateLevel: 9 }).toString("base64");
}

async function main(): Promise<void> {
/** Write `src/generated/banner-image.ts` from the banner mask. */
export async function emit(): Promise<void> {
const png = PNG.sync.read(readFileSync(MASK_PATH));
const pngBase64 = encodePngBase64(png);

Expand Down Expand Up @@ -149,8 +149,3 @@ export const BANNER_IMAGE = {
`Wrote ${OUT_PATH} (${png.width}x${png.height}, png ${pngBase64.length} base64 bytes)\n`,
);
}

main().catch((err) => {
process.stderr.write(`generate-banner-image failed: ${String(err)}\n`);
process.exit(1);
});
Loading
Loading