@astryxdesign/cli 0.3.0-canary.c857354 → 0.3.0-canary.d52d2cb

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 (129) hide show
  1. package/api/component/_adapter.mjs +3 -0
  2. package/api/integration/validate-integration.d.mts +2 -18
  3. package/api/integration/validate-integration.mjs +16 -141
  4. package/api/template/copy/copy.d.mts +2 -2
  5. package/api/template/copy/copy.mjs +2 -2
  6. package/api/template/list/list.d.mts +2 -2
  7. package/api/template/list/list.mjs +2 -2
  8. package/api/template/show/show.d.mts +2 -2
  9. package/api/template/show/show.mjs +2 -2
  10. package/api/template/skeleton/skeleton.d.mts +3 -3
  11. package/api/template/skeleton/skeleton.mjs +3 -3
  12. package/api/template/template.d.mts +7 -7
  13. package/api/template/template.mjs +7 -7
  14. package/api/template/template.test.mjs +12 -13
  15. package/assets/codemods/transforms/v0.3.0/__tests__/migrate-grid-minchildwidth-to-columns.test.mjs +82 -0
  16. package/assets/codemods/transforms/v0.3.0/migrate-grid-minchildwidth-to-columns.mjs +38 -2
  17. package/assets/docs/layout.doc.dense.mjs +1 -1
  18. package/assets/templates/blocks/components/AspectRatio/AspectRatioCircleImage.tsx +1 -1
  19. package/assets/templates/blocks/components/AspectRatio/AspectRatioImageGallery.tsx +1 -1
  20. package/assets/templates/blocks/components/AspectRatio/AspectRatioShowcase.tsx +3 -3
  21. package/assets/templates/blocks/components/AspectRatio/AspectRatioSquareImage.tsx +1 -1
  22. package/assets/templates/blocks/components/AspectRatio/AspectRatioWidescreen.tsx +1 -1
  23. package/assets/templates/blocks/components/Avatar/AvatarFallbackChain.tsx +6 -6
  24. package/assets/templates/blocks/components/Avatar/AvatarGroup.tsx +5 -5
  25. package/assets/templates/blocks/components/Avatar/AvatarInteractive.tsx +2 -2
  26. package/assets/templates/blocks/components/Avatar/AvatarShowcase.tsx +4 -4
  27. package/assets/templates/blocks/components/Avatar/AvatarTooltip.tsx +4 -4
  28. package/assets/templates/blocks/components/Avatar/AvatarUserCard.tsx +3 -3
  29. package/assets/templates/blocks/components/Avatar/AvatarWithImage.tsx +4 -4
  30. package/assets/templates/blocks/components/Avatar/AvatarWithStatus.tsx +3 -3
  31. package/assets/templates/blocks/components/ChatComposerDrawer/ChatComposerDrawerAttachments.tsx +5 -5
  32. package/assets/templates/blocks/components/Lightbox/LightboxGallery.tsx +4 -4
  33. package/assets/templates/blocks/components/Lightbox/LightboxShowcase.tsx +1 -1
  34. package/assets/templates/blocks/components/Lightbox/LightboxVideo.tsx +1 -1
  35. package/assets/templates/blocks/components/Lightbox/LightboxZoom.tsx +2 -2
  36. package/assets/templates/blocks/components/MediaTheme/MediaThemeImageOverlay.tsx +1 -1
  37. package/assets/templates/blocks/components/MediaTheme/MediaThemeLightScrim.tsx +1 -1
  38. package/assets/templates/blocks/components/MediaTheme/MediaThemeShowcase.tsx +1 -1
  39. package/assets/templates/blocks/components/Overlay/OverlayBottomStrip.tsx +1 -1
  40. package/assets/templates/blocks/components/Overlay/OverlayHoverReveal.tsx +1 -1
  41. package/assets/templates/blocks/components/Overlay/OverlayShowcase.tsx +1 -1
  42. package/assets/templates/blocks/components/TopNav/TopNavMegaMenu.tsx +1 -1
  43. package/assets/templates/pages/centered-hero/page.tsx +1 -1
  44. package/assets/templates/pages/classic-gallery/page.tsx +10 -10
  45. package/assets/templates/pages/detail-page/page.tsx +5 -5
  46. package/assets/templates/pages/form-two-column/page.tsx +1 -1
  47. package/assets/templates/pages/gallery-hero/page.tsx +3 -3
  48. package/assets/templates/pages/library/page.tsx +30 -30
  49. package/assets/templates/pages/login/page.tsx +1 -2
  50. package/assets/templates/pages/login-card/page.tsx +1 -2
  51. package/assets/templates/pages/login-split/page.tsx +5 -6
  52. package/assets/templates/pages/login-sso/page.tsx +2 -3
  53. package/assets/templates/pages/mixed-gallery/page.tsx +5 -5
  54. package/assets/templates/pages/payment-form/page.tsx +3 -3
  55. package/assets/templates/pages/product-detail/page.tsx +7 -7
  56. package/assets/templates/pages/product-gallery/page.tsx +6 -6
  57. package/assets/templates/pages/shell-top-nav/page.tsx +2 -2
  58. package/assets/templates/pages/side-gallery/page.tsx +9 -9
  59. package/assets/templates/pages/table-page-chart/page.tsx +6 -6
  60. package/assets/templates/pages/theme-showcase/page.tsx +6 -6
  61. package/authoring/_shared/errors.d.mts +21 -0
  62. package/authoring/codemod/codemod.doc.d.mts +11 -0
  63. package/authoring/codemod/parse.d.mts +48 -2
  64. package/authoring/config/config.doc.d.mts +10 -0
  65. package/authoring/config/parse.d.mts +54 -2
  66. package/authoring/doctypes/_schema.d.mts +248 -0
  67. package/authoring/doctypes/base/type.ts +6 -0
  68. package/authoring/doctypes/command/command.doc.d.mts +12 -0
  69. package/authoring/doctypes/command/parse.d.mts +11 -2
  70. package/authoring/doctypes/component/component.doc.d.mts +11 -0
  71. package/authoring/doctypes/component/parse.d.mts +11 -2
  72. package/authoring/doctypes/enum/enum.doc.d.mts +11 -0
  73. package/authoring/doctypes/enum/parse.d.mts +11 -2
  74. package/authoring/doctypes/function/function.doc.d.mts +11 -0
  75. package/authoring/doctypes/function/parse.d.mts +11 -2
  76. package/authoring/doctypes/function/parse.mjs +24 -4
  77. package/authoring/doctypes/hook/hook.doc.d.mts +10 -0
  78. package/authoring/doctypes/hook/parse.d.mts +11 -2
  79. package/authoring/doctypes/legacy.d.mts +13 -2
  80. package/authoring/doctypes/parse.d.mts +29 -20
  81. package/authoring/doctypes/reference/parse.d.mts +11 -2
  82. package/authoring/doctypes/reference/reference.doc.d.mts +11 -0
  83. package/authoring/doctypes/schema/parse.d.mts +11 -2
  84. package/authoring/doctypes/schema/schema.doc.d.mts +11 -0
  85. package/authoring/doctypes/template/parse.d.mts +12 -2
  86. package/authoring/doctypes/template/template.doc.d.mts +11 -0
  87. package/authoring/index.d.mts +16 -0
  88. package/authoring/integration/integration.doc.d.mts +10 -0
  89. package/authoring/integration/parse.d.mts +30 -2
  90. package/clients/cli/commands/hook.doc.mjs +1 -1
  91. package/clients/cli/lib/manifest.mjs +1 -1
  92. package/foundation/agent-docs/agent-docs.d.mts +197 -0
  93. package/foundation/agent-docs/agent-docs.mjs +3 -4
  94. package/foundation/agent-docs/agent-docs.test.mjs +8 -0
  95. package/foundation/config/config-cache.d.mts +53 -0
  96. package/foundation/config/project.d.mts +154 -0
  97. package/foundation/config/project.mjs +3 -3
  98. package/foundation/discovery/component-discovery.d.mts +140 -0
  99. package/foundation/discovery/component-loader.d.mts +50 -0
  100. package/foundation/discovery/hook-discovery.d.mts +28 -0
  101. package/{api/template/_adapter.d.mts → foundation/discovery/template-adapter.d.mts} +1 -1
  102. package/{api/template/_adapter.mjs → foundation/discovery/template-adapter.mjs} +28 -19
  103. package/foundation/env/node-version.d.mts +52 -0
  104. package/foundation/env/package-manager.d.mts +84 -0
  105. package/foundation/env/semver.d.mts +56 -0
  106. package/foundation/fs/module-loader.d.mts +36 -0
  107. package/foundation/fs/path-safety.d.mts +74 -0
  108. package/foundation/fs/paths.d.mts +35 -0
  109. package/foundation/integrations/integration-warnings.d.mts +17 -0
  110. package/foundation/integrations/integration-warnings.mjs +1 -1
  111. package/foundation/integrations/integrations.d.mts +80 -0
  112. package/foundation/integrations/validate-contributions.d.mts +22 -0
  113. package/foundation/integrations/validate-contributions.mjs +162 -0
  114. package/foundation/response/error-codes.d.mts +103 -0
  115. package/foundation/response/error-codes.doc.d.mts +11 -0
  116. package/foundation/response/json.d.mts +75 -0
  117. package/foundation/response/response-types.doc.d.mts +12 -0
  118. package/foundation/response/response.doc.d.mts +11 -0
  119. package/foundation/text/levenshtein.d.mts +21 -0
  120. package/foundation/text/string-utils.d.mts +43 -0
  121. package/foundation/xle/browser.d.mts +89 -0
  122. package/foundation/xle/expand.d.mts +23 -0
  123. package/foundation/xle/parse.d.mts +72 -0
  124. package/foundation/xle/print.d.mts +7 -0
  125. package/foundation/xle/registry-core.d.mts +168 -0
  126. package/foundation/xle/registry.d.mts +19 -0
  127. package/foundation/xle/splice.d.mts +42 -0
  128. package/foundation/xle/validate.d.mts +94 -0
  129. package/package.json +9 -9
@@ -270,6 +270,9 @@ export async function resolveUnscopedDoc(dirName, {coreDir, cwd, name}) {
270
270
  let resolvedName = dirName;
271
271
  // Track the resolving owner so the detail payload can carry ownership info.
272
272
  // Defaults to core; the legacy-external fallback below may reassign it.
273
+ // Annotated because CORE_PACKAGE's generated declaration carries the literal
274
+ // type, which (unlike a fresh literal) does not widen on assignment.
275
+ /** @type {string} */
273
276
  let resolvedOwnerPackage = CORE_PACKAGE;
274
277
  let resolvedSourcePath = readmePath ? findComponentSource(coreDir, dirName) : null;
275
278
 
@@ -1,24 +1,6 @@
1
1
  // @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
2
2
  // DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
3
3
 
4
- /**
5
- * Validate an already-LOADED integration (as produced by
6
- * `loadIntegrations` in lib/integrations.mjs — absolute contribution roots
7
- * plus identity) and return its issues. This is the reuse seam for everyday
8
- * commands that have already loaded the configured integrations and want the
9
- * SAME validators that `validate-integration` runs, without re-resolving the
10
- * manifest from disk.
11
- *
12
- * The manifest schema is intentionally NOT re-validated here: `loadIntegrations`
13
- * already validated it (and throws otherwise), so by the time a command holds a
14
- * loaded integration the manifest is known-good. We re-run the on-disk
15
- * contribution checks (roots + codemods/templates/components) because those can
16
- * regress independently of the manifest (a deleted directory, a broken template).
17
- *
18
- * @param {import('./validate-integration.type.mjs').LoadedIntegration} loaded loaded-integration-shaped object
19
- * @returns {Promise<Issue[]>}
20
- */
21
- export function validateLoadedIntegration(loaded: import("./validate-integration.type.mjs").LoadedIntegration): Promise<Issue[]>;
22
4
  /**
23
5
  * Validate the LOCAL integration package rooted at `cwd`: nearest package.json
24
6
  * + a single sibling astryx.integration.{ts,mjs,js}. A missing manifest yields
@@ -59,6 +41,7 @@ export function summarizeIssues(issues: Issue[]): {
59
41
  errors: number;
60
42
  warnings: number;
61
43
  };
44
+ export { validateLoadedIntegration };
62
45
  export type Issue = import("../../foundation/integrations/issue").AstryxIntegrationIssue;
63
46
  export type ValidateResult = {
64
47
  /**
@@ -79,3 +62,4 @@ export type ValidateResult = {
79
62
  manifestFile?: string | undefined;
80
63
  issues: Issue[];
81
64
  };
65
+ import { validateLoadedIntegration } from '../../foundation/integrations/validate-contributions.mjs';
@@ -17,6 +17,14 @@
17
17
  * is false only for the no-manifest local case, which is guidance (not an
18
18
  * error) so `validate-integration` can stay exit-0 in a non-integration dir.
19
19
  *
20
+ * The on-disk contribution validators themselves (roots + codemods/templates/
21
+ * components, behind `validateLoadedIntegration`) live in
22
+ * `foundation/integrations/validate-contributions.mjs`, because foundation also
23
+ * runs them: `Project` collects integration issues and `integration-warnings`
24
+ * nudges about them on ordinary commands. This file re-exports
25
+ * `validateLoadedIntegration` so existing importers are unaffected, and keeps
26
+ * the command-level entry points that resolve a manifest from disk.
27
+ *
20
28
  * Validators are intentionally small and independent so more checks can be
21
29
  * appended without reshaping the result. Issue `code`s are stable public
22
30
  * strings.
@@ -30,9 +38,14 @@ import {
30
38
  loadManifestObject,
31
39
  resolvePackageDir,
32
40
  } from '../../foundation/integrations/integrations.mjs';
33
- import {discoverIntegrationCodemods} from '../../assets/codemods/integration-discovery.mjs';
34
- import {discoverIntegrationTemplatesForOne} from '../template/template.mjs';
35
- import * as componentDiscovery from '../../foundation/discovery/component-discovery.mjs';
41
+ // The on-disk contribution validators live in foundation: Project and
42
+ // integration-warnings need them too, and foundation must not depend on api.
43
+ import {
44
+ validateLoadedIntegration,
45
+ issueError as error,
46
+ } from '../../foundation/integrations/validate-contributions.mjs';
47
+
48
+ export {validateLoadedIntegration};
36
49
 
37
50
  /**
38
51
  * @typedef {import('../../foundation/integrations/issue').AstryxIntegrationIssue} Issue
@@ -63,144 +76,6 @@ function findNearestPackageJson(cwd) {
63
76
  }
64
77
  }
65
78
 
66
- /** @param {string} code @param {string} message @returns {Issue} */
67
- function error(code, message) {
68
- return {code, severity: 'error', message};
69
- }
70
-
71
- /**
72
- * Verify each declared contribution root exists on disk. A declared-but-missing
73
- * root is a `missing_root` error.
74
- * @param {{components?: string, templates?: string, codemods?: string}} resolved
75
- * absolute resolved roots (undefined when not declared)
76
- * @param {Issue[]} issues
77
- */
78
- function checkRoots(resolved, issues) {
79
- const kinds = /** @type {const} */ (['components', 'templates', 'codemods']);
80
- for (const kind of kinds) {
81
- const root = resolved[kind];
82
- if (root == null) continue;
83
- if (!fs.existsSync(root)) {
84
- issues.push(
85
- error(
86
- 'missing_root',
87
- `Declared ${kind} root does not exist on disk: ${root}`,
88
- ),
89
- );
90
- }
91
- }
92
- }
93
-
94
- /**
95
- * Validate the integration's codemods via the landed discovery. Discovery is
96
- * strict (throws on bad export / duplicate id); we convert any throw into an
97
- * `invalid_codemod` error.
98
- * @param {import('./validate-integration.type.mjs').LoadedIntegration} integration loaded-integration-shaped object
99
- * @param {Issue[]} issues
100
- */
101
- async function checkCodemods(integration, issues) {
102
- if (!integration.codemods || !fs.existsSync(integration.codemods)) return;
103
- try {
104
- await discoverIntegrationCodemods([integration]);
105
- } catch (err) {
106
- issues.push(error('invalid_codemod', /** @type {any} */ (err).message));
107
- }
108
- }
109
-
110
- /**
111
- * Validate the integration's templates via the landed discovery. Per-template
112
- * problems are reported as `invalid_template` errors.
113
- * @param {import('./validate-integration.type.mjs').LoadedIntegration} integration loaded-integration-shaped object
114
- * @param {Issue[]} issues
115
- */
116
- async function checkTemplates(integration, issues) {
117
- if (!integration.templates || !fs.existsSync(integration.templates)) return;
118
- try {
119
- const {errors} = await discoverIntegrationTemplatesForOne(integration);
120
- for (const e of errors) {
121
- issues.push(error('invalid_template', e.message));
122
- }
123
- } catch (err) {
124
- issues.push(error('invalid_template', /** @type {any} */ (err).message));
125
- }
126
- }
127
-
128
- /**
129
- * Validate the integration's components via the landed ownership discovery.
130
- * Feature-detected: if the component-ownership export isn't present in this
131
- * build (sibling PR not yet merged), component validation is skipped rather
132
- * than hard-failing.
133
- *
134
- * `discoverIntegrationComponents` returns ownership records and does not throw
135
- * on a missing same-stem source — it records `sourcePath: null`. We surface
136
- * each such record as an `invalid_component` error.
137
- * @param {import('./validate-integration.type.mjs').LoadedIntegration} integration loaded-integration-shaped object
138
- * @param {Issue[]} issues
139
- */
140
- async function checkComponents(integration, issues) {
141
- if (!integration.components || !fs.existsSync(integration.components)) return;
142
- const discover = componentDiscovery.discoverIntegrationComponents;
143
- if (typeof discover !== 'function') return; // feature not present yet
144
- try {
145
- const records = (await discover(integration)) ?? [];
146
- for (const record of records) {
147
- if (record?.sourcePath == null) {
148
- issues.push(
149
- error(
150
- 'invalid_component',
151
- `Component "${record?.name}" is missing its same-stem source file ${record?.name}.tsx.`,
152
- ),
153
- );
154
- }
155
- }
156
- } catch (err) {
157
- issues.push(error('invalid_component', /** @type {any} */ (err).message));
158
- }
159
- }
160
-
161
- /**
162
- * Run every contribution validator against a loaded-integration-shaped object.
163
- * @param {import('./validate-integration.type.mjs').LoadedIntegration} integration
164
- * @param {Issue[]} issues
165
- */
166
- async function runContributionChecks(integration, issues) {
167
- await checkCodemods(integration, issues);
168
- await checkTemplates(integration, issues);
169
- await checkComponents(integration, issues);
170
- }
171
-
172
- /**
173
- * Validate an already-LOADED integration (as produced by
174
- * `loadIntegrations` in lib/integrations.mjs — absolute contribution roots
175
- * plus identity) and return its issues. This is the reuse seam for everyday
176
- * commands that have already loaded the configured integrations and want the
177
- * SAME validators that `validate-integration` runs, without re-resolving the
178
- * manifest from disk.
179
- *
180
- * The manifest schema is intentionally NOT re-validated here: `loadIntegrations`
181
- * already validated it (and throws otherwise), so by the time a command holds a
182
- * loaded integration the manifest is known-good. We re-run the on-disk
183
- * contribution checks (roots + codemods/templates/components) because those can
184
- * regress independently of the manifest (a deleted directory, a broken template).
185
- *
186
- * @param {import('./validate-integration.type.mjs').LoadedIntegration} loaded loaded-integration-shaped object
187
- * @returns {Promise<Issue[]>}
188
- */
189
- export async function validateLoadedIntegration(loaded) {
190
- /** @type {Issue[]} */
191
- const issues = [];
192
- if (!loaded || typeof loaded !== 'object') return issues;
193
- checkRoots(
194
- {
195
- components: loaded.components,
196
- templates: loaded.templates,
197
- codemods: loaded.codemods,
198
- },
199
- issues,
200
- );
201
- await runContributionChecks(loaded, issues);
202
- return issues;
203
- }
204
79
 
205
80
  /**
206
81
  * Validate a single integration given its package directory and identity.
@@ -4,11 +4,11 @@
4
4
  /**
5
5
  * Scaffold an already-resolved template to `targetPath` (relative to `cwd`) and
6
6
  * return the `template.copy` receipt.
7
- * @param {import('../_adapter.mjs').DiscoveredTemplate} match
7
+ * @param {import('../../../foundation/discovery/template-adapter.mjs').DiscoveredTemplate} match
8
8
  * @param {{targetPath: string, cwd: string, overwrite?: boolean}} ctx
9
9
  * @returns {import('../template.type.mjs').TemplateCopyResponse}
10
10
  */
11
- export function templateCopy(match: import("../_adapter.mjs").DiscoveredTemplate, { targetPath, cwd, overwrite }: {
11
+ export function templateCopy(match: import("../../../foundation/discovery/template-adapter.mjs").DiscoveredTemplate, { targetPath, cwd, overwrite }: {
12
12
  targetPath: string;
13
13
  cwd: string;
14
14
  overwrite?: boolean;
@@ -18,12 +18,12 @@ import {
18
18
  } from '../../../foundation/fs/path-safety.mjs';
19
19
  import {AstryxError} from '../../error.mjs';
20
20
  import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
21
- import {stripTemplateAssetRefs} from '../_adapter.mjs';
21
+ import {stripTemplateAssetRefs} from '../../../foundation/discovery/template-adapter.mjs';
22
22
 
23
23
  /**
24
24
  * Scaffold an already-resolved template to `targetPath` (relative to `cwd`) and
25
25
  * return the `template.copy` receipt.
26
- * @param {import('../_adapter.mjs').DiscoveredTemplate} match
26
+ * @param {import('../../../foundation/discovery/template-adapter.mjs').DiscoveredTemplate} match
27
27
  * @param {{targetPath: string, cwd: string, overwrite?: boolean}} ctx
28
28
  * @returns {import('../template.type.mjs').TemplateCopyResponse}
29
29
  */
@@ -3,11 +3,11 @@
3
3
 
4
4
  /**
5
5
  * Project a discovered template set into the `template.list` envelope.
6
- * @param {import('../_adapter.mjs').DiscoveredTemplate[]} templates
6
+ * @param {import('../../../foundation/discovery/template-adapter.mjs').DiscoveredTemplate[]} templates
7
7
  * @param {{type?: 'page' | 'block', package?: string}} [options]
8
8
  * @returns {import('../template.type.mjs').TemplateListResponse}
9
9
  */
10
- export function templateList(templates: import("../_adapter.mjs").DiscoveredTemplate[], options?: {
10
+ export function templateList(templates: import("../../../foundation/discovery/template-adapter.mjs").DiscoveredTemplate[], options?: {
11
11
  type?: "page" | "block";
12
12
  package?: string;
13
13
  }): import("../template.type.mjs").TemplateListResponse;
@@ -9,11 +9,11 @@
9
9
  * no-skeleton default) here.
10
10
  */
11
11
 
12
- import {pkgOf} from '../_adapter.mjs';
12
+ import {pkgOf} from '../../../foundation/discovery/template-adapter.mjs';
13
13
 
14
14
  /**
15
15
  * Project a discovered template set into the `template.list` envelope.
16
- * @param {import('../_adapter.mjs').DiscoveredTemplate[]} templates
16
+ * @param {import('../../../foundation/discovery/template-adapter.mjs').DiscoveredTemplate[]} templates
17
17
  * @param {{type?: 'page' | 'block', package?: string}} [options]
18
18
  * @returns {import('../template.type.mjs').TemplateListResponse}
19
19
  */
@@ -3,7 +3,7 @@
3
3
 
4
4
  /**
5
5
  * Build the `template.show` envelope for an already-resolved template.
6
- * @param {import('../_adapter.mjs').DiscoveredTemplate} match
6
+ * @param {import('../../../foundation/discovery/template-adapter.mjs').DiscoveredTemplate} match
7
7
  * @returns {import('../template.type.mjs').TemplateShowResponse}
8
8
  */
9
- export function templateShow(match: import("../_adapter.mjs").DiscoveredTemplate): import("../template.type.mjs").TemplateShowResponse;
9
+ export function templateShow(match: import("../../../foundation/discovery/template-adapter.mjs").DiscoveredTemplate): import("../template.type.mjs").TemplateShowResponse;
@@ -11,11 +11,11 @@
11
11
  import * as fs from 'node:fs';
12
12
  import {AstryxError} from '../../error.mjs';
13
13
  import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
14
- import {extractComponents} from '../_adapter.mjs';
14
+ import {extractComponents} from '../../../foundation/discovery/template-adapter.mjs';
15
15
 
16
16
  /**
17
17
  * Build the `template.show` envelope for an already-resolved template.
18
- * @param {import('../_adapter.mjs').DiscoveredTemplate} match
18
+ * @param {import('../../../foundation/discovery/template-adapter.mjs').DiscoveredTemplate} match
19
19
  * @returns {import('../template.type.mjs').TemplateShowResponse}
20
20
  */
21
21
  export function templateShow(match) {
@@ -5,8 +5,8 @@
5
5
  * Build the `template.skeleton` envelope for an already-resolved template.
6
6
  * `match` may be undefined when `--skeleton` is run without a name — the same
7
7
  * "specify a template name" error the dispatcher's resolution would surface.
8
- * @param {import('../_adapter.mjs').DiscoveredTemplate | undefined} match
9
- * @param {import('../_adapter.mjs').DiscoveredTemplate[]} templates
8
+ * @param {import('../../../foundation/discovery/template-adapter.mjs').DiscoveredTemplate | undefined} match
9
+ * @param {import('../../../foundation/discovery/template-adapter.mjs').DiscoveredTemplate[]} templates
10
10
  * @returns {import('../template.type.mjs').TemplateSkeletonResponse}
11
11
  */
12
- export function templateSkeleton(match: import("../_adapter.mjs").DiscoveredTemplate | undefined, templates: import("../_adapter.mjs").DiscoveredTemplate[]): import("../template.type.mjs").TemplateSkeletonResponse;
12
+ export function templateSkeleton(match: import("../../../foundation/discovery/template-adapter.mjs").DiscoveredTemplate | undefined, templates: import("../../../foundation/discovery/template-adapter.mjs").DiscoveredTemplate[]): import("../template.type.mjs").TemplateSkeletonResponse;
@@ -11,7 +11,7 @@
11
11
  import * as fs from 'node:fs';
12
12
  import {AstryxError} from '../../error.mjs';
13
13
  import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
14
- import {extractComponents} from '../_adapter.mjs';
14
+ import {extractComponents} from '../../../foundation/discovery/template-adapter.mjs';
15
15
 
16
16
  const STRUCTURAL = new Set([
17
17
  'AppShell',
@@ -223,8 +223,8 @@ function extractSkeleton(source) {
223
223
  * Build the `template.skeleton` envelope for an already-resolved template.
224
224
  * `match` may be undefined when `--skeleton` is run without a name — the same
225
225
  * "specify a template name" error the dispatcher's resolution would surface.
226
- * @param {import('../_adapter.mjs').DiscoveredTemplate | undefined} match
227
- * @param {import('../_adapter.mjs').DiscoveredTemplate[]} templates
226
+ * @param {import('../../../foundation/discovery/template-adapter.mjs').DiscoveredTemplate | undefined} match
227
+ * @param {import('../../../foundation/discovery/template-adapter.mjs').DiscoveredTemplate[]} templates
228
228
  * @returns {import('../template.type.mjs').TemplateSkeletonResponse}
229
229
  */
230
230
  export function templateSkeleton(match, templates) {
@@ -2,13 +2,13 @@
2
2
  // DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
3
3
 
4
4
  /**
5
- * @typedef {import('./_adapter.mjs').DiscoveredTemplate} DiscoveredTemplate
5
+ * @typedef {import('../../foundation/discovery/template-adapter.mjs').DiscoveredTemplate} DiscoveredTemplate
6
6
  */
7
7
  /**
8
- * @typedef {import('./_adapter.mjs').TemplateDiscoveryError} TemplateDiscoveryError
8
+ * @typedef {import('../../foundation/discovery/template-adapter.mjs').TemplateDiscoveryError} TemplateDiscoveryError
9
9
  */
10
10
  /**
11
- * @typedef {import('./_adapter.mjs').TemplateDocModule} TemplateDocModule
11
+ * @typedef {import('../../foundation/discovery/template-adapter.mjs').TemplateDocModule} TemplateDocModule
12
12
  */
13
13
  /**
14
14
  * @param {string} [name]
@@ -36,7 +36,7 @@ export function template(name?: string, options?: {
36
36
  type: string;
37
37
  data: unknown;
38
38
  }>;
39
- export type DiscoveredTemplate = import("./_adapter.mjs").DiscoveredTemplate;
40
- export type TemplateDiscoveryError = import("./_adapter.mjs").TemplateDiscoveryError;
41
- export type TemplateDocModule = import("./_adapter.mjs").TemplateDocModule;
42
- export { discoverAll, discoverAll as discoverTemplates, discoverAllWithErrors, discoverIntegrationTemplatesForOne, stripTemplateAssetRefs, listTemplates, findRelatedBlocks, findShowcase, extractComponents } from "./_adapter.mjs";
39
+ export type DiscoveredTemplate = import("../../foundation/discovery/template-adapter.mjs").DiscoveredTemplate;
40
+ export type TemplateDiscoveryError = import("../../foundation/discovery/template-adapter.mjs").TemplateDiscoveryError;
41
+ export type TemplateDocModule = import("../../foundation/discovery/template-adapter.mjs").TemplateDocModule;
42
+ export { discoverAll, discoverAll as discoverTemplates, discoverAllWithErrors, discoverIntegrationTemplatesForOne, stripTemplateAssetRefs, listTemplates, findRelatedBlocks, findShowcase, extractComponents } from "../../foundation/discovery/template-adapter.mjs";
@@ -6,16 +6,16 @@
6
6
  * This module is BOTH the template dispatcher and the stable import surface for
7
7
  * the template family. `template()` discovers the available templates, resolves
8
8
  * the requested one, and routes to a leaf (list/show/skeleton/copy). The shared
9
- * discovery/IO + cross-command helpers live in `./_adapter.mjs` and are
9
+ * discovery/IO + cross-command helpers live in `foundation/discovery/template-adapter.mjs` and are
10
10
  * RE-EXPORTED here so external import paths (`api/template/template.mjs`) —
11
11
  * used by component, layout, search, init, discover, validate-integration, and
12
12
  * lib/project — keep resolving unchanged.
13
13
  *
14
14
  * @position api/template — the template dispatcher + barrel; leaves live under
15
- * ./list, ./show, ./skeleton, ./copy and shared discovery under ./_adapter.
15
+ * ./list, ./show, ./skeleton, ./copy and shared discovery in foundation/discovery.
16
16
  */
17
17
 
18
- import {discoverAll, pkgOf} from './_adapter.mjs';
18
+ import {discoverAll, pkgOf} from '../../foundation/discovery/template-adapter.mjs';
19
19
  import {AstryxError} from '../error.mjs';
20
20
  import {ERROR_CODES} from '../../foundation/response/error-codes.mjs';
21
21
  import {templateList} from './list/list.mjs';
@@ -37,16 +37,16 @@ export {
37
37
  findRelatedBlocks,
38
38
  findShowcase,
39
39
  extractComponents,
40
- } from './_adapter.mjs';
40
+ } from '../../foundation/discovery/template-adapter.mjs';
41
41
 
42
42
  /**
43
- * @typedef {import('./_adapter.mjs').DiscoveredTemplate} DiscoveredTemplate
43
+ * @typedef {import('../../foundation/discovery/template-adapter.mjs').DiscoveredTemplate} DiscoveredTemplate
44
44
  */
45
45
  /**
46
- * @typedef {import('./_adapter.mjs').TemplateDiscoveryError} TemplateDiscoveryError
46
+ * @typedef {import('../../foundation/discovery/template-adapter.mjs').TemplateDiscoveryError} TemplateDiscoveryError
47
47
  */
48
48
  /**
49
- * @typedef {import('./_adapter.mjs').TemplateDocModule} TemplateDocModule
49
+ * @typedef {import('../../foundation/discovery/template-adapter.mjs').TemplateDocModule} TemplateDocModule
50
50
  */
51
51
 
52
52
  /**
@@ -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:');
@@ -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')),
@@ -64,7 +64,7 @@ export const docsDense = {
64
64
  'no Card-wrapped list items (card soup)',
65
65
  'no stacked full-width Cards as page structure',
66
66
  'no Cards in Cards',
67
- 'no decorative Badge counts/enums only; StatusDot/Token for status',
67
+ 'no decorative Badge: counts/enums only; StatusDot/Token for status',
68
68
  ],
69
69
  },
70
70
  ],
@@ -10,7 +10,7 @@ export default function AspectRatioCircleImage() {
10
10
  <Center width={300}>
11
11
  <AspectRatio ratio={1} shape="ellipse" fit="cover">
12
12
  <img
13
- src="https://lookaside.facebook.com/assets/astryx/light-home-square-1.png"
13
+ src="/template-assets/light-home-square-1.png"
14
14
  alt="Circular image"
15
15
  />
16
16
  </AspectRatio>
@@ -27,7 +27,7 @@ export default function AspectRatioImageGallery() {
27
27
  {images.map(({id, alt}) => (
28
28
  <AspectRatio key={id} ratio={4 / 3} fit="cover">
29
29
  <img
30
- src="https://lookaside.facebook.com/assets/astryx/illustrative-horizontal-1.png"
30
+ src="/template-assets/illustrative-horizontal-1.png"
31
31
  alt={alt}
32
32
  style={{borderRadius: 8}}
33
33
  />