Repository navigation
docs(cli): Generate and check docs that mirror CLI behavior - #1626
Merged
Merged
Conversation
Lists that copied code (destructive commands, detected agents, target syntax, skill file layout) are now generated from the code. New checks fail CI when a `sentry ...` example names a missing command or flag, passes a bare org slug as a project-first target, or when an installer option is missing from its help text or the install guide. `pnpm run cli` and `generate:docs` now create missing build-time files, and check:stale-refs flags `pnpm run cli -- <args>`, which pnpm forwards as a literal `--`. Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authored-By: BYK <burak.kaya@sentry.io>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
BYK
requested changes
Oct 23, 2026
Address review feedback on the docs drift guards: - Type agent tables by `KnownAgent` (keys of AGENT_DISPLAY_NAMES), so a detectable agent without a display name is a compile error. Drop the map-structure test. - Key TARGET_FORMS by parse result type so TypeScript enforces that every type is documented, and have the generator parse each example and fail on a mismatch. Drop target-syntax.test.ts. - Check only structured `docs.examples`, not examples hand-written into `fullDescription`. - Scan the install scripts in check:env-coverage so installer variables must be in ENV_VAR_REGISTRY (and therefore configuration.md). Add the missing SENTRY_NO_MODIFY_PATH. check:installer-docs now covers only installer help text and flags. - Replace the pnpm-specific `pnpm run cli --` lint with a dev-only preload that drops a leading `--`, so the dev scripts work with any package manager. - Add check:destructive: derive SDK DELETE operations from @sentry/api, follow calls, imports, and barrel re-exports through src/, and fail when a command that sends a DELETE is not marked destructive. Co-Authored-By: BYK <burak.kaya@sentry.io>
BYK
added a commit
that referenced
this pull request
Oct 23, 2026
…1594) Refs #1564. This is the first slice: the mechanism plus five commands. A `[<org>/][<project>]` positional now owns both parsing and resolution. The command picks a parser from `src/lib/target-positional.ts`, and that parser comes with its resolver: ```ts parse: orgOptionalTarget, // resolveOrgOptionalTarget parse: projectBoundTarget, // resolveProjectBoundTarget parse: orgProjectTarget(resolveViewTargets), // command-specific resolver ``` `func` gets a `LazyPositional` with three members: `target.parsed` (the `ParsedOrgProject`), `target.resolved` (a promise of the resolver's result), and `target.raw` (what the user typed): ```ts async *func(this: SentryContext, flags: ListFlags, target: OrgOptionalTarget) { const resolved = await target.resolved; ``` `func` never calls `parseOrgProjectArg` or a resolver, never picks which resolver to use, and needs no default for an omitted target. An omitted optional target arrives as auto-detect. `project view` moves its target switch into `resolveViewTargets` and reads `target.parsed.type`. - **No command names to pass.** Resolvers read the command path from `this.commandPrefix`, which Stricli's `forCommand` sets and the SDK invoker now sets from its command path. The `COMMAND_NAME` constants these commands used are gone. A context without `commandPrefix` makes resolution throw rather than print a hint with a blank command name; test mock contexts set it. - **Destructive commands guard automatically.** `buildDeleteCommand` calls `markDestructive` on its lazy positionals, and the target resolver then runs `requireExplicitTarget`. `project delete` uses plain `projectBoundTarget`. Once #1626's `destructive` flag lands, the call can move into `buildCommand` so `issue merge` and `trial start` get it too. - **Lint rule.** `sentry-cli/require-target-resolution` fails a command function that takes `OrgOptionalTarget`, `ProjectBoundTarget` or `OrgProjectTarget<R>` but never reads `.resolved`, since skipping it skips the resolver and the destructive guard. **When things run.** Both steps are lazy (`LazyPositional` in `src/lib/positional-parse.ts`, a plain class with memoized getters). The parse runs on the first `parsed` read and the resolver on the first `resolved` read; each runs once. Both happen inside `func`, so the order matches main: - The auth guard and rc URL check run first. A bad target with no login still reports "Not authenticated" (exit 10). - Parse errors keep their `CliError` semantics. `sentry project view @latest` exits 21 with the `ValidationError`, not Stricli's `Failed to parse …` (exit -4). A parse error also rejects `resolved`. - `parseOrgProjectArg`'s URL side effect still runs after those checks. For a pasted Sentry URL it calls `applySentryUrlContext`, which can switch `SENTRY_URL`/`SENTRY_HOST`, save that host as the default, or throw `HostScopeError`. Running it at Stricli scan time would have let the auth guard and rc URL check run against the host taken from the URL. Resolution stays a promise because resolvers need auth, config, DSN detection, and API calls. Shared code that handles any positional (`buildListCommand`'s plural-alias interception, `help` recovery, `setArgsContext`) reads the raw input with `rawPositional(value)`, an `instanceof LazyPositional` check. Stricli calls parsers with the command context as `this`; the resolver gets that context plus `commandName`. **Callers that skip Stricli.** The typed SDK (`buildInvoker`) and tests that call `command.loader()` pass raw strings. `buildCommand` runs `applyLazyPositionals` on them, and on omitted optional positionals, so every caller keeps passing what the user typed and `func` always gets the same shape. When plural-alias interception drops the target, `buildListCommand` runs it again so the dropped target becomes auto-detect. `sdk.project.view({ orgProject: "@latest" })` is rejected instead of silently auto-detecting. The SDK invoker also sets `directInvoke`, which turns off `help` positional recovery: a failing SDK call whose target is `help` throws its error, as on main, instead of returning help text. **Shell completion is unchanged.** Completion kinds come from a tag on the parser (`withCompletionTarget`), shared with `parseOrgTarget` and `parseOrgProjectTarget`. The generated targets file matches main. Migrated: `trace list`, `replay list`, `feedback list`, `project view`, `project delete`. Not in this PR: - `LIST_TARGET_POSITIONAL` / `buildOrgListCommand`. #1575 (`splitListTargetAndFilter`) reads the raw target and splits it before parsing (for example `acme/web-*`). These should move after it lands, using `target.raw`. - Issue and dashboard positionals. #1572 adds `parseIssueTarget` / `parseDashboardTarget` in the same files. When one of the two PRs rebases, its parsers should switch from the `PARSER_TARGETS` map to `withCompletionTarget`. - `[<org>/<project>/]<id>` positionals (`trace view`, `span list`, `event view`, `log view`, …). Stricli parses each argument on its own, but what each one means depends on how many were given. Those keep parsing in `func`. - Release `org/version` positionals. They already share `resolveReleaseTarget`. <!-- junior-request-attribution:start --> via **BYK**. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:slack%3AC010884QHLG%3A1792667769.623489 --> -- [View Junior Session](https://junior-prod.sentry.dev/conversations/slack%3AC010884QHLG%3A1792667769.623489) [[Sentry]](https://sentry.sentry.io/explore/conversations/slack%3AC010884QHLG%3A1792667769.623489/?project=4510944073809921) <!-- junior-session-footer:end --> --------- Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: Burak Yigit Kaya <burak.kaya@sentry.io>
Follow-up to #1594: `buildCommand` now calls `markDestructive` when `destructive: true`, instead of `buildDeleteCommand` doing it, so the `destructive` flag is the single source for the Safety Rules list and the auto-detect guard. `issue merge` and `trial start` get the guard if they move to lazy target positionals. check:doc-examples now recognizes target positionals by their `org/project` completion kind, which covers the new lazy target parsers as well as `parseOrgProjectTarget`.
Contributor
Author
|
Merged
Passing: |
Resolve conflicts with #1633, which moved the remaining hand-written examples into `docs.examples`: take main's version of the dashboard commands and the alert/dashboard fragments, then regenerate the skill references. check:doc-examples now covers the migrated examples and flagged: - `dashboard revisions` and `dashboard restore` examples that pass a bare `my-org` (a project lookup); changed to `my-org/` - examples that start with `sudo` or `NAME=value`, which the check misread as the command name; it now skips those prefixes Co-Authored-By: BYK <burak.kaya@sentry.io>
#1633 added the `no-regex-markdown-parsing` lint rule. Replace the fenced block regex with the shared `markdownCodeBlocks` / `blankCodeBlocks` helpers in script/markdown.ts, which use the same lexer as the docs renderer.
BYK
approved these changes
Oct 23, 2026
BYK
added a commit
that referenced
this pull request
Oct 23, 2026
Closes #1637. Stacked on #1626 (it replaces `TARGET_FORMS` from that PR), so the base is `byk/cli-docs-drift-guards`. The `<org>/<project>` target syntax is now one peggy grammar, `packages/cli/grammar/org-project-target.pegjs`. Both the parser and the docs table come from it. - **Parser.** `parseOrgProjectArg` keeps its signature. It still keeps the first non-blank line, auto-detects on empty input, and reads Sentry URLs itself. Everything else goes through the generated parser. Slug and `@` checks run through `options` hooks (`validateResourceId`, `rejectAtSelector`), so error messages stay the same. `parseSlashOrgProject`, `parseExplicitProjectList`, and their helpers are deleted. The backtracking pitfall from the spike is documented in the grammar, and the existing `@latest` test guards it: token rules only capture text, and validation runs in the winning alternative. - **Docs.** Each `target` alternative has `// @Form <syntax> — <meaning>` lines above it, and `generate-docs-sections.ts` builds the table from them. Generation fails if an alternative has no `@form` or `@invalid` line. The check uses peggy's grammar AST, so a new form can't ship undocumented. The table now also lists the forms `TARGET_FORMS` was missing: Sentry URLs, `/<name>`, `<org>/<Project Name>`, and `<Project Name>`. Omitted targets and URLs never reach the grammar, so they are annotated in the grammar header. - **Generator.** `packages/toolkit-core/script/generate-parsers.ts` moved to `scripts/generate-parsers.mjs` (`pnpm run generate:parsers`). One list of grammar specs covers both packages, and `check:generated` and the pre-commit hook use it. The CLI output goes to `packages/cli/src/generated/org-project-target-grammar.{js,d.ts}` and is committed (`.gitignore` exception). It is about 24 KB unminified. peggy moves from a toolkit-core devDependency to the root and the CLI. The lockfile only changes the importer entries. Migration check, not committed: I compared the new parser with a copy of the old one on 139,954 inputs. They were every string up to 4 characters over `a b / , space @ - _ ? # \n 1 % . A \t`, the 53 literals in `arg-parsing.test.ts`, and some extra edge cases, each with `multi` on and off. There were 0 differences in results, error classes, messages, or fields. While doing this I found and fixed one real bug, which needed end-anchored token rules. Without them, PEG prefix matching turned `A #` into a `SyntaxError`. Not in this PR: `parseIssueArg` (step 4 of the issue), and removing `normalizeSlug` / `normalized`. `parseIssueArg` and `trace-target.ts` still use the latter. `originalSlug` is still needed: it is how resolution tells a display name from a slug. <!-- junior-request-attribution:start --> via **BYK**. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:local%3Aweb%3Ac5ce32bb458d46e900d4be2e --> -- [View Junior Session in Sentry](https://sentry.sentry.io/explore/conversations/local%3Aweb%3Ac5ce32bb458d46e900d4be2e/?project=4510944073809921) <!-- junior-session-footer:end --> --------- Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: Claude Opus <noreply@anthropic.com> Co-authored-by: BYK <burak.kaya@sentry.io>
This branch was previously deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#1541 fixed most of the CLI docs drift found in getsentry/cli#1644, but the remaining gaps were all the same kind of problem: hand-written docs that copy facts out of the code. Fixing each one by hand would just bring the drift back later, so this PR makes the docs come from the code, and adds CI checks for what can't be generated.
Generated from code (via
generate-docs-sections.tsmarkers, checked bycheck:docs-sections):destructiveflag onbuildCommanddrives this list, andbuildDeleteCommandsets it automatically.issue mergeandtrial startset it explicitly. The list grows from 2 commands to 10, and the generated skill picks it up. It now also coverscli uninstall,integration deleteandissue unlink, which refactor(cli): Generate the SDK with the TypeScript AST instead of strings #1644's hand-made list missed.AGENT_DISPLAY_NAMESmap. A unit test fails ifdetectAgent()can return an agent with no display name. This adds Antigravity, which the docs were missing.CONTRIBUTING.md. It comes fromTARGET_FORMS, and a test runs every documented example throughparseOrgProjectArg(). The test also fails if a parse type has no documented form.plugins/README.md. Generation fails if a listed path no longer exists.New checks (added to
ci:policy):check:doc-exampleschecks about 1,400sentry …invocations against the live route tree. It covers fenced shell blocks and inline code in hand-written docs, plus command--helpexamples. An example fails if the command doesn't exist (aliases count), if it uses a flag the command doesn't define, or if it passes a baremy-orgwhere the CLI looks up projects first. The last rule found ambiguous examples in the dashboard help,alert metrics create,project listandagent-conversation list, which now usemy-org/.check:installer-docschecks the install scripts:SENTRY_*variable or flag an install script reads must appear in its own help text.SENTRY_CLI_NO_TELEMETRY, which the bash installer and the guide didn't mention.check:stale-refsnow flagspnpm run cli -- <args>. pnpm passes the literal--through, sopnpm run cli -- --helpfails with "Command not found: --help".Dev setup:
pnpm run cliandgenerate:docsnow create missingsrc/generated/files on a fresh checkout throughscript/ensure-generated.mjs. That removes thegenerate:schemabootstrap step instead of documenting it. Before this,pnpm run cliandpnpm run test:unitfailed on a fresh clone.Docs-only fixes:
sentry inittarget table now matchesresolveTarget: existing project, then org, then a new project.--keep-confighelp text now uses the XDG config dir constant instead of~/.sentry.sentry cli setupfor agent skills; package managers also skip completions.Not ported from #1644: its claim that Homebrew runs
cli setupautomatically. The formula in.craft.ymldoesn't run it, and its caveats tell users to run it themselves.via BYK.
--
View Junior Session in Sentry