Astryx config + integration API#3280
Conversation
Delete the standalone gap-report command and its supporting helpers.
After a successful swizzle the CLI now prints a short maintainer
feedback note pointing at the issue tracker (and, when the gh CLI is
available, a ready-to-run 'gh issue create' command) instead of
filing issues directly.
The swizzle.copy JSON result drops the gapReport fields and adds an
optional feedback object { issuesUrl, ghCommand? }.
…ort (#3261) Replace the loose passthrough config and integration API with a locked, strict v1 surface. AstryxConfig is now { integrations?: string[]; issuesUrl?: string; hooks?: { postCodemod?: PostCodemodHook[] } } and validates with a strict Zod schema — unknown keys are hard errors. AstryxIntegration is now { components?; templates?; codemods?; issuesUrl? }, also strict. - New @astryxdesign/cli/integration subpath export with createIntegration; createConfig stays on @astryxdesign/cli/config. - Integrations resolve by package name only. Each package declares a single conventional root manifest (astryx.integration.{ts,mjs,js}) sibling to its package.json; identity (name/version) comes from package.json. .ts manifests load via jiti. - App config loads from astryx.config.{ts,mjs,js} sibling to the nearest package.json (no upward closest-config walk); multiple configs is an error, missing yields an empty config. - Post-codemod hooks now come from app config (hooks.postCodemod) and run via execFile; dry-run previews, apply executes in order and fails on nonzero exit. Clean breaking change — the prior config/integration API was not in real use.
Add createPageTemplate/createBlockTemplate authoring helpers exported from @astryxdesign/cli/template, validated with zod and tagging each doc with its page/block type. Add type-driven, package-scoped discovery for integration-provided templates: a template id is the path under the integration's templates root minus the .doc.* suffix, with a required same-stem <id>.tsx source. The doc's type decides scaffolding (no /pages vs /blocks requirement). The template command gains --package, ambiguity errors that list candidates, and list entries that always carry id/name/description/type/package. Built-in template discovery, command behavior, and tests are unchanged.
Add createCodemod/createConfigCodemod authoring helpers behind a new @astryxdesign/cli/codemod export, and consume an integration's resolved codemods directory during `astryx upgrade`. Integration codemods are discovered version-first (<codemodsRoot>/<version>/<id>.<ext>), validated strictly, and run alongside the core registry codemods ordered by version (config codemods first, then code codemods). Broken discovery or a throwing transform fails the upgrade.
Add 'astryx validate-integration' to validate one integration package at a time — its conventional manifest, declared contribution roots, and the codemod/template/component contributions — reporting findings with the AstryxIntegrationIssue model (code, severity, message). With no argument it validates the local package (nearest package.json + sibling manifest); with a package name it validates an installed package from node_modules. Supports --json via the integration.validate envelope and exits 1 when any error-severity issue is present.
…ack (#3267) Generalize `astryx swizzle` so it can copy integration-owned components, not just core. Component resolution now goes through package ownership (core + configured integrations); --package scopes the lookup and an ambiguous name across packages errors with the candidate list instead of silently preferring core. Escaping relative imports are rewritten to the OWNING package's subpaths (rewriteImports gains an optional ownerPackage param defaulting to core). The copy now also excludes *.doc.* files. Maintainer feedback is routed through config: core uses config.issuesUrl (falling back to the default), integration components use their manifest issuesUrl, and the note is omitted entirely when no issues URL is available. The swizzle.copy JSON envelope gains an owner `package` field.
…3274) Add a compact, non-blocking nudge: when a configured integration has validation issues, component, template, and upgrade print exactly one line per integration to stderr pointing at validate-integration, instead of silently skipping broken contributions. The nudge reuses the validate-integration validators via a new validateLoadedIntegration export (no validation logic duplicated) and a shared warnOnIntegrationIssues helper. It writes to stderr only (never corrupts --json envelopes), is suppressed in --json mode, never throws, and never changes the exit code.
…fig (#3275) Register app-local XLE components through the validated config under experimental.xle.components (object form: { from, description?, default? }) instead of the previous unvalidated layout.components read. The raw-read shim and its special cwd-first config lookup are removed; XLE now resolves the config via loadConfig, consistent with the rest of the CLI.
…tion loading (#3276) Config and integration loading each duplicated a jiti singleton + user-module import helper and a conventional-file-by-basename discovery filter. Extract both into packages/cli/src/lib/module-loader.mjs (importUserModule, findPresentFiles) and route both callers through it. No behavior change; all error messages and return shapes are preserved.
* feat(cli): add Project configuration API with pluggable cache Introduce the Project class as a single, lazy, memoized entry point for reading resolved project configuration and discovery (components, templates, codemods) plus integration issue routing. Add a small pluggable cache (InMemoryConfigCache) keyed by a config content hash + cwd + discovery kind. * refactor(cli): migrate all callers to Project and remove loadConfig Route every config/discovery read through Project.load: discover, doctor, layout, template, component, swizzle, and the component/template command wrappers. findConfigPath now lives in lib/project.mjs as the single home. Removes lib/config.mjs (loadConfig) with no shim. * feat(cli): skip broken integrations on upgrade and add --skip-codemod Upgrade now treats an integration definition error (bad manifest/export, duplicate ids, missing root) as skip-and-warn rather than a hard failure, while an execution-time codemod throw still aborts the run. Adds a variadic --skip-codemod flag to exclude named codemods (core transform name or integration codemod id) so users can re-run past a failed codemod.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
PR Analysis Report📚 Storybook PreviewView Storybook for this PR 🧪 Sandbox PreviewView Sandbox for this PR No new or modified components detected. Bundle Size Summary
Accessibility AuditStatus: No accessibility violations detected. Generated by PR Enrichment workflow | Storybook | Sandbox | View full report |
…nfig-surface codemod (#3286) Remove the legacy in-CLI config codemod (migrate-xds-config-surfaces) and its bespoke {config:{packageJson,astryxConfig,xdsConfig}} execution machinery. The @xds/* config rename is handled by a separate out-of-band migration, so this codemod is redundant for the remaining holdouts — they reach 0.1.x with configs already renamed. Extract the modern (file, api)/jscodeshift config and code runners into a shared run-codemod.mjs used by both the core registry runner and the integration runner, so there is a single implementation. Core registry config codemods now route through the same path: a core entry signals config via meta.codemodType === 'config'.
….xle.components (#3290) The published 0.1.2 CLI read XLE app-component registration from astryx.config.* under layout.components. The next release relocates this to experimental.xle.components, validated by a strict schema that rejects unknown keys, so consumers with layout.components would hard-error on upgrade. This adds a v0.1.3 config codemod that performs the straight relocation. Semantics are identical between the old and new shapes; this is purely a move plus a string to object normalization: - string 'X' becomes { from: 'X' } (named import, key = export name) - object { from, description?, default? } is carried over unchanged The transform recognizes the default export as either a bare object literal or a createConfig({ ... }) wrapper, and bails with a clear migrate-manually error when the config cannot be statically analyzed, when experimental.xle already exists, when layout has keys other than components, or when an entry is neither a string nor an object literal.
…ndary (#3293) Add a single shared loadModuleWithSchema(file, schema, {label}) that imports a user-authored module, takes its default export, and validates it against a zod schema. Route all four user-module loaders (config, integration, codemod, template) through it so loading and validation are uniform and happen at the load/discovery boundary. The create* factories (createConfig, createIntegration, createCodemod, createConfigCodemod, createPageTemplate, createBlockTemplate) become pure typed identity helpers: they stamp the type discriminator where applicable and return the definition unchanged, performing no runtime validation. Their value is the exported TypeScript surface for editor DX. Define the metadata envelope schemas the loader validates: AstryxConfigSchema and AstryxIntegrationSchema (existing), CodemodEnvelopeSchema (a discriminated union over the stamped type), and TemplateEnvelopeSchema (BaseTemplateSchema + type). The bespoke 'must default-export a createCodemod result' structural check is removed in favor of schema validation, so a plain object matching the envelope is accepted regardless of how it was produced. Config strictness at the load boundary is preserved (unknown keys still rejected). The built-in core .doc.mjs / template.doc.mjs loader and convention are untouched; only the integration template path moves to loadModuleWithSchema.
…y-run guidance) (#3295) upgrade loaded astryx.config strictly before running any codemods, so a consumer whose config still had the legacy layout.components key — exactly what the v0.1.3 config codemod migrate-layout-components-to-experimental repairs — got rejected at load and the codemod that would fix it never ran. Reorder the pipeline so core codemods (config + code) run before Project.load. In --apply the config codemod writes the repaired config to disk, then the strict load succeeds. In dry-run, a config that fails validation but is fixable by a pending core config codemod no longer aborts: it reports the fixable error and the exact --codemod ... --apply command to repair it, then finishes the dry-run cleanly (integrations are processed on the --apply run). Strictness is unchanged; a genuine config error or a throwing transform still aborts.
cixzhang
left a comment
There was a problem hiding this comment.
Solid, well-structured PR — the config/integration schemas, new subpath exports, and Project API all read cleanly and the CLI package's own test suite is green. One blocker before this can land, though: the smoke-test check is failing for real (all three branch runs, not a flake), and the cause is this PR's own contract change.
Root cause (reproduced locally: PASS 155 / FAIL 151)
This PR intentionally changes the component --list JSON contract from bare strings to package-qualified objects:
- data: components // ["AppShell", "Button", ...]
+ data: listData // { AppShell: [{ name: "AppShell", package: "@astryxdesign/core" }], ... }That's a reasonable pre-1.0 change and the CLI vitest suite was updated for it. But the repo-level parity harness .github/scripts/api-cli-parity-test.mjs was not updated — it still does:
Object.values(componentList.data).flat() // now yields {name, package} OBJECTS, not strings…then feeds each object in as a component name, so every case runs as component [object Object]. The CLI degrades gracefully (No component named "[object Object]") but the API throws name.replace is not a function, and the resulting mismatch fails all 151 component cases.
Requested change
Since this PR changed the list contract, please update the parity harness in the same PR so the shape stays in sync — e.g.:
const allComponents = componentList.data && !componentList.error
? Object.values(componentList.data).flat().map(c => (typeof c === 'string' ? c : c.name))
: [];Optional (non-blocking)
The API's component(name) throws name.replace is not a function on a non-string name, while the CLI returns a clean error. Only reachable via the broken harness today, but a small type guard would make the two paths degrade identically.
Note on the other red check
dependency-check also shows red, but that looks like infra flakiness rather than a code issue: it passed on two earlier runs of this same branch, and the PR adds no new dependencies (only subpath exports). A re-run should clear it — no change needed on your end.
Summary
Brings the Astryx config + integration API work (developed on
xds-unprefix-integration) tomain. This is a cohesive set of CLI changes establishing a strict, validated configuration surface, a first-class integration system, file-based codemods and static templates, plus the internal architecture to support them.mainis merged in, so this includes the0.1.2release and the Shell page templates.Scope note: this branch grew since it was opened — it now carries 17 feature PRs (98 files, ~+7500/−2900). The list below is the full, current scope.
Config + integration core
AstryxConfig/AstryxIntegrationschemas (unknown keys rejected); new@astryxdesign/cli/integrationexport withcreateIntegration. (feat(cli): strict config + integration v1 schema and /integration export #3261)astryx.integration.{ts,mjs,js}manifest; identity frompackage.json.package.json.Projectconfiguration API — the single entry point for reading resolved config, components, templates, codemods, and issue routing (replaces the oldloadConfig), with a pluggable cache. (feat(cli): Project configuration API (replaces loadConfig) #3277)Authoring APIs (new subpath exports)
@astryxdesign/cli/codemod—createCodemod/createConfigCodemod+ file-based integration codemod discovery inupgrade. (feat(cli): file-based codemod API and /codemod export #3264)@astryxdesign/cli/template—createPageTemplate/createBlockTemplatewith type-driven, package-scoped template discovery. (feat(cli): static template API and /template export #3263)Commands
componentis package-ownership aware (--package, source resolution, package-qualified listings). (feat(cli): component package ownership and source discovery #3265)swizzlecopies integration-owned components, rewrites escaping imports to the owning package, and routes maintainer feedback through config / integration issue URLs. (feat(cli): swizzle integration-owned components + config-routed feedback #3267)validate-integration— new command + issue model for checking an integration package. (feat(cli): validate-integration command #3266)upgrade— skips misconfigured integrations with a warning instead of hard-failing;--skip-codemodflag; runs core codemods before loading config so a config codemod can repair an otherwise-invalid config, with dry-run guidance that surfaces the exact command to apply the fix. (feat(cli): Project configuration API (replaces loadConfig) #3277, fix(cli): run core codemods before loading config in upgrade (with dry-run guidance) #3295)gap-reportcommand;swizzleprints a short feedback link instead. (feat(cli): remove gap-report; swizzle prints feedback note #3255)Config codemods
(file, api)runner used by integration codemods; removed the obsolete xds config-surface codemod (the fleet's@xds/*→@astryxdesign/*config rename is handled out-of-band). (refactor(cli): unify config codemod runner and remove obsolete xds config-surface codemod #3286)astryx.configlayout.components→experimental.xle.components. (feat(cli): v0.1.3 codemod migrating layout.components to experimental.xle.components #3290)Architecture / internals
module-loaderutil for config / integration / codemod / template loading — one way to load user-authored modules: import, take the default export, validate against a schema at the load boundary.create*factories are now type-only (validation happens at load). (refactor(cli): extract shared module-loader util for config + integration loading #3276, refactor(cli): unified module loading with validation at the load boundary #3293)experimental.xle.components. (feat(cli): move XLE app components to experimental.xle.components config #3275)component/template/upgradeprint a one-line, non-blocking warning when a configured integration has validation issues. (feat(cli): warn when a configured integration has validation issues #3274)Validation
Full CLI test suite green (1621 tests),
pnpm build+verify-exportsclean across all packages, typecheck and changeset gates pass.mainwas merged into the branch conflict-free and the merged result re-validated end to end (including the docsite XLE-playground removal frommain, which is orthogonal to the CLI XLE work here).Notes for reviewers
.github/workflows/ci.ymlis identical tomain.