@astryxdesign/cli 0.3.0-canary.d1b7d82 → 0.3.0-canary.dccdabe

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/README.md +8 -8
  2. package/api/blog/blog.doc.mjs +2 -2
  3. package/api/build/build.doc.mjs +1 -1
  4. package/api/component/component.doc.mjs +1 -1
  5. package/api/discover/discover.doc.mjs +2 -2
  6. package/api/docs/docs.doc.mjs +2 -2
  7. package/api/doctor/doctor.doc.mjs +3 -3
  8. package/api/hook/hook.doc.mjs +1 -1
  9. package/api/init/init.doc.mjs +1 -1
  10. package/api/integration/summarizeIssues.doc.mjs +1 -1
  11. package/api/integration/validateIntegration.doc.mjs +1 -1
  12. package/api/json/assertResponse.doc.mjs +2 -2
  13. package/api/json/isError.doc.mjs +1 -1
  14. package/api/json/parseResponse.doc.mjs +2 -2
  15. package/api/layout/layoutCheck.doc.mjs +1 -1
  16. package/api/layout/layoutExpand.doc.mjs +1 -1
  17. package/api/layout/layoutGrammar.doc.mjs +1 -1
  18. package/api/swizzle/swizzle.doc.mjs +1 -1
  19. package/api/template/template.test.mjs +12 -13
  20. package/api/theme/listThemes.doc.mjs +2 -2
  21. package/api/theme/themeAdd.doc.mjs +1 -1
  22. package/api/theme/themeBuild.doc.mjs +3 -3
  23. package/api/theme/themeList.doc.mjs +2 -2
  24. package/api/upgrade/upgrade.doc.mjs +1 -1
  25. package/assets/codemods/transforms/v0.3.0/__tests__/migrate-grid-minchildwidth-to-columns.test.mjs +82 -0
  26. package/assets/codemods/transforms/v0.3.0/migrate-grid-minchildwidth-to-columns.mjs +38 -2
  27. package/assets/docs/browser-support.doc.mjs +6 -6
  28. package/assets/docs/icons.doc.mjs +3 -3
  29. package/assets/docs/illustrations.doc.mjs +2 -2
  30. package/assets/docs/internationalization.doc.mjs +1 -1
  31. package/assets/docs/layout.doc.dense.mjs +1 -1
  32. package/assets/docs/migration.doc.mjs +1 -1
  33. package/assets/docs/principles.doc.mjs +1 -1
  34. package/assets/docs/styling-libraries.doc.mjs +1 -1
  35. package/assets/docs/styling.doc.mjs +4 -4
  36. package/assets/docs/theme.doc.mjs +3 -3
  37. package/assets/docs/typography.doc.mjs +2 -2
  38. package/assets/templates/blocks/components/AspectRatio/AspectRatioCircleImage.tsx +1 -1
  39. package/assets/templates/blocks/components/AspectRatio/AspectRatioImageGallery.tsx +1 -1
  40. package/assets/templates/blocks/components/AspectRatio/AspectRatioShowcase.tsx +3 -3
  41. package/assets/templates/blocks/components/AspectRatio/AspectRatioSquareImage.tsx +1 -1
  42. package/assets/templates/blocks/components/AspectRatio/AspectRatioWidescreen.tsx +1 -1
  43. package/assets/templates/blocks/components/Avatar/AvatarFallbackChain.tsx +6 -6
  44. package/assets/templates/blocks/components/Avatar/AvatarGroup.tsx +5 -5
  45. package/assets/templates/blocks/components/Avatar/AvatarInteractive.tsx +2 -2
  46. package/assets/templates/blocks/components/Avatar/AvatarShowcase.tsx +4 -4
  47. package/assets/templates/blocks/components/Avatar/AvatarTooltip.tsx +4 -4
  48. package/assets/templates/blocks/components/Avatar/AvatarUserCard.tsx +3 -3
  49. package/assets/templates/blocks/components/Avatar/AvatarWithImage.tsx +4 -4
  50. package/assets/templates/blocks/components/Avatar/AvatarWithStatus.tsx +3 -3
  51. package/assets/templates/blocks/components/ChatComposerDrawer/ChatComposerDrawerAttachments.tsx +5 -5
  52. package/assets/templates/blocks/components/Lightbox/LightboxGallery.tsx +4 -4
  53. package/assets/templates/blocks/components/Lightbox/LightboxShowcase.tsx +1 -1
  54. package/assets/templates/blocks/components/Lightbox/LightboxVideo.tsx +1 -1
  55. package/assets/templates/blocks/components/Lightbox/LightboxZoom.tsx +2 -2
  56. package/assets/templates/blocks/components/MediaTheme/MediaThemeImageOverlay.tsx +1 -1
  57. package/assets/templates/blocks/components/MediaTheme/MediaThemeLightScrim.tsx +1 -1
  58. package/assets/templates/blocks/components/MediaTheme/MediaThemeShowcase.tsx +1 -1
  59. package/assets/templates/blocks/components/Overlay/OverlayBottomStrip.tsx +1 -1
  60. package/assets/templates/blocks/components/Overlay/OverlayHoverReveal.tsx +1 -1
  61. package/assets/templates/blocks/components/Overlay/OverlayShowcase.tsx +1 -1
  62. package/assets/templates/blocks/components/TopNav/TopNavMegaMenu.tsx +1 -1
  63. package/assets/templates/pages/centered-hero/page.tsx +1 -1
  64. package/assets/templates/pages/classic-gallery/page.tsx +10 -10
  65. package/assets/templates/pages/detail-page/page.tsx +5 -5
  66. package/assets/templates/pages/form-two-column/page.tsx +1 -1
  67. package/assets/templates/pages/gallery-hero/page.tsx +3 -3
  68. package/assets/templates/pages/library/page.tsx +30 -30
  69. package/assets/templates/pages/login/page.tsx +1 -2
  70. package/assets/templates/pages/login-card/page.tsx +1 -2
  71. package/assets/templates/pages/login-split/page.tsx +5 -6
  72. package/assets/templates/pages/login-sso/page.tsx +2 -3
  73. package/assets/templates/pages/mixed-gallery/page.tsx +5 -5
  74. package/assets/templates/pages/payment-form/page.tsx +3 -3
  75. package/assets/templates/pages/product-detail/page.tsx +7 -7
  76. package/assets/templates/pages/product-gallery/page.tsx +6 -6
  77. package/assets/templates/pages/shell-top-nav/page.tsx +2 -2
  78. package/assets/templates/pages/side-gallery/page.tsx +9 -9
  79. package/assets/templates/pages/table-page-chart/page.tsx +6 -6
  80. package/assets/templates/pages/theme-showcase/page.tsx +6 -6
  81. package/authoring/codemod/codemod.doc.mjs +2 -2
  82. package/authoring/config/config.doc.mjs +1 -1
  83. package/authoring/doctypes/base/type.ts +6 -0
  84. package/authoring/doctypes/command/command.doc.mjs +2 -2
  85. package/authoring/doctypes/component/component.doc.mjs +7 -7
  86. package/authoring/doctypes/enum/enum.doc.mjs +2 -2
  87. package/authoring/doctypes/function/function.doc.mjs +3 -3
  88. package/authoring/doctypes/hook/hook.doc.mjs +3 -3
  89. package/authoring/doctypes/reference/reference.doc.mjs +3 -3
  90. package/authoring/doctypes/schema/schema.doc.mjs +1 -1
  91. package/authoring/doctypes/template/template.doc.mjs +3 -3
  92. package/clients/cli/commands/build.doc.mjs +1 -1
  93. package/clients/cli/commands/component.doc.mjs +1 -1
  94. package/clients/cli/commands/doctor.doc.mjs +2 -2
  95. package/clients/cli/commands/layout-expand.doc.mjs +1 -1
  96. package/clients/cli/commands/layout.doc.mjs +1 -1
  97. package/clients/cli/commands/manifest.doc.mjs +3 -3
  98. package/clients/cli/commands/search.doc.mjs +1 -1
  99. package/clients/cli/commands/theme-add.doc.mjs +1 -1
  100. package/clients/cli/commands/theme-build.doc.mjs +1 -1
  101. package/clients/cli/commands/theme-list.doc.mjs +1 -1
  102. package/clients/cli/commands/theme.doc.mjs +2 -2
  103. package/clients/cli/commands/validate-integration.doc.mjs +2 -2
  104. package/foundation/agent-docs/agent-docs.mjs +3 -4
  105. package/foundation/agent-docs/agent-docs.test.mjs +8 -0
  106. package/foundation/discovery/template-adapter.mjs +8 -4
  107. package/foundation/response/response-types.doc.mjs +7 -7
  108. package/foundation/response/response.doc.mjs +5 -5
  109. package/package.json +9 -9
package/README.md CHANGED
@@ -72,7 +72,7 @@ Options:
72
72
  | `search` | Search components, hooks, docs, and templates in one ranked list |
73
73
  | `swizzle` | Copy component source for customization |
74
74
  | `template` | Inject a page or block template |
75
- | `theme` | Theme tools build, export, and manage themes |
75
+ | `theme` | Theme tools: build, export, and manage themes |
76
76
  | `upgrade` | Run codemods to migrate between versions |
77
77
  | `validate-integration` | Validate an Astryx integration package (manifest + contributions) |
78
78
 
@@ -385,7 +385,7 @@ Every response has a `type` discriminant. The full set is below (generated from
385
385
 
386
386
  | Type | What `data` carries |
387
387
  | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
388
- | `component.list` | The component catalog grouped by category: `detail` (the level names \| compact \| full) and `components`, the grouped map of names+package, brief entries, or a full ComponentDoc per entry. |
388
+ | `component.list` | The component catalog grouped by category: `detail` (the level: names \| compact \| full) and `components`, the grouped map of names+package, brief entries, or a full ComponentDoc per entry. |
389
389
  | `component.detail` | One component's authored ComponentDoc plus ownership metadata (owner package, import specifier, and whether source is available). |
390
390
  | `component.detail.props` | Just one component's props table (ComponentPropDoc[]). |
391
391
  | `component.detail.source` | One component's source file, as {component, source}. |
@@ -393,12 +393,12 @@ Every response has a `type` discriminant. The full set is below (generated from
393
393
  | `component.detail.blocks` | One component's example blocks, as {component, showcase, examples, related} of BlockEntry. |
394
394
  | `docs.list` | All reference-doc topics as DocsListEntry[] ({topic, description}), in discovery order. |
395
395
  | `docs.detail` | One topic's full ReferenceDoc, with token-ref blocks inlined. |
396
- | `docs.detail.section` | A single ReferenceSection of a topic the first whose title contains the section query. |
397
- | `blog.list` | The feed URL plus every post parsed from the RSS feed each with slug, title, description, date, type, authors, link, and plaintext URL. |
396
+ | `docs.detail.section` | A single ReferenceSection of a topic: the first whose title contains the section query. |
397
+ | `blog.list` | The feed URL plus every post parsed from the RSS feed, each with slug, title, description, date, type, authors, link, and plaintext URL. |
398
398
  | `blog.detail` | One post's metadata plus the feed URL and the post's full plaintext body. |
399
399
  | `discover.list` | The configured external packages (name, category, components, version, description); when empty it carries meta.configured to tell "nothing configured" from "nothing discovered". |
400
400
  | `discover.detail` | A single external package entry, for an @scope/name query. |
401
- | `discover.detail.doc` | The validated ComponentDoc for one external component an @scope/name/Component query, or a free-text term resolving to exactly one component. |
401
+ | `discover.detail.doc` | The validated ComponentDoc for one external component: an @scope/name/Component query, or a free-text term resolving to exactly one component. |
402
402
  | `discover.search` | The echoed query plus the matching {package, component} pairs, when a free-text term matches several components. |
403
403
  | `search` | The echoed query plus a ranked SearchResultEntry[] (domain, name, score, reason, description, follow-up command, and import path where relevant). |
404
404
  | `build.help` | A marker (`playbook: true`) that the renderer expands into the how-to-build-a-page workflow; emitted when no query is given. |
@@ -409,15 +409,15 @@ Every response has a `type` discriminant. The full set is below (generated from
409
409
  | `template.show` | The resolved template's raw source plus its description, kind, and the component names it composes. |
410
410
  | `template.skeleton` | A layout skeleton (structural tags with spatial annotations) plus the template's description and the components it composes. |
411
411
  | `template.copy` | A scaffold receipt: template id, output directory, written file name, and file count. |
412
- | `hook.list` | The hook catalog grouped by category: `detail` (the level names \| compact \| full) and `components`, the grouped map of hook names, brief entries, or a full HookDoc per entry. |
412
+ | `hook.list` | The hook catalog grouped by category: `detail` (the level: names \| compact \| full) and `components`, the grouped map of hook names, brief entries, or a full HookDoc per entry. |
413
413
  | `hook.detail` | One hook's full authored HookDoc. |
414
414
  | `hook.detail.params` | Just one hook's parameters table (HookParamDoc[]). |
415
415
  | `theme.build` | A theme build receipt: name, token- and component-override counts, output size, the written outputs {css, js, dts, and variantsDts when applicable}, and any validation warnings. |
416
416
  | `theme.build.check` | The --check receipt: theme name, an upToDate flag, the stale outputs (each {path, reason: missing \| outdated}), and the full list of checked paths. Writes nothing. |
417
- | `theme.list` | Every bundled theme as a ThemeListEntry[] each with slug, displayName, description, and a maintained flag. |
417
+ | `theme.list` | Every bundled theme as a ThemeListEntry[]: each with slug, displayName, description, and a maintained flag. |
418
418
  | `theme.add` | A scaffold receipt: resolved slug, displayName, maintained flag, outputDir (relative to cwd), the theme entry file, its exportName, and the files written. |
419
419
  | `upgrade.list` | Every available codemod, oldest→newest, as {name, title, version, optional}; returned for --list without running anything. |
420
- | `upgrade.status` | A short-circuit outcome with no codemods run up_to_date, no_codemods, or config_fixable each carrying the agent-docs summary. |
420
+ | `upgrade.status` | A short-circuit outcome with no codemods run (up_to_date, no_codemods, or config_fixable), each carrying the agent-docs summary. |
421
421
  | `upgrade.run` | The run receipt: from/to versions, codemod count, integrations processed, the agent-docs summary, and (apply mode) filesChanged, transformsApplied, and per-codemod errors. |
422
422
  | `manifest` | The self-describing CLI capability manifest: name, version, apiVersion, global options, the command tree (args, options, json flag, response types, examples), the jsonSupported allowlist, and the flat responseTypes index. |
423
423
  | `doctor` | The health-check report: `checks` (each with id, label, status: pass \| warn \| fail \| info, a message, and a fix when not passing) plus a `summary` of counts per status. |
@@ -14,7 +14,7 @@ export const doc = {
14
14
  displayName: 'blog()',
15
15
  summary: 'List blog posts, or read one, from the published RSS feed.',
16
16
  description:
17
- 'Reads the design system blog the same way any feed reader does over the ' +
17
+ 'Reads the design system blog the same way any feed reader does, over the ' +
18
18
  "published RSS feed, never the blog's source files. With no slug it lists every " +
19
19
  "post parsed from the feed; with a slug it reads that post's full plaintext body " +
20
20
  'via the .txt alternate the feed advertises. Both envelopes carry feedUrl so a ' +
@@ -35,7 +35,7 @@ export const doc = {
35
35
  {
36
36
  type: 'blog.list',
37
37
  description:
38
- 'The feed URL plus every post parsed from the feed each with slug, title, description, date, type, authors, link, and its plaintext URL.',
38
+ 'The feed URL plus every post parsed from the feed, each with slug, title, description, date, type, authors, link, and its plaintext URL.',
39
39
  },
40
40
  {
41
41
  type: 'blog.detail',
@@ -54,7 +54,7 @@ export const doc = {
54
54
  {
55
55
  type: 'build.help',
56
56
  description:
57
- 'Emitted when the query is omitted a pure marker (`data.playbook: true`) that the command renderer expands into the page-building workflow prose.',
57
+ 'Emitted when the query is omitted: a pure marker (`data.playbook: true`) that the command renderer expands into the page-building workflow prose.',
58
58
  },
59
59
  {
60
60
  type: 'build.kit',
@@ -108,7 +108,7 @@ export const doc = {
108
108
  {
109
109
  type: 'component.list',
110
110
  description:
111
- "The catalog grouped by category. data.detail is the level ('names' | 'compact' | 'full') and data.components is the grouped map names+package, brief entries, or full ComponentDoc per entry.",
111
+ "The catalog grouped by category. data.detail is the level ('names' | 'compact' | 'full') and data.components is the grouped map: names+package, brief entries, or full ComponentDoc per entry.",
112
112
  },
113
113
  {
114
114
  type: 'component.detail',
@@ -15,7 +15,7 @@ export const doc = {
15
15
  summary: 'Browse and search components from configured external packages.',
16
16
  description:
17
17
  'Explores components contributed by configured external packages and integrations ' +
18
- 'the ones that declare a components root. With no query it lists those packages; ' +
18
+ 'the ones that declare a components root. With no query it lists those packages; ' +
19
19
  'an @scope/name query browses one package; @scope/name/Component (or a free-text ' +
20
20
  "term that resolves to a single component) returns that component's validated doc; " +
21
21
  'a free-text term with several matches returns the candidate list.',
@@ -68,7 +68,7 @@ export const doc = {
68
68
  {
69
69
  type: 'discover.detail.doc',
70
70
  description:
71
- 'The validated ComponentDoc for one external component an @scope/name/Component query, or a free-text term that resolves to exactly one component.',
71
+ 'The validated ComponentDoc for one external component: an @scope/name/Component query, or a free-text term that resolves to exactly one component.',
72
72
  },
73
73
  {
74
74
  type: 'discover.search',
@@ -13,7 +13,7 @@ export const doc = {
13
13
  name: 'docs',
14
14
  displayName: 'docs()',
15
15
  summary:
16
- 'Read the reference docs list every topic, one topic, or a single section of a topic.',
16
+ 'Read the reference docs: list every topic, one topic, or a single section of a topic.',
17
17
  description:
18
18
  'Routes on its arguments: no topic lists every reference-doc topic; a topic ' +
19
19
  'returns that full ReferenceDoc (with token-ref blocks inlined); a topic ' +
@@ -75,7 +75,7 @@ export const doc = {
75
75
  {
76
76
  type: 'docs.detail.section',
77
77
  description:
78
- 'A single ReferenceSection of the topic the first whose title contains the section query.',
78
+ 'A single ReferenceSection of the topic: the first whose title contains the section query.',
79
79
  },
80
80
  ],
81
81
  throws: [
@@ -14,10 +14,10 @@ export const doc = {
14
14
  displayName: 'doctor()',
15
15
  summary: 'Read-only project + environment health check.',
16
16
  description:
17
- 'Runs a series of side-effect-free diagnostics Node version, ' +
17
+ 'Runs a series of side-effect-free diagnostics: Node version, ' +
18
18
  '@astryxdesign/core install and version alignment with the CLI, installed ' +
19
19
  'themes and wiring, astryx.config validity, agent docs, core peer ' +
20
- 'dependencies, and the detected package manager and returns a structured ' +
20
+ 'dependencies, and the detected package manager, and returns a structured ' +
21
21
  'report. It only reads (never installs, writes, or mutates), so it is safe ' +
22
22
  'as a CI gate and for agents to invoke.',
23
23
  importPath: '@astryxdesign/cli/api',
@@ -34,7 +34,7 @@ export const doc = {
34
34
  {
35
35
  type: 'doctor',
36
36
  description:
37
- 'The diagnostic report: `data.checks` each with a stable id, label, `status` (`pass` | `warn` | `fail` | `info`), a one-line message, and a `fix` when the status is not `pass` plus `data.summary` with counts per status.',
37
+ 'The diagnostic report: `data.checks`, each with a stable id, label, `status` (`pass` | `warn` | `fail` | `info`), a one-line message, and a `fix` when the status is not `pass`; plus `data.summary` with counts per status.',
38
38
  },
39
39
  ],
40
40
  examples: [
@@ -70,7 +70,7 @@ export const doc = {
70
70
  {
71
71
  type: 'hook.list',
72
72
  description:
73
- "The catalog grouped by category. data.detail is the level ('names' | 'compact' | 'full') and data.components is the grouped map hook names, brief entries, or full HookDoc per entry.",
73
+ "The catalog grouped by category. data.detail is the level ('names' | 'compact' | 'full') and data.components is the grouped map: hook names, brief entries, or full HookDoc per entry.",
74
74
  },
75
75
  {
76
76
  type: 'hook.detail',
@@ -69,7 +69,7 @@ export const doc = {
69
69
  {
70
70
  type: 'init.remove',
71
71
  description:
72
- 'Confirmation that the managed agent-docs block was removed (`data.removed: true`) returned when `removeAgents` is set.',
72
+ 'Confirmation that the managed agent-docs block was removed (`data.removed: true`), returned when `removeAgents` is set.',
73
73
  },
74
74
  ],
75
75
  throws: [
@@ -17,7 +17,7 @@ export const doc = {
17
17
  'Tally integration issues by severity into error and warning counts.',
18
18
  description:
19
19
  'A synchronous helper that reduces an AstryxIntegrationIssue[] (as returned ' +
20
- 'by validateIntegration) to counts of errors and warnings the seam a ' +
20
+ 'by validateIntegration) to counts of errors and warnings: the seam a ' +
21
21
  'caller uses to decide an exit code or print a summary line. Issues of any ' +
22
22
  'other severity are ignored.',
23
23
  importPath: '@astryxdesign/cli/api',
@@ -45,7 +45,7 @@ export const doc = {
45
45
  {
46
46
  type: 'integration.validate',
47
47
  description:
48
- 'The result envelope: `data.name` and `data.version` of the validated package (both null when no local manifest is found), plus `data.issues` an AstryxIntegrationIssue[] of {code, severity: `warning` | `error`, message}.',
48
+ 'The result envelope: `data.name` and `data.version` of the validated package (both null when no local manifest is found), plus `data.issues`, an AstryxIntegrationIssue[] of {code, severity: `warning` | `error`, message}.',
49
49
  },
50
50
  ],
51
51
  examples: [
@@ -27,7 +27,7 @@ export const doc = {
27
27
  name: 'raw',
28
28
  type: 'unknown',
29
29
  description:
30
- 'The CLI stdout to parse a JSON string, or an object that was already parsed.',
30
+ 'The CLI stdout to parse: a JSON string, or an object that was already parsed.',
31
31
  required: true,
32
32
  },
33
33
  {
@@ -42,7 +42,7 @@ export const doc = {
42
42
  {
43
43
  type: 'any',
44
44
  description:
45
- 'The parsed envelope, guaranteed at runtime to carry the requested `type`. The published signature is untyped cast to the matching *Response type for typed access.',
45
+ 'The parsed envelope, guaranteed at runtime to carry the requested `type`. The published signature is untyped; cast to the matching *Response type for typed access.',
46
46
  },
47
47
  ],
48
48
  throws: [
@@ -14,7 +14,7 @@ export const doc = {
14
14
  displayName: 'isError()',
15
15
  summary: 'Did the CLI return an error envelope?',
16
16
  description:
17
- 'Tests a parsed response for an `error` key. Branch on this before touching `data` ' +
17
+ 'Tests a parsed response for an `error` key. Branch on this before touching `data`: ' +
18
18
  'and prefer the stable `code` field over matching the human-readable message, which is ' +
19
19
  'not a contract. Note this returns a plain boolean, not a TypeScript type predicate, so ' +
20
20
  'it does not narrow on its own: cast to the matching *Response type to get typed access.',
@@ -26,7 +26,7 @@ export const doc = {
26
26
  name: 'raw',
27
27
  type: 'unknown',
28
28
  description:
29
- 'The CLI stdout to parse a JSON string, or an object that was already parsed.',
29
+ 'The CLI stdout to parse: a JSON string, or an object that was already parsed.',
30
30
  required: true,
31
31
  },
32
32
  ],
@@ -34,7 +34,7 @@ export const doc = {
34
34
  {
35
35
  type: 'any',
36
36
  description:
37
- 'The { type, data, meta? } envelope, or a CLIError envelope. The published signature is intentionally untyped narrow it by casting to the matching *Response type exported from @astryxdesign/cli/json.',
37
+ 'The { type, data, meta? } envelope, or a CLIError envelope. The published signature is intentionally untyped; narrow it by casting to the matching *Response type exported from @astryxdesign/cli/json.',
38
38
  },
39
39
  ],
40
40
  throws: [
@@ -18,7 +18,7 @@ export const doc = {
18
18
  'The validator behind `astryx layout check`. Parses and validates a compressed XLE/XLO ' +
19
19
  'expression without generating any TSX, and echoes it back in both canonical surfaces ' +
20
20
  '(compact and outline). Validation failures are reported in the layout.check envelope ' +
21
- '(valid: false) with line/col and suggestions not thrown so callers can lint an ' +
21
+ '(valid: false) with line/col and suggestions (not thrown) so callers can lint an ' +
22
22
  'expression and surface fixes.',
23
23
  importPath: '@astryxdesign/cli/api',
24
24
  signature:
@@ -16,7 +16,7 @@ export const doc = {
16
16
  summary: 'Expand a validated layout expression into XDS TSX.',
17
17
  description:
18
18
  'The generator behind `astryx layout expand`. Parses and validates a compressed XLE/XLO ' +
19
- 'expression, then expands it into ready-to-use XDS TSX auto-routing structural children ' +
19
+ 'expression, then expands it into ready-to-use XDS TSX, auto-routing structural children ' +
20
20
  'into the right slots, scaffolding typed useState for interactive controls, and splicing or ' +
21
21
  'importing any referenced template blocks. Returns the code (and metadata) in a layout.expand ' +
22
22
  'envelope, optionally writing it to a path within cwd.',
@@ -15,7 +15,7 @@ export const doc = {
15
15
  displayName: 'layoutGrammar()',
16
16
  summary: 'Return the XLE/XLO grammar cheatsheet for this install.',
17
17
  description:
18
- 'The reference behind `astryx layout grammar` the agent cheatsheet for writing XLE/XLO ' +
18
+ 'The reference behind `astryx layout grammar`: the agent cheatsheet for writing XLE/XLO ' +
19
19
  "layout expressions, with the alias table generated from this branch's registry rather than " +
20
20
  'hand-maintained, so short names always reflect the components actually installed.',
21
21
  importPath: '@astryxdesign/cli/api',
@@ -88,7 +88,7 @@ export const doc = {
88
88
  },
89
89
  {
90
90
  code: 'ERR_AMBIGUOUS_COMPONENT',
91
- when: 'more than one package provides the component choose one with package',
91
+ when: 'more than one package provides the component; choose one with package',
92
92
  },
93
93
  {
94
94
  code: 'ERR_NO_SOURCE',
@@ -4,36 +4,35 @@ import {describe, it, expect} from 'vitest';
4
4
  import {stripTemplateAssetRefs, template} from './template.mjs';
5
5
 
6
6
  describe('stripTemplateAssetRefs', () => {
7
- it('replaces a lookaside astryx image URL with an inline data URI', () => {
7
+ it('replaces a /template-assets image path with an inline data URI', () => {
8
8
  const src =
9
- "const hero = 'https://lookaside.facebook.com/assets/astryx/colorful-home-horizontal-1.png';";
9
+ "const hero = '/template-assets/colorful-home-horizontal-1.png';";
10
10
  const out = stripTemplateAssetRefs(src);
11
- expect(out).not.toContain('lookaside.facebook.com');
11
+ expect(out).not.toContain('/template-assets/');
12
12
  expect(out).toContain('data:image/svg+xml,');
13
13
  });
14
14
 
15
- it('replaces a lookaside block-avatar image URL', () => {
16
- const src =
17
- 'src="https://lookaside.facebook.com/assets/astryx/avatar-profile-05.jpg"';
15
+ it('replaces a /template-assets block-avatar image path', () => {
16
+ const src = 'src="/template-assets/avatar-profile-05.jpg"';
18
17
  const out = stripTemplateAssetRefs(src);
19
- expect(out).not.toContain('lookaside.facebook.com');
18
+ expect(out).not.toContain('/template-assets/');
20
19
  expect(out).toContain('data:image/svg+xml,');
21
20
  });
22
21
 
23
- it('replaces every lookaside reference, not just the first', () => {
22
+ it('replaces every /template-assets reference, not just the first', () => {
24
23
  const src = [
25
- "'https://lookaside.facebook.com/assets/astryx/colorful-home-horizontal-1.png'",
26
- "'https://lookaside.facebook.com/assets/astryx/illustrative-horizontal-3.png'",
27
- "'https://lookaside.facebook.com/assets/astryx/moody-scene-horizontal-1.png'",
24
+ "'/template-assets/colorful-home-horizontal-1.png'",
25
+ "'/template-assets/illustrative-horizontal-3.png'",
26
+ "'/template-assets/moody-scene-horizontal-1.png'",
28
27
  ].join('\n');
29
28
  const out = stripTemplateAssetRefs(src);
30
- expect(out).not.toContain('lookaside.facebook.com');
29
+ expect(out).not.toContain('/template-assets/');
31
30
  expect(out.match(/data:image\/svg\+xml,/g)).toHaveLength(3);
32
31
  });
33
32
 
34
33
  it('preserves surrounding source structure', () => {
35
34
  const src =
36
- "const data = [{src: 'https://lookaside.facebook.com/assets/astryx/x.png', alt: 'X'}];";
35
+ "const data = [{src: '/template-assets/x.png', alt: 'X'}];";
37
36
  const out = stripTemplateAssetRefs(src);
38
37
  expect(out).toContain("alt: 'X'");
39
38
  expect(out).toContain('const data = [{src:');
@@ -16,7 +16,7 @@ export const doc = {
16
16
  displayName: 'listThemes()',
17
17
  summary: 'Read the bundled-theme manifest and return its raw entries.',
18
18
  description:
19
- "The shared data layer behind theme list/add reads the CLI's bundled-theme manifest " +
19
+ "The shared data layer behind theme list/add: reads the CLI's bundled-theme manifest " +
20
20
  '(templates/themes/manifest.json, generated by scripts/generate-cli-themes.mjs) and returns ' +
21
21
  'the parsed entries. themeList() projects this into the theme.list envelope; themeAdd() ' +
22
22
  'resolves a slug against it. Returns synchronously.',
@@ -28,7 +28,7 @@ export const doc = {
28
28
  {
29
29
  type: 'BundledTheme[]',
30
30
  description:
31
- 'The parsed themes array from the bundle manifest each BundledTheme has slug, displayName, description, maintained, entry, exportName, and files. Empty when the manifest has not been generated.',
31
+ 'The parsed themes array from the bundle manifest: each BundledTheme has slug, displayName, description, maintained, entry, exportName, and files. Empty when the manifest has not been generated.',
32
32
  },
33
33
  ],
34
34
  throws: [
@@ -16,7 +16,7 @@ export const doc = {
16
16
  summary: 'Scaffold a bundled theme into a project as editable source.',
17
17
  description:
18
18
  "Copies a bundled theme's source (from the CLI's templates/themes/<slug>) into the " +
19
- "consumer's project so they own it no theme package needed. Writes are staged to temp " +
19
+ "consumer's project so they own it, no theme package needed. Writes are staged to temp " +
20
20
  'files then renamed, rolling back partials on failure so a failed write never leaves a ' +
21
21
  'half-written theme. Returns a theme.add receipt describing where the files landed and how ' +
22
22
  'to import them.',
@@ -20,7 +20,7 @@ export const doc = {
20
20
  'emits the exact CSS the <Theme> runtime does), writes a scoped CSS file, a JS module ' +
21
21
  'that re-exports the built theme, and a .d.ts (plus an optional .variants.d.ts when the ' +
22
22
  'theme adds custom prop values). With {check: true} it writes nothing and instead compares ' +
23
- 'each output against disk, returning the drift the CI guard for committed, generated theme CSS.',
23
+ 'each output against disk, returning the drift: the CI guard for committed, generated theme CSS.',
24
24
  importPath: '@astryxdesign/cli/api',
25
25
  signature:
26
26
  'themeBuild(file: string, options?: {out?: string, check?: boolean}, ctx?: {cwd?: string}): Promise<ThemeBuildResponse | ThemeBuildCheckResponse | null>',
@@ -51,7 +51,7 @@ export const doc = {
51
51
  name: 'options.check',
52
52
  type: 'boolean',
53
53
  description:
54
- 'Compile in memory and compare each output against what is on disk instead of writing the CI drift guard.',
54
+ 'Compile in memory and compare each output against what is on disk instead of writing: the CI drift guard.',
55
55
  default: 'false',
56
56
  },
57
57
  {
@@ -86,7 +86,7 @@ export const doc = {
86
86
  },
87
87
  {
88
88
  code: 'ERR_CORE_NOT_FOUND',
89
- when: '@astryxdesign/core/theme cannot be imported a built, resolvable @astryxdesign/core is required',
89
+ when: '@astryxdesign/core/theme cannot be imported; a built, resolvable @astryxdesign/core is required',
90
90
  },
91
91
  {
92
92
  code: 'ERR_WRITE_FAILED',
@@ -15,7 +15,7 @@ export const doc = {
15
15
  displayName: 'themeList()',
16
16
  summary: 'List the themes bundled with this CLI build.',
17
17
  description:
18
- 'Projects the bundled-theme manifest into the theme.list envelope the themes that themeAdd ' +
18
+ 'Projects the bundled-theme manifest into the theme.list envelope: the themes that themeAdd ' +
19
19
  'can scaffold. A pure projection of the manifest read by listThemes(); no I/O beyond that read, ' +
20
20
  'and it returns synchronously.',
21
21
  importPath: '@astryxdesign/cli/api',
@@ -26,7 +26,7 @@ export const doc = {
26
26
  {
27
27
  type: 'theme.list',
28
28
  description:
29
- 'Every bundled theme as a ThemeListEntry[] each entry has slug, displayName, description, and a maintained flag.',
29
+ 'Every bundled theme as a ThemeListEntry[]: each entry has slug, displayName, description, and a maintained flag.',
30
30
  },
31
31
  ],
32
32
  throws: [
@@ -86,7 +86,7 @@ export const doc = {
86
86
  {
87
87
  type: 'upgrade.list',
88
88
  description:
89
- 'Every available codemod, oldest→newest, as {name, title, version, optional} returned when `list` is set; nothing is run.',
89
+ 'Every available codemod, oldest→newest, as {name, title, version, optional}, returned when `list` is set; nothing is run.',
90
90
  },
91
91
  {
92
92
  type: 'upgrade.status',
@@ -0,0 +1,82 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ import {describe, it, expect} from 'vitest';
4
+
5
+ async function applyTransform(source) {
6
+ const {default: transform} =
7
+ await import('../migrate-grid-minchildwidth-to-columns.mjs');
8
+ const jscodeshift = (await import('jscodeshift')).default;
9
+ const j = jscodeshift.withParser('tsx');
10
+ const api = {jscodeshift: j, stats: () => {}, report: () => {}};
11
+ const file = {source, path: 'test.tsx'};
12
+ const result = transform(file, api);
13
+ return result ?? source;
14
+ }
15
+
16
+ /** Collapse whitespace (and space padding inside braces) so formatting of
17
+ * pretty-printed multiline objects does not matter for assertions. */
18
+ function normalize(str) {
19
+ return str
20
+ .replace(/\s+/g, ' ')
21
+ .replace(/\{\s+/g, '{')
22
+ .replace(/\s+\}/g, '}')
23
+ .trim();
24
+ }
25
+
26
+ describe('migrate-grid-minchildwidth-to-columns', () => {
27
+ it('migrates minChildWidth only to columns={{minWidth, repeat: "fit"}}', async () => {
28
+ const input = `import {Grid} from '@astryxdesign/core';
29
+ const t = <Grid minChildWidth={280}><Item /></Grid>;`;
30
+ const output = await applyTransform(input);
31
+ expect(output).not.toContain('minChildWidth');
32
+ expect(normalize(output)).toContain(
33
+ normalize("columns={{minWidth: 280, repeat: 'fit'}}"),
34
+ );
35
+ });
36
+
37
+ it('migrates columns={n} + minChildWidth to columns={{minWidth, max, repeat: "fit"}}', async () => {
38
+ const input = `import {Grid} from '@astryxdesign/core';
39
+ const t = <Grid columns={3} minChildWidth={280}><Item /></Grid>;`;
40
+ const output = await applyTransform(input);
41
+ expect(output).not.toContain('minChildWidth');
42
+ expect(normalize(output)).toContain(
43
+ normalize("columns={{minWidth: 280, max: 3, repeat: 'fit'}}"),
44
+ );
45
+ });
46
+
47
+ it('preserves other attributes (gap) when migrating the combo', async () => {
48
+ const input = `import {Grid} from '@astryxdesign/core';
49
+ const t = <Grid columns={5} gap={5} minChildWidth={140}><Item /></Grid>;`;
50
+ const output = await applyTransform(input);
51
+ expect(output).not.toContain('minChildWidth');
52
+ expect(normalize(output)).toContain(
53
+ normalize("columns={{minWidth: 140, max: 5, repeat: 'fit'}}"),
54
+ );
55
+ expect(output).toContain('gap={5}');
56
+ });
57
+
58
+ it('bails when columns is already an object (ambiguous)', async () => {
59
+ const input = `import {Grid} from '@astryxdesign/core';
60
+ const t = <Grid columns={{minWidth: 100}} minChildWidth={280}><Item /></Grid>;`;
61
+ const output = await applyTransform(input);
62
+ // Unchanged: object columns is ambiguous, so it is left as-is.
63
+ expect(output).toBe(input);
64
+ });
65
+
66
+ it('bails when columns is a non-numeric/dynamic expression', async () => {
67
+ const input = `import {Grid} from '@astryxdesign/core';
68
+ const t = <Grid columns={cols} minChildWidth={280}><Item /></Grid>;`;
69
+ const output = await applyTransform(input);
70
+ expect(output).toBe(input);
71
+ });
72
+
73
+ it('is alias-aware and supports the subpath import source', async () => {
74
+ const input = `import {Grid as G} from '@astryxdesign/core/Grid';
75
+ const t = <G columns={4} minChildWidth={200}><Item /></G>;`;
76
+ const output = await applyTransform(input);
77
+ expect(output).not.toContain('minChildWidth');
78
+ expect(normalize(output)).toContain(
79
+ normalize("columns={{minWidth: 200, max: 4, repeat: 'fit'}}"),
80
+ );
81
+ });
82
+ });
@@ -49,11 +49,10 @@ export default function transformer(file, api) {
49
49
  a.type === 'JSXAttribute' && a.name?.name === 'minChildWidth',
50
50
  );
51
51
  if (!minAttr) return;
52
- const hasColumns = attrs.some(
52
+ const columnsAttr = attrs.find(
53
53
  (/** @type {any} */ a) =>
54
54
  a.type === 'JSXAttribute' && a.name?.name === 'columns',
55
55
  );
56
- if (hasColumns) return;
57
56
 
58
57
  let minWidthExpr;
59
58
  if (
@@ -67,6 +66,43 @@ export default function transformer(file, api) {
67
66
  return;
68
67
  }
69
68
 
69
+ if (columnsAttr) {
70
+ // Both `columns` and `minChildWidth` are present. This is only safely
71
+ // migratable when `columns` is a numeric literal (e.g. `columns={3}`).
72
+ //
73
+ // In the old (0.2.0) Grid runtime, when both props were set and
74
+ // `columns` was a number, `minChildWidth` dominated and the numeric
75
+ // `columns` became a *max column cap* with `auto-fit`:
76
+ // repeat(auto-fit, minmax(minChildWidth, 1fr)) capped at `columns`.
77
+ // The 0.3.0 `columns` object models this exactly as
78
+ // {minWidth, max, repeat: 'fit'}.
79
+ // So the lossless rewrite is:
80
+ // <Grid columns={3} minChildWidth={280}>
81
+ // -> <Grid columns={{minWidth: 280, max: 3, repeat: 'fit'}}>
82
+ //
83
+ // If `columns` is already an object or a non-numeric/dynamic
84
+ // expression, the combined behavior is genuinely ambiguous, so we
85
+ // bail and leave it unmigrated (it surfaces separately).
86
+ const columnsValue = columnsAttr.value;
87
+ if (columnsValue?.type !== 'JSXExpressionContainer') return;
88
+ const columnsExpr = columnsValue.expression;
89
+ const isNumericLiteral =
90
+ (columnsExpr?.type === 'NumericLiteral' ||
91
+ columnsExpr?.type === 'Literal') &&
92
+ typeof columnsExpr.value === 'number';
93
+ if (!isNumericLiteral) return;
94
+
95
+ const columnsObject = j.objectExpression([
96
+ j.property('init', j.identifier('minWidth'), minWidthExpr),
97
+ j.property('init', j.identifier('max'), j.literal(columnsExpr.value)),
98
+ j.property('init', j.identifier('repeat'), j.literal('fit')),
99
+ ]);
100
+ columnsAttr.value = j.jsxExpressionContainer(columnsObject);
101
+ attrs.splice(attrs.indexOf(minAttr), 1);
102
+ hasChanges = true;
103
+ return;
104
+ }
105
+
70
106
  const columnsObject = j.objectExpression([
71
107
  j.property('init', j.identifier('minWidth'), minWidthExpr),
72
108
  j.property('init', j.identifier('repeat'), j.literal('fit')),