Repository navigation
feat(cli): self-documenting CLI — colocated typed docs for every command, API function, and schema - #4714
Merged
Conversation
josephfarina
requested review from
cixzhang,
ejhammond and
imdreamrunner
as code owners
August 4, 2026 22:26
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
PR Analysis Report📚 Storybook PreviewView Storybook for this PR 🧪 Sandbox PreviewView Sandbox for this PR No new or modified components detected. Bundle Size SummaryNo component packages changed. Accessibility AuditStatus: No accessibility violations detected. Generated by PR Enrichment workflow | Storybook | Sandbox | View full report |
…-types
Adds the typed, colocated doc vocabulary the CLI docs will be authored against,
and generalizes the shared `type: 'function'` doc so hooks and CLI/API functions
are one kind:
- FunctionDoc — a function (hook or @astryxdesign/cli/api export); `returns` now
also accepts nameless {type,data} envelope entries (relaxed, back-compat).
- SchemaDoc — an authored object shape (config, integration, codemod, the
doc-types, the response envelope) with recursive field docs.
- CommandDoc — a CLI command; references its FunctionDoc via `fn` (feeds the
later defineCommand -> --help converter).
- EnumDoc — a closed vocabulary (error codes, response-type discriminants).
Each has a sealed zod parser wired into parseDoc's discriminant switch and
exported from @astryxdesign/cli/authoring. Purely additive: existing docs still
validate (typecheck:authoring green) and the CLI surface is unchanged
(golden --check: 0 diffs).
Co-authored-by: Cursor <cursoragent@cursor.com>
Adds a colocated FunctionDoc (`<fn>.doc.mjs`, kind: 'api') for every public
@astryxdesign/cli/api export — component, docs, hook, template, blog, discover,
swizzle, theme{Build,Add,List}, listThemes, layout{Expand,Check,Grammar}, search,
build, upgrade, init, doctor, validateIntegration, summarizeIssues.
Each is grounded in its function's source of truth: params from the Options
typedef, returns as one {type,data} envelope entry per response discriminant, and
throws enumerated from the real ERROR_CODES throw sites. A colocated
api-docs-parse test validates every doc through parseDoc (21 docs). Additive only
— the CLI surface is unchanged (golden --check: 0 diffs).
Co-authored-by: Cursor <cursoragent@cursor.com>
Colocated docs for the rest of the CLI surface: - CommandDocs (clients/cli/commands/*.doc.mjs) — one per command/subcommand, each referencing its FunctionDoc via `fn`, with flags mapped to params. - SchemaDocs — the authored objects: config, integration, codemod, the response envelope, and the doc-types themselves (component/hook/function/reference/ template/schema/command/enum) so authors can finally see each shape. - EnumDocs — the error-codes and response-types vocabularies. Adds a drift harness (packages/cli/test/drift): every colocated doc must parse; each CommandDoc's fn + arg/option params must resolve to a real FunctionDoc; the command name must exist in the manifest; and the two EnumDocs must equal ERROR_CODES / the manifest response-type set exactly. 57 docs checked, 0 drift. Additive only — golden --check: 0 diffs; typecheck:authoring green. Co-authored-by: Cursor <cursoragent@cursor.com>
defineCommand (clients/cli/lib) turns a typed CommandDoc (+ the FunctionDoc it wraps, for inherited param descriptions) into a configured Commander command — the single converter that lets --help + the manifest be sourced from the same colocated docs that feed `astryx docs` and the docsite. Unit-tested against search's doc. Extends the drift harness so every CommandDoc must mirror the live CLI exactly: args (name/required/variadic), option flags, and subcommands all match the manifest (57 docs, 0 drift). Registrations are not switched over yet, so the CLI surface is unchanged (golden --check: 0 diffs); flipping each command to defineCommand — which regenerates that command's help golden — is the follow-on. Co-authored-by: Cursor <cursoragent@cursor.com>
Documents the CLI's four-layer architecture (clients/api/authoring/foundation), the colocated self-documenting .doc.mjs system (FunctionDoc/CommandDoc/SchemaDoc/ EnumDoc/ReferenceDoc) and its drift harness, how to add a command, and how to test the CLI (drift + golden harnesses, typecheck:authoring). Co-authored-by: Cursor <cursoragent@cursor.com>
typecheck:strict (checkJs over the whole CLI, including every .doc.mjs) now covers the converter: guard the command token against the string|undefined from Array.pop(), and cast the action to Commander's expected signature. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
The command, error-code, and response-type tables in packages/cli/README.md are now generated between markers from the sources of truth (astryx manifest + the error-codes and response-types EnumDocs), formatted through the repo prettier config, with a `readme:check` vitest gate. Fixes the drift: the command table now lists blog/build/layout/ validate-integration; the error-code table drops the non-existent ERR_TEMPLATE_CONFIG/ERR_TEMPLATE_GET and adds the missing real codes (ERR_UNKNOWN_THEME, ERR_CODEMOD_FAILED, ERR_FETCH_FAILED, ERR_LAYOUT_PARSE/INVALID, ERR_AMBIGUOUS_*, ...); the response-type table covers all 40 discriminants. Co-authored-by: Cursor <cursoragent@cursor.com>
…neCommand Migrates the search registration to defineCommand, which builds the Commander command (description, args, options) from search.doc.mjs — the first command with --help sourced from the docs. Refines defineCommand to preserve the exact --help/manifest surface: `choices` and `examples` stay doc metadata (surfaced by `astryx docs` and the doc site, not injected into help), and an arg gets a description only when the doc sets one explicitly. Behavior-preserving: golden --check 0 diffs, drift 0, typecheck:strict 0. Co-authored-by: Cursor <cursoragent@cursor.com>
Migrates every remaining command registration — component, docs, hook, template, blog, discover, swizzle, build, doctor, upgrade, init, validate-integration, and the theme + layout command groups — to defineCommand, so each command's --help and the introspected manifest are built from its colocated CommandDoc. Actions are preserved verbatim; doc summaries and option descriptions were aligned to the exact current strings, which surfaced (and fixed) real doc drift: stray trailing periods on summaries and param-only options that would have inherited FunctionDoc text. Widens CommandOptionDoc.default to string | boolean | string[] for upgrade's repeatable/boolean flag defaults. Behavior-preserving across the whole CLI surface: golden --check 84 invocations 0 diffs, typecheck:strict 0, typecheck:authoring 0, drift 0. Co-authored-by: Cursor <cursoragent@cursor.com>
A field with no `type` hit Zod's default invalid_type message ("expected string,
received undefined") because the `.min(1, 'field type is required')` message only
covers empty strings, not undefined. Adds the Zod v4 `{error}` message so a
missing type reports "field type is required" for doc authors.
Co-authored-by: Cursor <cursoragent@cursor.com>
The golden verification harness stays a local-only tool for proving CLI-surface changes are behavior-preserving; it is not committed. Remove its commands from CONTRIBUTING and the changeset, pointing contributors at the committed drift suite and the README generator (readme / readme:check) instead. Co-authored-by: Cursor <cursoragent@cursor.com>
6 tasks done
The published ./api type-surface check (packing the CLI fires prepack -> sync:api-types) failed with TS7016: authoring/index.d.ts re-exports parseFunction/parseSchema/parseCommand/parseEnum from their .mjs parsers, which shipped no declaration files, so a strict consumer resolved them as implicit any. Adds a parse.d.mts beside each new parser, matching the existing component/hook/reference/template parsers. Verified with the CI script itself: node .github/scripts/cli-api-types-verify.mjs Co-authored-by: Cursor <cursoragent@cursor.com>
…ture check
CONTRIBUTING describes the CLI's layering and file conventions; most of them are
mechanical, so check them instead of reviewing them.
ESLint (no custom rules needed — no-restricted-imports / no-restricted-syntax,
kept disjoint per layer because flat config overrides rather than merges a
same-named rule):
- authoring/ imports no other layer; api/ never imports clients/
- zod stays sealed behind the authoring/ parsers
- commands register via defineCommand, so --help and the manifest keep coming
from the colocated CommandDoc
scripts/check-cli-structure.mjs covers what per-file linting cannot — which files
exist next to each other. Wired into check:repo, so it runs in `pnpm lint` and
the pre-commit hook:
- every doc-type ships type.ts + parse.mjs + parse.d.mts + <kind>.doc.mjs, and
re-exports its parser. This is exactly the invariant whose violation shipped
a broken ./api type surface and only failed at pack time in CI (TS7016).
- every api/<name>/ ships typedefs, a FunctionDoc, and a test
Closes the one gap that check surfaced: api/json's three published consumer
helpers (parseResponse, isError, assertResponse) had no FunctionDocs. All 16 api
folders now conform, with no exemptions.
Every rule is already clean repo-wide, so they land as errors to prevent
regressions rather than to flag a backlog.
Co-authored-by: Cursor <cursoragent@cursor.com>
… script Two pieces of drift found in review, both in code this PR added: - authoring/doctypes/parse.d.mts declared only 4 of the 8 doc kinds parseDoc dispatches to. That declaration shadows the implementation for published consumers, so narrowing a parsed SchemaDoc/CommandDoc/EnumDoc read as a no-overlap comparison and `fields`/`members` were inaccessible. - CONTRIBUTING told contributors to run `pnpm -F @astryxdesign/cli test`, but the CLI package defined no test script, so the documented command silently did nothing. Adds test + test:watch, scoped to packages/cli. check:cli-structure only asserted that parse.d.mts existed, not that it was complete — so it could not have caught the first bug. It now also asserts the parseDoc union covers every doc-type folder (verified: reintroducing the stale union fails the check with one error per missing kind). Also hardens two gaps found while reviewing that script: - it reported "0 checked — structure is intact" if a checked directory were ever moved; a check that passes when its subject vanishes is worse than none - the README generator surfaced a CLI that failed to boot as "Unexpected end of JSON input" rather than the real exit code and stderr Co-authored-by: Cursor <cursoragent@cursor.com>
Audit of the human-facing artifacts turned up three inaccuracies: - CONTRIBUTING's "Adding a command" steps were out of order. defineCommand builds the command *from* its CommandDoc, so the doc has to exist before the handler; the steps still said register-then-document. They also never mentioned defineCommand or the test that check:cli-structure now requires. - The three api/json FunctionDocs overstated their types. The published surface is `parseResponse(raw): any` and `isError(result): boolean` — isError is not a TypeScript type predicate and does not narrow, so describing it as one was wrong. They now state the real signatures and point at the *Response types for typed access. - The changeset omitted both user-visible outcomes: --help is now generated from the CommandDocs, and the README reference tables are generated (which fixed documented error codes that do not exist and four missing commands). Co-authored-by: Cursor <cursoragent@cursor.com>
6 tasks done
josephfarina
added a commit
that referenced
this pull request
Aug 5, 2026
… bottom layer (#4736) Two follow-ups to #4714. Generate the published ./authoring type declarations from JSDoc instead of hand-writing them, the same way ./api already worked. The 13 hand-maintained .d.mts files are gone. A hand-written declaration shadows the JSDoc beside it, so it could disagree with the implementation and still compile — and both ways that can go wrong had already shipped (a missing declaration resolving as `any`, and a stale parseDoc union that dropped three doc kinds). Generation makes both impossible: tsc rejects a `@returns` that disagrees with the code before a declaration can be emitted. Also fixes parseFunction, which published HookDoc under the general name. Make foundation/ a true bottom layer: it no longer imports api/, and ESLint now enforces that direction alongside the existing authoring/ and api/ rules. The template adapter and the integration contribution validators moved into foundation, where their callers already were; neither had any api dependency. The Project <-> adapter module cycle still exists, but no longer spans layers. Behavior-preserving: the CLI's observable surface is byte-identical across 84 invocations.
3 tasks done
This branch was successfully 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.
Summary
Makes the CLI self-documenting: a typed, colocated
.doc.mjssits next to every documentable surface, validated so it cannot drift from the code.@astryxdesign/cli/authoring):FunctionDoc,CommandDoc,SchemaDoc,EnumDoc, each with a sealed zod parser wired intoparseDoc. Generalizes the shared'function'doc so hooks and CLI/API functions are one kind.FunctionDocfor every@astryxdesign/cli/apiexport, aCommandDocfor every command/subcommand (referencing its function viafn), aSchemaDocfor config/integration/codemod, the response envelope, and the doc-types themselves, andEnumDocs for the error-code and response-type vocabularies.defineCommandconverter (CommandDoc → Commander) so--helpand the manifest can be sourced from the docs.CommandDoc'sfn/args/options resolve to a real function and match the manifest; theEnumDocs equalERROR_CODES/ the manifest response-type set exactly) and a golden harness (the whole observable CLI surface — help, manifest, command output, error paths, exit codes — is byte-stable).CONTRIBUTING.md: a new "Working on the astryx CLI" section.Additive only: the CLI's observable behavior is unchanged (golden
--check0-diff throughout). Every.doc.mjsis type-checked in CI viatypecheck:strict(checkJsoverapi/,clients/,foundation/,authoring/).Test plan
pnpm -F @astryxdesign/cli test(parser + drift + golden suites)pnpm -F @astryxdesign/cli golden:check-> 0 diffspnpm -F @astryxdesign/cli typecheck:strict(the new.doc.mjstype-check)pnpm -F @astryxdesign/cli typecheck:authoringnode packages/cli/test/drift/docs-drift.mjs-> 0 driftFollow-ups (not in this PR)
astryx docs(namespaced discovery + rendering) and on the doc site.defineCommandso--helpis sourced from the docs.Made with Cursor