Skip to content

feat: add TypeDoc A2UI catalog extractor#2527

Merged
PupilTong merged 10 commits intolynx-family:mainfrom
PupilTong:codex/a2ui-typedoc-catalog-extractor
Apr 27, 2026
Merged

feat: add TypeDoc A2UI catalog extractor#2527
PupilTong merged 10 commits intolynx-family:mainfrom
PupilTong:codex/a2ui-typedoc-catalog-extractor

Conversation

@PupilTong
Copy link
Copy Markdown
Collaborator

@PupilTong PupilTong commented Apr 25, 2026

Summary

  • add a TypeDoc-only A2UI catalog extractor package with API, CLI, and skill packaging support
  • annotate A2UI catalog interfaces with standard TSDoc plus the single custom @a2uiCatalog tag
  • switch A2UI catalog generation away from the old local catalog_generator.ts script

Validation

  • pnpm --filter @lynx-js/a2ui-catalog-extractor test
  • pnpm --filter @lynx-js/a2ui-catalog-extractor build
  • pnpm --filter @lynx-js/a2ui-reactlynx build
  • regenerated packages/genui/a2ui/dist/catalog/*/catalog.json deep-equals the old generated catalog JSON baseline
  • verified no direct typescript, @microsoft/tsdoc, or @babel/parser dependency remains in packages/genui/a2ui-catalog-extractor

Summary by CodeRabbit

  • New Features

    • Added an automated A2UI catalog extractor with a CLI to generate component catalogs and produce A2UI v0.9 catalog JSON.
  • Documentation

    • Added comprehensive extractor README, skill docs, and catalog annotation conventions for TypeDoc→catalog mapping.
  • Chores

    • Build pipeline switched to the new extractor; legacy generator removed. Small prop-type ordering tweaks and catalog annotations added across components. CI/config updates (coverage, test discovery, build refs).
  • Tests

    • Added unit and integration tests validating catalog extraction and outputs.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 25, 2026

⚠️ No Changeset found

Latest commit: 99256e3

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 25, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new TypeDoc-driven extractor package and CLI (@lynx-js/a2ui-catalog-extractor), documents @a2uiCatalog tagging, replaces the in-repo catalog generator with the new extractor, annotates many components for extraction, and updates build, test, TypeScript, Turbo, and Codecov configurations accordingly.

Changes

Cohort / File(s) Summary
Extractor Core
packages/genui/a2ui-catalog-extractor/src/index.ts, packages/genui/a2ui-catalog-extractor/src/cli.ts
New TypeDoc-based extraction library and CLI: TypeDoc parsing, Type → JSON Schema mapping, extraction APIs, writers to emit per-component catalog.json, and CLI arg parsing and runner.
Extractor Packaging & Build
packages/genui/a2ui-catalog-extractor/package.json, packages/genui/a2ui-catalog-extractor/rslib.config.ts, packages/genui/a2ui-catalog-extractor/tsconfig.json, packages/genui/a2ui-catalog-extractor/tsconfig.build.json, packages/genui/a2ui-catalog-extractor/bin/a2ui-catalog-extractor.js
New package manifest, ESM exports, CLI shim, build/test scripts, rslib config, and tsconfig files; package marked private with file whitelist and CLI binary.
Extractor Tests & Config
packages/genui/a2ui-catalog-extractor/test/extractor.test.ts, packages/genui/a2ui-catalog-extractor/rstest.config.ts
Comprehensive unit/integration tests for extraction flows and package rstest config.
Extractor Docs & Skill
packages/genui/a2ui-catalog-extractor/README.md, packages/genui/a2ui-catalog-extractor/skills/a2ui-catalog-extractor/SKILL.md, .github/a2ui-catalog.instructions.md
Documentation describing CLI usage, TypeDoc tagging conventions (@a2uiCatalog), supported TypeScript patterns → JSON Schema mappings, and extractor expectations.
Component Annotations
packages/genui/a2ui/src/catalog/Button/index.tsx, .../Card/index.tsx, .../CheckBox/index.tsx, .../Divider/index.tsx, .../RadioGroup/index.tsx, .../Text/index.tsx
Added @a2uiCatalog JSDoc tags to exported props interfaces to opt components into extraction (documentation-only).
Component Type Adjustments
packages/genui/a2ui/src/catalog/Column/index.tsx, .../List/index.tsx, .../Row/index.tsx, .../Image/index.tsx
Minor prop union reorderings and small type edits; Image uses typed useLynxEffect replacing a useEffect callback (explicit undefined return).
Removed In-repo Generator
packages/genui/a2ui/tools/catalog_generator.ts
Deleted the prior in-repo TypeScript catalog generator (full removal of its scanning and TS-checking logic).
Build Pipeline Changes
packages/genui/a2ui/package.json, packages/genui/a2ui/turbo.json
Build now runs the new extractor CLI (devDependency added); removed tools/** from turbo build inputs.
Workspace & CI Config
packages/genui/tsconfig.json, rstest.config.ts, codecov.yml
Added extractor tsconfig reference, included extractor tests in root rstest projects, and excluded .github/, codecov.yml, pnpm-lock.yaml, and rstest.config.ts from Codecov.
CLI Entrypoint
packages/genui/a2ui-catalog-extractor/bin/a2ui-catalog-extractor.js
Node-executable shim added for invoking compiled CLI.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • HuJean
  • Sherry-hue
  • gaoachao
  • fzx2666-fz

Poem

🐇 I hopped through TypeDoc fields and tags,

Nibbled comments, chased small parsing nags,
From props to schema I carved the night,
Saved each enum, kept ordering tight,
Catalogs sprout — I thumped in delight! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'feat: add TypeDoc A2UI catalog extractor' directly and clearly summarizes the main change: adding a new TypeDoc-based catalog extractor tool for A2UI components.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 25, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@PupilTong PupilTong changed the title [codex] add TypeDoc A2UI catalog extractor feat: add TypeDoc A2UI catalog extractor Apr 25, 2026
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 25, 2026

Merging this PR will improve performance by 9.16%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 2 improved benchmarks
✅ 79 untouched benchmarks
⏩ 26 skipped benchmarks1

Performance Changes

Benchmark BASE HEAD Efficiency
002-hello-reactLynx-destroyBackground 894.9 µs 819.8 µs +9.16%
transform 1000 view elements 43 ms 39.9 ms +7.57%

Comparing PupilTong:codex/a2ui-typedoc-catalog-extractor (99256e3) with main (5b57e36)2

Open in CodSpeed

Footnotes

  1. 26 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (f6d7177) during the generation of this report, so 5b57e36 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@relativeci
Copy link
Copy Markdown

relativeci Bot commented Apr 25, 2026

React Example

#7658 Bundle Size — 225.38KiB (0%).

99256e3(current) vs f6d7177 main#7646(baseline)

Bundle metrics  no changes
                 Current
#7658
     Baseline
#7646
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 4 4
No change  Modules 179 179
No change  Duplicate Modules 69 69
No change  Duplicate Code 44.57% 44.57%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#7658
     Baseline
#7646
No change  IMG 145.76KiB 145.76KiB
No change  Other 79.63KiB 79.63KiB

Bundle analysis reportBranch PupilTong:codex/a2ui-typedoc-cat...Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented Apr 25, 2026

React External

#774 Bundle Size — 680.27KiB (0%).

99256e3(current) vs f6d7177 main#763(baseline)

Bundle metrics  no changes
                 Current
#774
     Baseline
#763
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 3 3
No change  Modules 17 17
No change  Duplicate Modules 5 5
No change  Duplicate Code 8.59% 8.59%
No change  Packages 0 0
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#774
     Baseline
#763
No change  Other 680.27KiB 680.27KiB

Bundle analysis reportBranch PupilTong:codex/a2ui-typedoc-cat...Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented Apr 25, 2026

Web Explorer

#9231 Bundle Size — 900.02KiB (0%).

99256e3(current) vs f6d7177 main#9219(baseline)

Bundle metrics  Change 2 changes
                 Current
#9231
     Baseline
#9219
No change  Initial JS 44.46KiB 44.46KiB
No change  Initial CSS 2.22KiB 2.22KiB
No change  Cache Invalidation 0% 0%
No change  Chunks 9 9
No change  Assets 11 11
Change  Modules 230(+0.88%) 228
No change  Duplicate Modules 11 11
Change  Duplicate Code 27.28%(-0.04%) 27.29%
No change  Packages 10 10
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#9231
     Baseline
#9219
No change  JS 495.88KiB 495.88KiB
No change  Other 401.92KiB 401.92KiB
No change  CSS 2.22KiB 2.22KiB

Bundle analysis reportBranch PupilTong:codex/a2ui-typedoc-cat...Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented Apr 25, 2026

React MTF Example

#790 Bundle Size — 196.54KiB (0%).

99256e3(current) vs f6d7177 main#778(baseline)

Bundle metrics  no changes
                 Current
#790
     Baseline
#778
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 3 3
No change  Modules 173 173
No change  Duplicate Modules 66 66
No change  Duplicate Code 44.08% 44.08%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#790
     Baseline
#778
No change  IMG 111.23KiB 111.23KiB
No change  Other 85.31KiB 85.31KiB

Bundle analysis reportBranch PupilTong:codex/a2ui-typedoc-cat...Project dashboard


Generated by RelativeCIDocumentationReport issue

@PupilTong PupilTong marked this pull request as ready for review April 27, 2026 04:22
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (14)
codecov.yml (1)

32-32: Consider broadening the rstest config ignore to all per-package configs.

"rstest.config.ts" (without a glob) will only match the file at the repository root. The per-package configs under e.g. packages/webpack/*/rstest.config.ts are not covered by this ignore (the new packages/genui/a2ui-catalog-extractor/rstest.config.ts is incidentally already excluded via the packages/genui/** rule). If the intent is to exclude all rstest configurations from coverage uniformly, use a glob pattern instead.

♻️ Suggested change
-  - "rstest.config.ts"
+  - "**/rstest.config.ts"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@codecov.yml` at line 32, The ignore entry "rstest.config.ts" in codecov.yml
only matches the repo root file; change it to a glob (for example
"**/rstest.config.ts") so that all per-package rstest.config.ts files (e.g.,
packages/*/*/rstest.config.ts) are excluded uniformly; update the
ignore/patterns list to replace the literal "rstest.config.ts" with the glob
pattern.
packages/genui/a2ui/src/catalog/RadioGroup/index.tsx (1)

19-22: Optional: rename RadioGroupComponentPropsRadioGroupProps for consistency.

Other catalog components in this PR use the <Name>Props convention (CheckBoxProps, DividerProps, ImageProps, etc.). Since the extractor now identifies the catalog interface via the @a2uiCatalog tag rather than the type name, this is a low-risk rename that improves consistency. Skip if this name is referenced externally.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui/src/catalog/RadioGroup/index.tsx` around lines 19 - 22,
Rename the exported interface RadioGroupComponentProps to RadioGroupProps to
match the project's <Name>Props convention; update the declaration and all local
references/usages in this file (and any internal imports) from
RadioGroupComponentProps → RadioGroupProps, ensure the `@a2uiCatalog` tag remains
above the interface, and only skip or add a deprecation/export alias if
RadioGroupComponentProps is referenced externally to preserve backward
compatibility.
packages/genui/a2ui/src/catalog/Image/index.tsx (1)

10-13: Optional: simplify the isolated-declarations workaround.

The local useLynxEffect cast plus explicit return undefined; works, but it's a bit unusual to read. If this pattern is needed in more catalog components, consider extracting it into a shared helper (e.g. in core/) so each component file doesn't duplicate the cast. If this stays a one-off, the current approach is fine.

Also applies to: 38-41

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui/src/catalog/Image/index.tsx` around lines 10 - 13, Create
a shared typed wrapper for the isolated-declarations workaround instead of
duplicating the local cast: add an exported wrapper (e.g., export const
useLynxEffect = useEffect as (effect: () => void | (() => void), deps?: readonly
unknown[]) => void) in the shared core utilities and replace the local cast in
this file by importing that shared useLynxEffect; if you decide to keep it
one-off, simplify the local declaration to the same typed alias without the
explicit `return undefined;` boilerplate and use the imported/aliased
useLynxEffect in place of the inline cast.
packages/genui/a2ui-catalog-extractor/bin/a2ui-catalog-extractor.js (1)

1-7: LGTM — minimal shim is fine.

Optional: if a friendlier error matters for first-time users running the CLI before the package is built, you could wrap the import in a try/catch that prints a "run pnpm build first" hint. Otherwise the current implementation is idiomatic for monorepo bin shims.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-catalog-extractor/bin/a2ui-catalog-extractor.js` around
lines 1 - 7, Wrap the dynamic import of '../dist/cli.js' in a try/catch to
provide a friendlier error for users who run the CLI before building; catch any
exception thrown by the import in the top-level script (the current import
'../dist/cli.js' statement) and log a clear hint like "package not built — run
`pnpm build`" (and optionally the original error) before exiting with a non-zero
code so first-time users get actionable guidance.
packages/genui/a2ui/package.json (1)

96-96: Build script bypasses turbo and uses a fragile relative path.

Two concerns with the new build pipeline:

  1. pnpm --filter @lynx-js/a2ui-catalog-extractor build invoked from inside another package's build script bypasses turbo's task graph and caching. Since @lynx-js/a2ui-catalog-extractor is already a devDependency, the dependency build should be expressed via turbo's dependsOn (^build) instead of nested pnpm invocations. Otherwise running pnpm turbo build will not see this as a graph edge and may produce inconsistent caching.
  2. node ../a2ui-catalog-extractor/dist/cli.js hard-codes the relative workspace layout. The package publishes a bin (a2ui-catalog-extractor./bin/a2ui-catalog-extractor.js), so prefer invoking it through pnpm-resolved bins for portability and to honor the shebang.
♻️ Proposed refactor
   "scripts": {
-    "build": "pnpm --filter `@lynx-js/a2ui-catalog-extractor` build && node ../a2ui-catalog-extractor/dist/cli.js --catalog-dir src/catalog --out-dir dist/catalog && tsc -b"
+    "build": "a2ui-catalog-extractor --catalog-dir src/catalog --out-dir dist/catalog && tsc -b"
   },

And ensure turbo.json declares the extractor's build as an upstream dependency ("dependsOn": ["^build"]) so the CLI is available before this package builds.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui/package.json` at line 96, The current build script in
package.json uses a nested pnpm call and a fragile relative node path; change
the "build" script to invoke the published bin name (a2ui-catalog-extractor) via
the resolved pnpm bin (e.g., "a2ui-catalog-extractor --catalog-dir src/catalog
--out-dir dist/catalog && tsc -b") instead of "pnpm --filter ... build" and
"node ../a2ui-catalog-extractor/dist/cli.js", and add a Turbo dependency so the
extractor package is built first by declaring in turbo.json that this package's
build "dependsOn": ["^build"] (so the `@lynx-js/a2ui-catalog-extractor` build runs
via Turbo and the CLI is available).
packages/genui/a2ui-catalog-extractor/test/extractor.test.ts (2)

26-29: Temporary directories are not cleaned up.

fs.mkdtempSync(...) is invoked twice (Lines 26 and 204) but the resulting directories are never removed. Each test run leaves residue under os.tmpdir(). Consider wrapping cleanup in an afterEach/afterAll (or a try/finally) using fs.rmSync(dir, { recursive: true, force: true }).

♻️ Example cleanup pattern
+import { afterAll } from '@rstest/core';
+
+const tempDirs: string[] = [];
+afterAll(() => {
+  for (const dir of tempDirs) {
+    fs.rmSync(dir, { recursive: true, force: true });
+  }
+});
+
 // ...
-    const fixtureDir = fs.mkdtempSync(
-      path.join(os.tmpdir(), 'a2ui-catalog-fixture-'),
-    );
+    const fixtureDir = fs.mkdtempSync(
+      path.join(os.tmpdir(), 'a2ui-catalog-fixture-'),
+    );
+    tempDirs.push(fixtureDir);

Also applies to: 204-204

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-catalog-extractor/test/extractor.test.ts` around lines 26
- 29, The tests create temporary directories via fs.mkdtempSync (e.g.,
fixtureDir and the other temp dir created later) but never remove them; add
cleanup to remove these temp dirs after tests by tracking the created paths
(e.g., push fixtureDir and the second mkdtemp result into an array) and calling
fs.rmSync(dir, { recursive: true, force: true }) in an afterEach or afterAll
hook in extractor.test.ts, or wrap individual test logic in try/finally to
rmSync on the corresponding fixtureDir; reference the variables fixtureDir and
the second mkdtemp return to locate where to add the cleanup.

202-224: Test depends on prebuilt packages/genui/a2ui/dist/catalog.

This test compares freshly extracted output against the pre-existing dist catalogs, so it implicitly requires pnpm --filter @lynx-js/a2ui build (or a previous successful build) to have run first. If dist/catalog is missing, the test fails at the existence check inside readDistCatalogs().

Consider either:

  • documenting the build prerequisite in the package README / SKILL, or
  • adding a workspace-level test dependency / a globalSetup that runs the a2ui build, or
  • pointing the comparison at the source-of-truth fixtures committed inside this package instead of a sibling package's build output

so that this test is hermetic and survives a clean check-out / fresh CI runner.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-catalog-extractor/test/extractor.test.ts` around lines
202 - 224, The test "generates JSON deep-equal to
packages/genui/a2ui/dist/catalog" depends on an external build
(readDistCatalogs() reading a2ui dist), making it non-hermetic; either make the
test use committed fixtures or ensure the build runs before tests. Fix by
changing readDistCatalogs() (or the test setup around a2uiDir and
writeComponentCatalogs()) to fall back to a committed test fixture directory
(e.g., test/fixtures/catalogs) when packages/genui/a2ui/dist/catalog is missing,
or add a global setup step that runs the `@lynx-js/a2ui` build (pnpm --filter
`@lynx-js/a2ui` build) before this test executes; update the test to reference the
fixture path or ensure globalSetup runs prior to invoking
writeComponentCatalogs() so the loop comparing expectedCatalog.json with actual
catalog.json remains valid.
packages/genui/a2ui-catalog-extractor/package.json (1)

8-15: Consider exposing an explicit import condition.

The . export only declares types and default. While ESM-only consumers will resolve default, adding an import condition makes the intent explicit and avoids surprises if a bundler or runtime introduces other conditions later:

♻️ Suggested change
     ".": {
       "types": "./dist/index.d.ts",
+      "import": "./dist/index.js",
       "default": "./dist/index.js"
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-catalog-extractor/package.json` around lines 8 - 15, The
package.json exports field currently declares only the "." entry with "types"
and "default"; add an explicit "import" condition to the "." export (alongside
"default" and "types") so ESM consumers resolve deterministically—update the
exports object for "." to include an "import" key pointing to the same ESM build
(e.g., "./dist/index.js") while leaving "types" and "default" unchanged so
bundlers/runtimes that check "import" pick the intended module.
packages/genui/a2ui-catalog-extractor/README.md (1)

102-127: Optional: document the cwd option in the API section.

The test suite uses extractCatalogComponents({ cwd, sourceFiles }) and writeComponentCatalogs({ cwd, outDir, sourceFiles }), but the README API examples omit cwd. Mentioning that sourceFiles are resolved against cwd (defaulting to process.cwd()) would help consumers integrate the extractor from arbitrary working directories.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-catalog-extractor/README.md` around lines 102 - 127,
Update the README API examples to document the optional cwd option and how
sourceFiles are resolved against it: note that extractCatalogComponents and
writeComponentCatalogs accept an optional cwd (defaulting to process.cwd()),
show a minimal example calling extractCatalogComponents({ cwd, sourceFiles })
and writeComponentCatalogs({ cwd, outDir, sourceFiles }), and state that
sourceFiles are resolved relative to cwd so consumers can run the extractor from
arbitrary working directories; keep extractCatalogComponentsFromTypeDocJson
unchanged but mention it does not use cwd.
packages/genui/a2ui-catalog-extractor/src/cli.ts (1)

118-122: Document/consolidate --source vs --catalog-dir precedence.

When both are supplied, --catalog-dir is silently ignored — this isn't conveyed in usage. Consider concatenating them ([...sourceInputs, ...catalogDirs]) so they're additive (matching the --source "Repeatable" semantics), or note the precedence in the help text. Right now the two flags are nearly synonymous, which makes the silent override surprising.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-catalog-extractor/src/cli.ts` around lines 118 - 122, The
current precedence logic silently ignores options.catalogDirs when
options.sourceInputs is present; update the inputs resolution so the two flags
are additive: build inputs by concatenating options.sourceInputs and
options.catalogDirs (e.g., combine the arrays) and then fall back to
['src/catalog'] only if the combined array is empty; update the variable/logic
around inputs (referencing inputs, options.sourceInputs, options.catalogDirs) so
both repeatable flags are respected together.
packages/genui/a2ui-catalog-extractor/src/index.ts (4)

731-744: Edge case: character: 0 is dropped from error locations.

if (source.character) is falsy for column 0, so an error at the very first column won't render :0 in the location prefix. Trivial in practice, but source.character !== undefined would be more correct (same applies to source.line, although line 0 is unusual).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-catalog-extractor/src/index.ts` around lines 731 - 744,
The createReflectionError function drops column/line 0 because it checks
truthiness; change the presence checks for source.character and source.line to
explicit undefined/null checks (e.g., source.character !== undefined and
source.line !== undefined or source.character != null) so 0 is preserved in the
generated location string; update the conditional expressions that build `line`
and `character` (and mention source.fullFileName/fileName usage remains
unchanged) to use these explicit checks in the createReflectionError function.

208-223: Stale per-component outputs are not pruned.

writeCatalogComponents only writes new files; if a catalog interface is renamed or removed, the old dist/catalog/<OldName>/catalog.json directory will persist and bloat the published package until a manual rm -rf dist/catalog runs. If the build pipeline doesn't already clean dist/catalog between builds, consider clearing outDir (or at least old per-component subdirectories not present in components) at the start of this function.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-catalog-extractor/src/index.ts` around lines 208 - 223,
writeCatalogComponents currently only writes/overwrites per-component
directories and never removes stale directories, causing removed/renamed
components to remain; fix by pruning outDir before writing: resolve options.cwd
and outDir as already done, then either remove the entire outDir
(fs.rmSync(outDir, { recursive: true, force: true })) and recreate it, or
enumerate existing subdirectories under outDir and delete any whose names are
not present in the components array (compare against components.map(c =>
c.name)); then proceed to create componentOutDir and write catalog.json as
before. Ensure safe handling of non-existent outDir and preserve existing code
paths for cwd, options, components, outDir, and componentOutDir.

487-523: Move the declaration.children === undefined guard above the loop.

The check at lines 515-520 is logically dead-code-adjacent: if declaration.children is undefined, the for…of (declaration.children ?? []) at line 498 silently iterates nothing, builds an empty schema, then we throw. Either:

  • Move the guard before the loop so the intent ("a reflection type literal with no member declarations is invalid") is enforced upfront, or
  • Remove the guard and accept {} (an empty object literal) as a valid schema.
♻️ Suggested ordering
 function parseObjectReflection(
   declaration: TypeDocReflection,
   owner: TypeDocReflection,
 ): JsonSchema {
+  if (declaration.children === undefined) {
+    throw createReflectionError(
+      owner,
+      `Missing object declaration for "${owner.name}".`,
+    );
+  }
+
   const schema: JsonSchema = {
     type: 'object',
     properties: {},
     required: [],
     additionalProperties: false,
   };
 
-  for (const child of declaration.children ?? []) {
+  for (const child of declaration.children) {
     if (!isPropertyReflection(child)) {
       continue;
     }
@@
     if (!isOptionalProperty(child)) {
       schema.required!.push(child.name);
     }
   }
 
-  if (declaration.children === undefined) {
-    throw createReflectionError(
-      owner,
-      `Missing object declaration for "${owner.name}".`,
-    );
-  }
-
   return schema;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-catalog-extractor/src/index.ts` around lines 487 - 523,
The function parseObjectReflection currently iterates over declaration.children
with for (const child of declaration.children ?? []) then later throws if
declaration.children === undefined; move the guard that throws
createReflectionError(owner, `Missing object declaration for "${owner.name}".`)
to before the for-loop (i.e., check if declaration.children === undefined and
throw immediately) so you don't silently build an empty schema when children are
missing; update parseObjectReflection accordingly to validate
declaration.children up-front before using it.

431-445: Numeric literal types lose precision in the JSON schema.

The parseLiteralType function preserves string literal values via enum: [value], but numeric and bigint literals only emit { type: 'number' }. When parseUnionType processes a union like 2 | 4 | 8, each literal becomes { type: 'number' }, which deduplicates via schemasEqual to a single entry, making the constraint indistinguishable from an untyped number in the final schema.

Currently no numeric literal unions exist in the catalog definitions, so this is not actively affecting generated schemas. However, if catalog authors later use numeric unions for constrained values (e.g. icon sizes), the constraint will be silently lost. To future-proof, consider mirroring the string path:

     case 'number':
-    case 'bigint':
       return { type: 'number' };
+    case 'bigint':
+      return { type: 'number', enum: [value] };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-catalog-extractor/src/index.ts` around lines 431 - 445,
parseLiteralType currently emits generic { type: 'number' } for number/bigint
literals which loses per-literal constraints when parseUnionType deduplicates
via schemasEqual; change parseLiteralType so numeric and bigint literals mirror
the string path by returning an enum containing the literal (e.g. for number: {
type: 'number', enum: [value] } and for bigint: include a preserved
representation such as { type: 'integer' or 'number', enum: [Number(value) or
value.toString()] } according to existing JSON schema conventions in your
codebase) so parseUnionType and schemasEqual preserve distinct numeric literal
members; update any tests that expect previous behavior. Ensure you modify
parseLiteralType and validate interaction with parseUnionType and schemasEqual.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/genui/a2ui-catalog-extractor/src/cli.ts`:
- Around line 163-170: The main-module detection using import.meta.url ===
`file://${process.argv[1]}` fails on Windows; replace the equality check by
converting process.argv[1] to a file URL with pathToFileURL and comparing that
to import.meta.url (i.e., import pathToFileURL from 'url' and use
pathToFileURL(process.argv[1]).href === import.meta.url) so runCli is invoked
reliably when this file is executed directly; update the try block around runCli
and keep the existing error handling for consistency.

---

Nitpick comments:
In `@codecov.yml`:
- Line 32: The ignore entry "rstest.config.ts" in codecov.yml only matches the
repo root file; change it to a glob (for example "**/rstest.config.ts") so that
all per-package rstest.config.ts files (e.g., packages/*/*/rstest.config.ts) are
excluded uniformly; update the ignore/patterns list to replace the literal
"rstest.config.ts" with the glob pattern.

In `@packages/genui/a2ui-catalog-extractor/bin/a2ui-catalog-extractor.js`:
- Around line 1-7: Wrap the dynamic import of '../dist/cli.js' in a try/catch to
provide a friendlier error for users who run the CLI before building; catch any
exception thrown by the import in the top-level script (the current import
'../dist/cli.js' statement) and log a clear hint like "package not built — run
`pnpm build`" (and optionally the original error) before exiting with a non-zero
code so first-time users get actionable guidance.

In `@packages/genui/a2ui-catalog-extractor/package.json`:
- Around line 8-15: The package.json exports field currently declares only the
"." entry with "types" and "default"; add an explicit "import" condition to the
"." export (alongside "default" and "types") so ESM consumers resolve
deterministically—update the exports object for "." to include an "import" key
pointing to the same ESM build (e.g., "./dist/index.js") while leaving "types"
and "default" unchanged so bundlers/runtimes that check "import" pick the
intended module.

In `@packages/genui/a2ui-catalog-extractor/README.md`:
- Around line 102-127: Update the README API examples to document the optional
cwd option and how sourceFiles are resolved against it: note that
extractCatalogComponents and writeComponentCatalogs accept an optional cwd
(defaulting to process.cwd()), show a minimal example calling
extractCatalogComponents({ cwd, sourceFiles }) and writeComponentCatalogs({ cwd,
outDir, sourceFiles }), and state that sourceFiles are resolved relative to cwd
so consumers can run the extractor from arbitrary working directories; keep
extractCatalogComponentsFromTypeDocJson unchanged but mention it does not use
cwd.

In `@packages/genui/a2ui-catalog-extractor/src/cli.ts`:
- Around line 118-122: The current precedence logic silently ignores
options.catalogDirs when options.sourceInputs is present; update the inputs
resolution so the two flags are additive: build inputs by concatenating
options.sourceInputs and options.catalogDirs (e.g., combine the arrays) and then
fall back to ['src/catalog'] only if the combined array is empty; update the
variable/logic around inputs (referencing inputs, options.sourceInputs,
options.catalogDirs) so both repeatable flags are respected together.

In `@packages/genui/a2ui-catalog-extractor/src/index.ts`:
- Around line 731-744: The createReflectionError function drops column/line 0
because it checks truthiness; change the presence checks for source.character
and source.line to explicit undefined/null checks (e.g., source.character !==
undefined and source.line !== undefined or source.character != null) so 0 is
preserved in the generated location string; update the conditional expressions
that build `line` and `character` (and mention source.fullFileName/fileName
usage remains unchanged) to use these explicit checks in the
createReflectionError function.
- Around line 208-223: writeCatalogComponents currently only writes/overwrites
per-component directories and never removes stale directories, causing
removed/renamed components to remain; fix by pruning outDir before writing:
resolve options.cwd and outDir as already done, then either remove the entire
outDir (fs.rmSync(outDir, { recursive: true, force: true })) and recreate it, or
enumerate existing subdirectories under outDir and delete any whose names are
not present in the components array (compare against components.map(c =>
c.name)); then proceed to create componentOutDir and write catalog.json as
before. Ensure safe handling of non-existent outDir and preserve existing code
paths for cwd, options, components, outDir, and componentOutDir.
- Around line 487-523: The function parseObjectReflection currently iterates
over declaration.children with for (const child of declaration.children ?? [])
then later throws if declaration.children === undefined; move the guard that
throws createReflectionError(owner, `Missing object declaration for
"${owner.name}".`) to before the for-loop (i.e., check if declaration.children
=== undefined and throw immediately) so you don't silently build an empty schema
when children are missing; update parseObjectReflection accordingly to validate
declaration.children up-front before using it.
- Around line 431-445: parseLiteralType currently emits generic { type: 'number'
} for number/bigint literals which loses per-literal constraints when
parseUnionType deduplicates via schemasEqual; change parseLiteralType so numeric
and bigint literals mirror the string path by returning an enum containing the
literal (e.g. for number: { type: 'number', enum: [value] } and for bigint:
include a preserved representation such as { type: 'integer' or 'number', enum:
[Number(value) or value.toString()] } according to existing JSON schema
conventions in your codebase) so parseUnionType and schemasEqual preserve
distinct numeric literal members; update any tests that expect previous
behavior. Ensure you modify parseLiteralType and validate interaction with
parseUnionType and schemasEqual.

In `@packages/genui/a2ui-catalog-extractor/test/extractor.test.ts`:
- Around line 26-29: The tests create temporary directories via fs.mkdtempSync
(e.g., fixtureDir and the other temp dir created later) but never remove them;
add cleanup to remove these temp dirs after tests by tracking the created paths
(e.g., push fixtureDir and the second mkdtemp result into an array) and calling
fs.rmSync(dir, { recursive: true, force: true }) in an afterEach or afterAll
hook in extractor.test.ts, or wrap individual test logic in try/finally to
rmSync on the corresponding fixtureDir; reference the variables fixtureDir and
the second mkdtemp return to locate where to add the cleanup.
- Around line 202-224: The test "generates JSON deep-equal to
packages/genui/a2ui/dist/catalog" depends on an external build
(readDistCatalogs() reading a2ui dist), making it non-hermetic; either make the
test use committed fixtures or ensure the build runs before tests. Fix by
changing readDistCatalogs() (or the test setup around a2uiDir and
writeComponentCatalogs()) to fall back to a committed test fixture directory
(e.g., test/fixtures/catalogs) when packages/genui/a2ui/dist/catalog is missing,
or add a global setup step that runs the `@lynx-js/a2ui` build (pnpm --filter
`@lynx-js/a2ui` build) before this test executes; update the test to reference the
fixture path or ensure globalSetup runs prior to invoking
writeComponentCatalogs() so the loop comparing expectedCatalog.json with actual
catalog.json remains valid.

In `@packages/genui/a2ui/package.json`:
- Line 96: The current build script in package.json uses a nested pnpm call and
a fragile relative node path; change the "build" script to invoke the published
bin name (a2ui-catalog-extractor) via the resolved pnpm bin (e.g.,
"a2ui-catalog-extractor --catalog-dir src/catalog --out-dir dist/catalog && tsc
-b") instead of "pnpm --filter ... build" and "node
../a2ui-catalog-extractor/dist/cli.js", and add a Turbo dependency so the
extractor package is built first by declaring in turbo.json that this package's
build "dependsOn": ["^build"] (so the `@lynx-js/a2ui-catalog-extractor` build runs
via Turbo and the CLI is available).

In `@packages/genui/a2ui/src/catalog/Image/index.tsx`:
- Around line 10-13: Create a shared typed wrapper for the isolated-declarations
workaround instead of duplicating the local cast: add an exported wrapper (e.g.,
export const useLynxEffect = useEffect as (effect: () => void | (() => void),
deps?: readonly unknown[]) => void) in the shared core utilities and replace the
local cast in this file by importing that shared useLynxEffect; if you decide to
keep it one-off, simplify the local declaration to the same typed alias without
the explicit `return undefined;` boilerplate and use the imported/aliased
useLynxEffect in place of the inline cast.

In `@packages/genui/a2ui/src/catalog/RadioGroup/index.tsx`:
- Around line 19-22: Rename the exported interface RadioGroupComponentProps to
RadioGroupProps to match the project's <Name>Props convention; update the
declaration and all local references/usages in this file (and any internal
imports) from RadioGroupComponentProps → RadioGroupProps, ensure the
`@a2uiCatalog` tag remains above the interface, and only skip or add a
deprecation/export alias if RadioGroupComponentProps is referenced externally to
preserve backward compatibility.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cfdbe9ee-ffac-4169-8df4-5e20df2d17b8

📥 Commits

Reviewing files that changed from the base of the PR and between 27d5872 and b9ead9e.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (28)
  • .github/a2ui-catalog.instructions.md
  • codecov.yml
  • packages/genui/a2ui-catalog-extractor/README.md
  • packages/genui/a2ui-catalog-extractor/bin/a2ui-catalog-extractor.js
  • packages/genui/a2ui-catalog-extractor/package.json
  • packages/genui/a2ui-catalog-extractor/rslib.config.ts
  • packages/genui/a2ui-catalog-extractor/rstest.config.ts
  • packages/genui/a2ui-catalog-extractor/skills/a2ui-catalog-extractor/SKILL.md
  • packages/genui/a2ui-catalog-extractor/src/cli.ts
  • packages/genui/a2ui-catalog-extractor/src/index.ts
  • packages/genui/a2ui-catalog-extractor/test/extractor.test.ts
  • packages/genui/a2ui-catalog-extractor/tsconfig.build.json
  • packages/genui/a2ui-catalog-extractor/tsconfig.json
  • packages/genui/a2ui/package.json
  • packages/genui/a2ui/src/catalog/Button/index.tsx
  • packages/genui/a2ui/src/catalog/Card/index.tsx
  • packages/genui/a2ui/src/catalog/CheckBox/index.tsx
  • packages/genui/a2ui/src/catalog/Column/index.tsx
  • packages/genui/a2ui/src/catalog/Divider/index.tsx
  • packages/genui/a2ui/src/catalog/Image/index.tsx
  • packages/genui/a2ui/src/catalog/List/index.tsx
  • packages/genui/a2ui/src/catalog/RadioGroup/index.tsx
  • packages/genui/a2ui/src/catalog/Row/index.tsx
  • packages/genui/a2ui/src/catalog/Text/index.tsx
  • packages/genui/a2ui/tools/catalog_generator.ts
  • packages/genui/a2ui/turbo.json
  • packages/genui/tsconfig.json
  • rstest.config.ts
💤 Files with no reviewable changes (2)
  • packages/genui/a2ui/turbo.json
  • packages/genui/a2ui/tools/catalog_generator.ts

Comment thread packages/genui/a2ui-catalog-extractor/src/cli.ts
@PupilTong PupilTong self-assigned this Apr 27, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/genui/a2ui-catalog-extractor/src/cli.ts (1)

27-42: Document -h, -v, and that --catalog-dir is repeatable.

parseCliArgs accepts -h/-v aliases (lines 69, 73) and pushes to catalogDirs (line 57), but the usage block doesn't mention either. Minor docs gap.

📝 Suggested wording
 Options:
-  --catalog-dir <dir>  Directory to scan for TypeScript catalog interfaces.
+  --catalog-dir <dir>  Directory to scan for TypeScript catalog interfaces. Repeatable.
   --source <path>      Source file or directory to scan. Repeatable.
   --typedoc-json <file>
                        Read an existing TypeDoc JSON project instead of
                        running TypeDoc conversion.
   --out-dir <dir>     Output directory for component catalog.json files.
-  --version           Print the package version.
-  --help              Print this help message.
+  --version, -v       Print the package version.
+  --help, -h          Print this help message.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-catalog-extractor/src/cli.ts` around lines 27 - 42,
Update the CLI usage string to document the accepted short aliases and
repeatability: add `-h` and `-v` as aliases for `--help` and `--version`, and
note that `--catalog-dir` is repeatable (matches behavior in parseCliArgs which
accepts `-h`/`-v` and pushes into catalogDirs). Edit the `usage` constant to
include these items and a brief line in Defaults indicating `--catalog-dir` can
be repeated so the help text aligns with parseCliArgs and the catalogDirs
handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/genui/a2ui-catalog-extractor/src/cli.ts`:
- Around line 27-42: Update the CLI usage string to document the accepted short
aliases and repeatability: add `-h` and `-v` as aliases for `--help` and
`--version`, and note that `--catalog-dir` is repeatable (matches behavior in
parseCliArgs which accepts `-h`/`-v` and pushes into catalogDirs). Edit the
`usage` constant to include these items and a brief line in Defaults indicating
`--catalog-dir` can be repeated so the help text aligns with parseCliArgs and
the catalogDirs handling.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2f34b828-d9d3-414f-a3fb-d517718d03c7

📥 Commits

Reviewing files that changed from the base of the PR and between 1652a90 and 67fd232.

📒 Files selected for processing (1)
  • packages/genui/a2ui-catalog-extractor/src/cli.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (5)
packages/genui/a2ui-catalog-extractor/test/extractor.test.ts (1)

26-28: Optional: clean up mkdtempSync directories after each test.

mkdtempSync is called here and at line 268 but the directories are never removed, leaving behind a2ui-catalog-fixture-* and a2ui-catalog-out-* directories under the OS temp dir on every CI/local run. An afterEach (or try/finally) using fs.rmSync(dir, { recursive: true, force: true }) would keep test runs hermetic.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-catalog-extractor/test/extractor.test.ts` around lines 26
- 28, Tests create temporary directories using fs.mkdtempSync (e.g., the
fixtureDir variable and the other mkdtempSync call at line ~268) but never
remove them; add cleanup to avoid leaving a2ui-catalog-fixture-* and
a2ui-catalog-out-* in the OS temp dir by removing the created dirs after each
test (or in a try/finally) — call fs.rmSync(dir, { recursive: true, force: true
}) for fixtureDir and the output dir in an afterEach block (or immediately in
finally) so the temporary directories created by mkdtempSync are reliably
deleted.
packages/genui/a2ui-catalog-extractor/src/index.ts (4)

283-298: collectSourceFiles doesn't guard against symlink cycles.

fs.readdirSync(dir, { withFileTypes: true }) returns symlinks reported via dirent.isDirectory() only when the link points to a directory and the OS resolves it eagerly; on some platforms a directory symlink shows up as dirent.isSymbolicLink() and is missed entirely, while on others it can recurse through cyclic links. For the current packages/genui/a2ui/src/catalog tree this isn't an issue, but if the extractor is ever pointed at a workspace with linked deps (e.g., pnpm node_modules mirrors leaking into src) it could either skip files or hang.

Consider explicitly skipping symlinks (or tracking visited real paths via fs.realpathSync) and treating only dirent.isDirectory() && !dirent.isSymbolicLink() as recurseable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-catalog-extractor/src/index.ts` around lines 283 - 298,
collectSourceFiles can recurse into or miss symlinked directories leading to
cycles or skipped files; update it to ignore symbolic links when recursing (only
recurse when dirent.isDirectory() && !dirent.isSymbolicLink()) and/or maintain a
Set of visited real paths using fs.realpathSync to avoid following the same real
path twice; apply this change around the fs.readdirSync loop and ensure
isSupportedSourceFile behavior is unchanged for files (still accepting symlinked
files if desired) so you avoid infinite recursion and platform-dependent misses.

193-198: extractCatalogComponentsFromTypeDocJson is a thin alias of extractCatalogComponentsFromTypeDocProject.

The two functions have identical behavior — only the parameter type narrows from ProjectReflection | TypeDocProject to TypeDocProject. Either:

  • Document this entry point's purpose (e.g., "consume TypeDoc's --json output without a ProjectReflection") in a TSDoc block, or
  • Drop the wrapper and let callers use extractCatalogComponentsFromTypeDocProject directly.

Right now it reads as accidental duplication.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-catalog-extractor/src/index.ts` around lines 193 - 198,
The function extractCatalogComponentsFromTypeDocJson is a redundant thin alias
of extractCatalogComponentsFromTypeDocProject; either document its intended
distinct purpose with a TSDoc comment (e.g., "Entrypoint for consuming TypeDoc
--json output as a TypeDocProject") placed immediately above
extractCatalogComponentsFromTypeDocJson, or remove the wrapper and update any
callers/exports to use extractCatalogComponentsFromTypeDocProject directly;
locate and change the symbol extractCatalogComponentsFromTypeDocJson in this
module and any import sites so there are no dangling references.

471-526: schemasEqual is order-sensitive and may leave duplicate oneOf branches.

schemasEqual compares schemas via JSON.stringify, which is sensitive to property insertion order. Within parseUnionType two branches that are structurally identical but produced via different code paths (e.g. a reflection object schema and a reference Record/Array schema, or two object schemas whose properties were inserted in different orders) will fail equality and end up duplicated in the resulting oneOf.

It's not a correctness bug for the catalogs in this PR, but the dedup is mostly a no-op as written. A canonicalizing compare would make the intent reliable, e.g.:

♻️ Suggested fix
-function schemasEqual(left: JsonSchema, right: JsonSchema): boolean {
-  return JSON.stringify(left) === JSON.stringify(right);
-}
+function schemasEqual(left: JsonSchema, right: JsonSchema): boolean {
+  return canonicalize(left) === canonicalize(right);
+}
+
+function canonicalize(value: unknown): string {
+  if (Array.isArray(value)) {
+    return `[${value.map(canonicalize).join(',')}]`;
+  }
+  if (value && typeof value === 'object') {
+    const entries = Object.entries(value as Record<string, unknown>)
+      .filter(([, v]) => v !== undefined)
+      .sort(([a], [b]) => a.localeCompare(b))
+      .map(([k, v]) => `${JSON.stringify(k)}:${canonicalize(v)}`);
+    return `{${entries.join(',')}}`;
+  }
+  return JSON.stringify(value);
+}

Also applies to: 792-794


326-361: Approve with one nit on required: [].

The component schema construction is straightforward and the inheritance/property filtering looks correct. One small nit: when an interface has no required properties, the emitted schema retains an empty required: [] (and similarly at line 542 for nested objects). It's valid JSON Schema but adds noise to catalog.json. Stripping it (and the empty properties: {} for Record-style schemas) when empty would keep snapshots tidier:

-  return schema;
+  if (schema.required && schema.required.length === 0) {
+    delete schema.required;
+  }
+  return schema;

Not blocking — feel free to defer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-catalog-extractor/src/index.ts` around lines 326 - 361,
When building the JsonSchema in createComponentSchema, remove noisy empty
containers before returning: if schema.required is an empty array, delete
schema.required; if schema.properties is an empty object
(Object.keys(schema.properties || {}).length === 0), delete schema.properties.
Apply the same cleanup in the codepath that constructs nested/object schemas
(e.g., where parseTypeDocType produces object/Record schemas) so empty required:
[] and empty properties: {} are stripped across the board.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/genui/a2ui-catalog-extractor/src/index.ts`:
- Around line 283-298: collectSourceFiles can recurse into or miss symlinked
directories leading to cycles or skipped files; update it to ignore symbolic
links when recursing (only recurse when dirent.isDirectory() &&
!dirent.isSymbolicLink()) and/or maintain a Set of visited real paths using
fs.realpathSync to avoid following the same real path twice; apply this change
around the fs.readdirSync loop and ensure isSupportedSourceFile behavior is
unchanged for files (still accepting symlinked files if desired) so you avoid
infinite recursion and platform-dependent misses.
- Around line 193-198: The function extractCatalogComponentsFromTypeDocJson is a
redundant thin alias of extractCatalogComponentsFromTypeDocProject; either
document its intended distinct purpose with a TSDoc comment (e.g., "Entrypoint
for consuming TypeDoc --json output as a TypeDocProject") placed immediately
above extractCatalogComponentsFromTypeDocJson, or remove the wrapper and update
any callers/exports to use extractCatalogComponentsFromTypeDocProject directly;
locate and change the symbol extractCatalogComponentsFromTypeDocJson in this
module and any import sites so there are no dangling references.
- Around line 326-361: When building the JsonSchema in createComponentSchema,
remove noisy empty containers before returning: if schema.required is an empty
array, delete schema.required; if schema.properties is an empty object
(Object.keys(schema.properties || {}).length === 0), delete schema.properties.
Apply the same cleanup in the codepath that constructs nested/object schemas
(e.g., where parseTypeDocType produces object/Record schemas) so empty required:
[] and empty properties: {} are stripped across the board.

In `@packages/genui/a2ui-catalog-extractor/test/extractor.test.ts`:
- Around line 26-28: Tests create temporary directories using fs.mkdtempSync
(e.g., the fixtureDir variable and the other mkdtempSync call at line ~268) but
never remove them; add cleanup to avoid leaving a2ui-catalog-fixture-* and
a2ui-catalog-out-* in the OS temp dir by removing the created dirs after each
test (or in a try/finally) — call fs.rmSync(dir, { recursive: true, force: true
}) for fixtureDir and the output dir in an afterEach block (or immediately in
finally) so the temporary directories created by mkdtempSync are reliably
deleted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 897debc9-5de4-4b4b-95c0-27b27137cabd

📥 Commits

Reviewing files that changed from the base of the PR and between 67fd232 and 7f971b6.

📒 Files selected for processing (4)
  • packages/genui/a2ui-catalog-extractor/README.md
  • packages/genui/a2ui-catalog-extractor/src/index.ts
  • packages/genui/a2ui-catalog-extractor/test/extractor.test.ts
  • packages/genui/a2ui/package.json
✅ Files skipped from review due to trivial changes (1)
  • packages/genui/a2ui-catalog-extractor/README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/genui/a2ui/package.json

@PupilTong PupilTong force-pushed the codex/a2ui-typedoc-catalog-extractor branch from 7f971b6 to 19ae319 Compare April 27, 2026 08:25
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/genui/a2ui-catalog-extractor/test/extractor.test.ts (1)

26-28: Optional: clean up mkdtempSync directories after tests.

Both the fixture and out-dir tests create temp directories under os.tmpdir() but never remove them. Over many CI runs this slowly accumulates. Consider tracking the created paths and cleaning them up via afterAll/afterEach (using fs.rmSync(dir, { recursive: true, force: true })).

Also applies to: 266-288

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-catalog-extractor/test/extractor.test.ts` around lines 26
- 28, Tests create temporary directories using fs.mkdtempSync (e.g., the
fixtureDir variable and the other out-dir temp dirs) but never remove them;
update the test file to track created temp paths and remove them in an
appropriate teardown hook (use afterEach or afterAll) by calling fs.rmSync(dir,
{ recursive: true, force: true }) for each tracked directory so CI temp files
are cleaned up after tests complete.
packages/genui/a2ui-catalog-extractor/src/cli.ts (1)

173-176: Optional: include the stack on unexpected non-Error failures.

For end-user CLI output, printing error.message is the right default. However, if the runCli path throws something unusual (e.g., a TypeDoc-bootstrapped failure or a programmer error), losing the stack hampers debugging. Consider gating stack output behind an env var like DEBUG or A2UI_VERBOSE.

♻️ Suggested tweak
 } catch (error) {
-  console.error(error instanceof Error ? error.message : String(error));
+  if (error instanceof Error) {
+    console.error(process.env['A2UI_VERBOSE'] ? error.stack : error.message);
+  } else {
+    console.error(String(error));
+  }
   process.exitCode = 1;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/genui/a2ui-catalog-extractor/src/cli.ts` around lines 173 - 176,
Update the catch block that follows runCli to still print the short message to
stdout but, when an env var like DEBUG or A2UI_VERBOSE is set, also print the
full stack for debugging: keep the existing console.error(error instanceof Error
? error.message : String(error)); then if (process.env.DEBUG ||
process.env.A2UI_VERBOSE) log error instanceof Error ? error.stack : (error &&
(error as any).stack) ? String((error as any).stack) : String(error); finally
leave process.exitCode = 1 as-is so the CLI exits with failure.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/genui/a2ui-catalog-extractor/test/extractor.test.ts`:
- Around line 266-288: The test can fail if `@lynx-js/a2ui` isn't built because
readDistCatalogs() asserts expect(fs.existsSync(a2uiDistCatalogDir)).toBe(true);
update the root turbo.json task configuration for this test to add a dependency
on the a2ui package build (e.g., add "dependsOn": ["^a2ui#build"] or the
workspace-specific a2ui build task) so Turbo will build the a2ui package before
running the test; ensure the change targets the test task entry that runs
packages/genui/a2ui-catalog-extractor tests.

---

Nitpick comments:
In `@packages/genui/a2ui-catalog-extractor/src/cli.ts`:
- Around line 173-176: Update the catch block that follows runCli to still print
the short message to stdout but, when an env var like DEBUG or A2UI_VERBOSE is
set, also print the full stack for debugging: keep the existing
console.error(error instanceof Error ? error.message : String(error)); then if
(process.env.DEBUG || process.env.A2UI_VERBOSE) log error instanceof Error ?
error.stack : (error && (error as any).stack) ? String((error as any).stack) :
String(error); finally leave process.exitCode = 1 as-is so the CLI exits with
failure.

In `@packages/genui/a2ui-catalog-extractor/test/extractor.test.ts`:
- Around line 26-28: Tests create temporary directories using fs.mkdtempSync
(e.g., the fixtureDir variable and the other out-dir temp dirs) but never remove
them; update the test file to track created temp paths and remove them in an
appropriate teardown hook (use afterEach or afterAll) by calling fs.rmSync(dir,
{ recursive: true, force: true }) for each tracked directory so CI temp files
are cleaned up after tests complete.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: efa20530-5053-4cc3-a212-dd59fa775f9e

📥 Commits

Reviewing files that changed from the base of the PR and between 7f971b6 and 19ae319.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (28)
  • .github/a2ui-catalog.instructions.md
  • codecov.yml
  • packages/genui/a2ui-catalog-extractor/README.md
  • packages/genui/a2ui-catalog-extractor/bin/a2ui-catalog-extractor.js
  • packages/genui/a2ui-catalog-extractor/package.json
  • packages/genui/a2ui-catalog-extractor/rslib.config.ts
  • packages/genui/a2ui-catalog-extractor/rstest.config.ts
  • packages/genui/a2ui-catalog-extractor/skills/a2ui-catalog-extractor/SKILL.md
  • packages/genui/a2ui-catalog-extractor/src/cli.ts
  • packages/genui/a2ui-catalog-extractor/src/index.ts
  • packages/genui/a2ui-catalog-extractor/test/extractor.test.ts
  • packages/genui/a2ui-catalog-extractor/tsconfig.build.json
  • packages/genui/a2ui-catalog-extractor/tsconfig.json
  • packages/genui/a2ui/package.json
  • packages/genui/a2ui/src/catalog/Button/index.tsx
  • packages/genui/a2ui/src/catalog/Card/index.tsx
  • packages/genui/a2ui/src/catalog/CheckBox/index.tsx
  • packages/genui/a2ui/src/catalog/Column/index.tsx
  • packages/genui/a2ui/src/catalog/Divider/index.tsx
  • packages/genui/a2ui/src/catalog/Image/index.tsx
  • packages/genui/a2ui/src/catalog/List/index.tsx
  • packages/genui/a2ui/src/catalog/RadioGroup/index.tsx
  • packages/genui/a2ui/src/catalog/Row/index.tsx
  • packages/genui/a2ui/src/catalog/Text/index.tsx
  • packages/genui/a2ui/tools/catalog_generator.ts
  • packages/genui/a2ui/turbo.json
  • packages/genui/tsconfig.json
  • rstest.config.ts
💤 Files with no reviewable changes (2)
  • packages/genui/a2ui/turbo.json
  • packages/genui/a2ui/tools/catalog_generator.ts
✅ Files skipped from review due to trivial changes (17)
  • packages/genui/a2ui/src/catalog/CheckBox/index.tsx
  • packages/genui/a2ui/src/catalog/Button/index.tsx
  • packages/genui/a2ui-catalog-extractor/tsconfig.build.json
  • packages/genui/tsconfig.json
  • packages/genui/a2ui/src/catalog/RadioGroup/index.tsx
  • packages/genui/a2ui/src/catalog/Text/index.tsx
  • packages/genui/a2ui/src/catalog/Divider/index.tsx
  • packages/genui/a2ui/src/catalog/Card/index.tsx
  • packages/genui/a2ui-catalog-extractor/skills/a2ui-catalog-extractor/SKILL.md
  • packages/genui/a2ui-catalog-extractor/bin/a2ui-catalog-extractor.js
  • .github/a2ui-catalog.instructions.md
  • packages/genui/a2ui/src/catalog/List/index.tsx
  • packages/genui/a2ui-catalog-extractor/rstest.config.ts
  • packages/genui/a2ui-catalog-extractor/tsconfig.json
  • packages/genui/a2ui-catalog-extractor/README.md
  • packages/genui/a2ui/src/catalog/Row/index.tsx
  • packages/genui/a2ui-catalog-extractor/package.json
🚧 Files skipped from review as they are similar to previous changes (5)
  • rstest.config.ts
  • packages/genui/a2ui/src/catalog/Image/index.tsx
  • codecov.yml
  • packages/genui/a2ui-catalog-extractor/rslib.config.ts
  • packages/genui/a2ui/src/catalog/Column/index.tsx

Comment thread packages/genui/a2ui-catalog-extractor/test/extractor.test.ts Outdated
@PupilTong PupilTong merged commit 43353c6 into lynx-family:main Apr 27, 2026
76 of 81 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants