Skip to content

fix(cli,themes): verify the icon import theme build emits - #4625

Open
AKnassa wants to merge 7 commits into
facebook:mainfrom
AKnassa:rocky/issue-4620-theme-build-icons
Open

fix(cli,themes): verify the icon import theme build emits#4625
AKnassa wants to merge 7 commits into
facebook:mainfrom
AKnassa:rocky/issue-4620-theme-build-icons

Conversation

@AKnassa

@AKnassa AKnassa commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

What this does

astryx theme build copies the icon-registry import from the theme source straight into the module it generates, and nothing checked that the copied specifier means anything from where the generated file lives. The build now verifies it before writing: the specifier is rewritten onto the compiled companion when one exists, kept with a warning when only the TypeScript source sits beside the output, and the build fails with a clear error when nothing can satisfy it.

One PR for two issues on purpose: both defects are the same emit step. #4620 is the symptom in the published themes (extensionless ./icons, so /built cannot load in Node or externalized SSR). #4621 is the symptom for self-built themes (the import points at a module the build never produces). A resolve check is the union of the two fixes, and either half alone would still have to decide the other's case.

What changed

  • packages/cli/api/theme/build/build.mjs resolves the emitted specifier against the out dir before any file is written. Preference order is .mjs, then .js, then .cjs. .mjs wins deliberately: with a dual tsup build, .js is the CJS twin and hands consumers a second, distinct registry instance. A registry that exists only as TypeScript source beside the output is kept as written with a warning, since in-place builds are consumed through bundlers that resolve it. When nothing satisfies the import the build throws ERR_THEME_ICON_UNRESOLVED before writing anything, matching the existing stage-then-commit design. Bare package specifiers pass through untouched.
  • Companion checks require the exact on-disk name. A case-insensitive filesystem (the macOS default) would otherwise let a mac build emit a specifier that fails on the Linux systems the artifact ships to.
  • All seven theme packages now run tsup before astryx theme build, so the compiled registry exists when the specifier is verified, and check-fully-specified.mjs gates their dist the way it already gates core, lab, charts and richtext.
  • The in-place warning also lands in the API receipt's warnings array, since the shared logger is silent for programmatic callers.
  • 21 tests: the resolve ladder (parent-relative and nested specifiers, .cjs fallback, a directory named icons, fully-specified-but-missing, ladder order, exact case), plus e2e for the CLI error and exit code, the --json envelope code, watch-mode surviving the failure and recovering on the next change, and a spawned-Node proof that the emitted module actually loads.

How to check

The published-theme shape (was ERR_MODULE_NOT_FOUND before):

pnpm -F @astryxdesign/theme-neutral build
node --input-type=module -e "const m = await import('./packages/themes/neutral/dist/neutral.js'); console.log(m.neutralTheme.name, Object.keys(m.neutralIconRegistry).length)"
# neutral 26

The self-built shape (was a silently broken module before, now a clear failure):

astryx theme build src/myTheme.ts -o dist/theme.css
# Error: The generated theme module imports './icons', but no module satisfies it in dist/.
#        ... compile the icon registry into dist/ (tsup, esbuild, tsc) before running `theme build`,
#        or remove the theme's `icons` field.

Not included

The packaging defects the issues list as contributing factors (no type field in the theme packages, ./built mapping require to the ESM file) are left alone. With the specifier fixed the entry loads under both module systems on current Node, and flipping type would change how the existing CJS dist/source.js parses.

Fixes #4620
Fixes #4621

AKnassa added 4 commits July 31, 2026 21:55
The icon-registry specifier is scraped from the theme source (where
jiti resolves extensionless './icons') and was emitted verbatim into
a module Node ESM loads — where it resolves nothing. Verify it
against the out dir before any write: rewrite onto the compiled
companion (preferring .mjs — the .js twin of a dual tsup build is a
second registry instance), keep a source-only sibling with a warning
(in-place builds consumed through bundlers), and fail loudly with
ERR_THEME_ICON_UNRESOLVED when nothing satisfies the import.

Fixes facebook#4620
Fixes facebook#4621
Run tsup before astryx theme build so the compiled registry exists
when the emitted specifier is verified — the generated module now
imports './icons.mjs' and the /built entry loads under Node ESM and
externalized SSR. tsup owns dist cleaning now that it runs first,
and check-fully-specified.mjs gates every theme's dist the way it
already gates core/lab/charts/richtext.

Refs facebook#4620
The in-place-build warning went only through the shared logger,
which is silent for programmatic callers — mirror it into the
receipt's warnings array like the validation warnings.

Refs facebook#4620
A case-insensitive filesystem (macOS default) let statSync match
icons.mjs onto ICONS.mjs, so a mac build could emit a specifier that
fails on the case-sensitive systems the artifact ships to. Only the
exact on-disk name satisfies the import now (readdir check).

Edge battery: parent-relative and nested specifiers rewrite onto
their companions, .cjs-only fallback, a directory named icons is not
a module, fully-specified-but-missing fails, the default out path
(beside the source) takes the warn path, compiled beats TS-sibling
(ladder order, mutation-verified), --json envelope carries
ERR_THEME_ICON_UNRESOLVED, and --watch survives the throw and
recovers on the next change.

Refs facebook#4620
Refs facebook#4621
@vercel

vercel Bot commented Aug 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
astryx Ready Ready Preview Aug 3, 2026 5:36pm

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Aug 1, 2026
@github-actions github-actions Bot added community Authored by a community contributor (not on the eng/design team) needs:code-review High-risk change (new package/component/API) — needs human code review before merge labels Aug 1, 2026
@AKnassa
AKnassa marked this pull request as ready for review August 1, 2026 02:18
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

🧪 Sandbox Preview

View Sandbox for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

No new or modified components detected.

Bundle Size Summary

No component packages changed.

Accessibility Audit

Status: No accessibility violations detected.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

github-actions Bot added a commit that referenced this pull request Aug 1, 2026
Upstream restructured packages/cli (lib/ -> foundation/response/, cli/ ->
clients/cli/). Resolved by moving build-theme.icons.test.mjs to
clients/cli/commands/ and updating its two relative paths (run-cli helper,
CLI bin). ERR_THEME_ICON_UNRESOLVED merged into foundation/response/
error-codes.mjs via rename detection.

@cixzhang cixzhang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good from the implementation side — the resolver/checking approach makes sense and the focused theme smoke passes.

Before merge, could you please clean up two things:

  • Rebase/resolve the current merge conflicts against main.
  • Trim the changesets down to changelog-sized summaries. They currently read more like PR-body/root-cause notes; the changelog only needs the consumer-visible effect.

Thanks for the thorough test coverage here.

AKnassa added 2 commits August 3, 2026 13:19
Resolves the build.mjs file-header conflict: upstream's `--check` mode
paragraph (facebook#4565) and this branch's icon-resolve paragraph both land.
Review feedback: the changelog only needs what changed for consumers, not
the root cause. The resolve-order reasoning, the TypeScript-source warning
case, and the theme build-order/guard wiring stay in the PR body.
@github-actions github-actions Bot removed the needs:code-review High-risk change (new package/component/API) — needs human code review before merge label Aug 3, 2026
github-actions Bot added a commit that referenced this pull request Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot. community Authored by a community contributor (not on the eng/design team)

Projects

None yet

2 participants