@astryxdesign/cli 0.1.2 → 0.1.3

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 (238) hide show
  1. package/CHANGELOG.md +64 -1
  2. package/README.md +49 -55
  3. package/docs/layout.doc.dense.mjs +90 -0
  4. package/docs/layout.doc.mjs +160 -0
  5. package/docs/principles.doc.dense.mjs +2 -2
  6. package/docs/principles.doc.mjs +4 -0
  7. package/docs/principles.doc.zh.mjs +2 -2
  8. package/docs/theme.doc.mjs +2 -2
  9. package/package.json +13 -1
  10. package/src/api/blog.mjs +192 -0
  11. package/src/api/blog.test.mjs +144 -0
  12. package/src/api/component.mjs +253 -12
  13. package/src/api/discover.mjs +14 -7
  14. package/src/api/doctor.mjs +10 -25
  15. package/src/api/index.mjs +1 -0
  16. package/src/api/layout.mjs +11 -10
  17. package/src/api/layout.test.mjs +4 -1
  18. package/src/api/template-integration.test.mjs +225 -0
  19. package/src/api/template.mjs +242 -84
  20. package/src/api/validate-integration.mjs +370 -0
  21. package/src/api/validate-integration.test.mjs +222 -0
  22. package/src/codemod.mjs +93 -0
  23. package/src/codemod.test.mjs +134 -0
  24. package/src/codemods/__tests__/registry.test.mjs +1 -0
  25. package/src/codemods/__tests__/runner.test.mjs +103 -0
  26. package/src/codemods/integration-discovery.mjs +168 -0
  27. package/src/codemods/integration-discovery.test.mjs +234 -0
  28. package/src/codemods/integration-runner.mjs +109 -0
  29. package/src/codemods/registry.mjs +1 -0
  30. package/src/codemods/run-codemod.mjs +207 -0
  31. package/src/codemods/runner.mjs +73 -96
  32. package/src/codemods/transforms/v0.0.15/index.mjs +0 -13
  33. package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-css-surfaces.test.mjs +67 -0
  34. package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-declare-module.test.mjs +61 -0
  35. package/src/codemods/transforms/v0.1.0/__tests__/v0.1.0-ordering.test.mjs +104 -0
  36. package/src/codemods/transforms/{v0.0.15 → v0.1.0}/drop-xds-prefix-imports.mjs +15 -4
  37. package/src/codemods/transforms/v0.1.0/index.mjs +43 -6
  38. package/src/codemods/transforms/v0.1.0/migrate-xds-css-surfaces.mjs +77 -0
  39. package/src/codemods/transforms/v0.1.0/migrate-xds-declare-module.mjs +78 -0
  40. package/src/codemods/transforms/v0.1.3/__tests__/migrate-layout-components-to-experimental.test.mjs +360 -0
  41. package/src/codemods/transforms/v0.1.3/index.mjs +19 -0
  42. package/src/codemods/transforms/v0.1.3/migrate-layout-components-to-experimental.mjs +260 -0
  43. package/src/commands/agent-docs.mjs +4 -1
  44. package/src/commands/blog.mjs +60 -0
  45. package/src/commands/build-theme.import-path.test.mjs +2 -12
  46. package/src/commands/build-theme.mjs +117 -0
  47. package/src/commands/build-theme.prose.test.mjs +5 -15
  48. package/src/commands/build-theme.watch.test.mjs +149 -0
  49. package/src/commands/component/index.mjs +44 -10
  50. package/src/commands/component-ownership.test.mjs +227 -0
  51. package/src/commands/discover.mjs +5 -16
  52. package/src/commands/doctor.test.mjs +3 -3
  53. package/src/commands/ensure-core-built.mjs +120 -0
  54. package/src/commands/json-contract.test.mjs +0 -32
  55. package/src/commands/swizzle.mjs +224 -235
  56. package/src/commands/swizzle.path-safety.test.mjs +3 -3
  57. package/src/commands/swizzle.routing.test.mjs +279 -0
  58. package/src/commands/template.mjs +33 -37
  59. package/src/commands/upgrade.config-ordering.test.mjs +273 -0
  60. package/src/commands/upgrade.integration-policy.test.mjs +204 -0
  61. package/src/commands/upgrade.mjs +349 -170
  62. package/src/commands/validate-integration.mjs +110 -0
  63. package/src/commands/validate-integration.test.mjs +124 -0
  64. package/src/config.mjs +7 -20
  65. package/src/config.test.mjs +89 -13
  66. package/src/index.mjs +8 -3
  67. package/src/integration.mjs +19 -0
  68. package/src/lib/component-discovery.mjs +177 -0
  69. package/src/lib/config-cache.mjs +91 -0
  70. package/src/lib/config-cache.test.mjs +83 -0
  71. package/src/lib/config-schema.mjs +41 -55
  72. package/src/lib/error-codes.mjs +15 -12
  73. package/src/lib/integration-warnings.mjs +62 -0
  74. package/src/lib/integration-warnings.test.mjs +102 -0
  75. package/src/lib/integrations.mjs +92 -115
  76. package/src/lib/integrations.test.mjs +88 -107
  77. package/src/lib/manifest.mjs +5 -2
  78. package/src/lib/module-loader.mjs +80 -0
  79. package/src/lib/module-loader.test.mjs +106 -0
  80. package/src/lib/project.mjs +502 -0
  81. package/src/lib/project.test.mjs +308 -0
  82. package/src/lib/site.mjs +20 -0
  83. package/src/template.mjs +73 -0
  84. package/src/template.test.mjs +127 -0
  85. package/src/types/api.d.ts +2 -6
  86. package/src/types/base.d.ts +3 -9
  87. package/src/types/codemod.d.ts +81 -0
  88. package/src/types/component.d.ts +27 -2
  89. package/src/types/config.d.ts +56 -85
  90. package/src/types/error-codes.d.ts +5 -3
  91. package/src/types/index.d.ts +0 -1
  92. package/src/types/integration.d.ts +29 -0
  93. package/src/types/swizzle.d.ts +9 -2
  94. package/src/types/template-api.d.ts +54 -0
  95. package/src/types/template.d.ts +10 -7
  96. package/src/types/upgrade.d.ts +29 -0
  97. package/src/types/validate-integration.d.ts +24 -0
  98. package/src/utils/github.mjs +0 -237
  99. package/src/utils/interactive.mjs +2 -2
  100. package/templates/blocks/components/AvatarStatusDot/AvatarStatusDotVariants.doc.mjs +14 -0
  101. package/templates/blocks/components/AvatarStatusDot/AvatarStatusDotVariants.tsx +28 -0
  102. package/templates/blocks/components/Blockquote/BlockquoteTestimonials.doc.mjs +14 -0
  103. package/templates/blocks/components/Blockquote/BlockquoteTestimonials.tsx +34 -0
  104. package/templates/blocks/components/Blockquote/BlockquoteWithCite.doc.mjs +14 -0
  105. package/templates/blocks/components/Blockquote/BlockquoteWithCite.tsx +21 -0
  106. package/templates/blocks/components/BreadcrumbItem/BreadcrumbItemBasic.doc.mjs +14 -0
  107. package/templates/blocks/components/BreadcrumbItem/BreadcrumbItemBasic.tsx +15 -0
  108. package/templates/blocks/components/ButtonGroup/ButtonGroupBasic.doc.mjs +14 -0
  109. package/templates/blocks/components/ButtonGroup/ButtonGroupBasic.tsx +16 -0
  110. package/templates/blocks/components/ChatDictationButton/ChatDictationButtonBasic.doc.mjs +19 -0
  111. package/templates/blocks/components/ChatDictationButton/ChatDictationButtonBasic.tsx +34 -0
  112. package/templates/blocks/components/CheckboxListItem/CheckboxListItemBasic.doc.mjs +14 -0
  113. package/templates/blocks/components/CheckboxListItem/CheckboxListItemBasic.tsx +30 -0
  114. package/templates/blocks/components/CollapsibleGroup/CollapsibleGroupAccordion.doc.mjs +14 -0
  115. package/templates/blocks/components/CollapsibleGroup/CollapsibleGroupAccordion.tsx +31 -0
  116. package/templates/blocks/components/CommandPaletteEmpty/CommandPaletteEmptyBasic.doc.mjs +14 -0
  117. package/templates/blocks/components/CommandPaletteEmpty/CommandPaletteEmptyBasic.tsx +26 -0
  118. package/templates/blocks/components/CommandPaletteFooter/CommandPaletteFooterBasic.doc.mjs +14 -0
  119. package/templates/blocks/components/CommandPaletteFooter/CommandPaletteFooterBasic.tsx +32 -0
  120. package/templates/blocks/components/CommandPaletteGroup/CommandPaletteGroupBasic.doc.mjs +18 -0
  121. package/templates/blocks/components/CommandPaletteGroup/CommandPaletteGroupBasic.tsx +32 -0
  122. package/templates/blocks/components/CommandPaletteItem/CommandPaletteItemBasic.doc.mjs +14 -0
  123. package/templates/blocks/components/CommandPaletteItem/CommandPaletteItemBasic.tsx +27 -0
  124. package/templates/blocks/components/ContextMenu/ContextMenuBasic.doc.mjs +14 -0
  125. package/templates/blocks/components/ContextMenu/ContextMenuBasic.tsx +32 -0
  126. package/templates/blocks/components/DateRangeInput/DateRangeInputWithPresets.doc.mjs +14 -0
  127. package/templates/blocks/components/DateRangeInput/DateRangeInputWithPresets.tsx +46 -0
  128. package/templates/blocks/components/DateRangeInput/DateRangeInputWithValidation.doc.mjs +14 -0
  129. package/templates/blocks/components/DateRangeInput/DateRangeInputWithValidation.tsx +52 -0
  130. package/templates/blocks/components/DateTimeInput/DateTimeInputWithValidation.doc.mjs +14 -0
  131. package/templates/blocks/components/DateTimeInput/DateTimeInputWithValidation.tsx +43 -0
  132. package/templates/blocks/components/DialogHeader/DialogHeaderBasic.doc.mjs +14 -0
  133. package/templates/blocks/components/DialogHeader/DialogHeaderBasic.tsx +30 -0
  134. package/templates/blocks/components/DropdownMenu/DropdownMenuShowcase.tsx +0 -6
  135. package/templates/blocks/components/DropdownMenuItem/DropdownMenuItemBasic.doc.mjs +14 -0
  136. package/templates/blocks/components/DropdownMenuItem/DropdownMenuItemBasic.tsx +27 -0
  137. package/templates/blocks/components/DropdownMenuItem/DropdownMenuItemShowcase.tsx +1 -5
  138. package/templates/blocks/components/FieldLabel/FieldLabelBasic.doc.mjs +14 -0
  139. package/templates/blocks/components/FieldLabel/FieldLabelBasic.tsx +20 -0
  140. package/templates/blocks/components/FieldStatus/FieldStatusBasic.doc.mjs +14 -0
  141. package/templates/blocks/components/FieldStatus/FieldStatusBasic.tsx +23 -0
  142. package/templates/blocks/components/FileInput/FileInputBasic.doc.mjs +14 -0
  143. package/templates/blocks/components/FileInput/FileInputBasic.tsx +22 -0
  144. package/templates/blocks/components/GridSpan/GridSpanColumns.doc.mjs +14 -0
  145. package/templates/blocks/components/GridSpan/GridSpanColumns.tsx +38 -0
  146. package/templates/blocks/components/HStack/HStackBasic.doc.mjs +14 -0
  147. package/templates/blocks/components/HStack/HStackBasic.tsx +16 -0
  148. package/templates/blocks/components/Hooks/useKeyboardHintHookUsage.doc.mjs +14 -0
  149. package/templates/blocks/components/Hooks/useKeyboardHintHookUsage.tsx +57 -0
  150. package/templates/blocks/components/HoverCard/HoverCardInteractiveContent.doc.mjs +1 -1
  151. package/templates/blocks/components/HoverCard/HoverCardInteractiveContent.tsx +9 -4
  152. package/templates/blocks/components/InputGroup/InputGroupBasic.doc.mjs +14 -0
  153. package/templates/blocks/components/InputGroup/InputGroupBasic.tsx +27 -0
  154. package/templates/blocks/components/LayoutContent/LayoutContentBasic.doc.mjs +23 -0
  155. package/templates/blocks/components/LayoutContent/LayoutContentBasic.tsx +40 -0
  156. package/templates/blocks/components/LayoutFooter/LayoutFooterActions.doc.mjs +22 -0
  157. package/templates/blocks/components/LayoutFooter/LayoutFooterActions.tsx +41 -0
  158. package/templates/blocks/components/LayoutHeader/LayoutHeaderWithActions.doc.mjs +23 -0
  159. package/templates/blocks/components/LayoutHeader/LayoutHeaderWithActions.tsx +40 -0
  160. package/templates/blocks/components/LayoutPanel/LayoutPanelNavigation.doc.mjs +22 -0
  161. package/templates/blocks/components/LayoutPanel/LayoutPanelNavigation.tsx +37 -0
  162. package/templates/blocks/components/Lightbox/LightboxGallery.doc.mjs +14 -0
  163. package/templates/blocks/components/Lightbox/LightboxGallery.tsx +53 -0
  164. package/templates/blocks/components/Lightbox/LightboxShowcase.tsx +4 -3
  165. package/templates/blocks/components/Lightbox/LightboxVideo.doc.mjs +14 -0
  166. package/templates/blocks/components/Lightbox/LightboxVideo.tsx +27 -0
  167. package/templates/blocks/components/Lightbox/LightboxZoom.doc.mjs +14 -0
  168. package/templates/blocks/components/Lightbox/LightboxZoom.tsx +33 -0
  169. package/templates/blocks/components/LinkProvider/LinkProviderCustomLink.doc.mjs +1 -1
  170. package/templates/blocks/components/MetadataListItem/MetadataListItemBasic.doc.mjs +14 -0
  171. package/templates/blocks/components/MetadataListItem/MetadataListItemBasic.tsx +19 -0
  172. package/templates/blocks/components/MobileNavToggle/MobileNavToggleBasic.doc.mjs +23 -0
  173. package/templates/blocks/components/MobileNavToggle/MobileNavToggleBasic.tsx +42 -0
  174. package/templates/blocks/components/MoreMenu/MoreMenuShowcase.tsx +0 -6
  175. package/templates/blocks/components/NavIcon/NavIconBasic.doc.mjs +14 -0
  176. package/templates/blocks/components/NavIcon/NavIconBasic.tsx +16 -0
  177. package/templates/blocks/components/RadioListItem/RadioListItemBasic.doc.mjs +14 -0
  178. package/templates/blocks/components/RadioListItem/RadioListItemBasic.tsx +30 -0
  179. package/templates/blocks/components/Resizable/ResizableSidebar.doc.mjs +25 -0
  180. package/templates/blocks/components/Resizable/ResizableSidebar.tsx +67 -0
  181. package/templates/blocks/components/SegmentedControlItem/SegmentedControlItemBasic.doc.mjs +14 -0
  182. package/templates/blocks/components/SegmentedControlItem/SegmentedControlItemBasic.tsx +21 -0
  183. package/templates/blocks/components/SelectorOption/SelectorOptionBasic.doc.mjs +14 -0
  184. package/templates/blocks/components/SelectorOption/SelectorOptionBasic.tsx +39 -0
  185. package/templates/blocks/components/SideNavCollapseButton/SideNavCollapseButtonBasic.doc.mjs +14 -0
  186. package/templates/blocks/components/SideNavCollapseButton/SideNavCollapseButtonBasic.tsx +55 -0
  187. package/templates/blocks/components/SideNavHeading/SideNavHeadingBasic.doc.mjs +14 -0
  188. package/templates/blocks/components/SideNavHeading/SideNavHeadingBasic.tsx +38 -0
  189. package/templates/blocks/components/SideNavItem/SideNavItemBasic.doc.mjs +14 -0
  190. package/templates/blocks/components/SideNavItem/SideNavItemBasic.tsx +49 -0
  191. package/templates/blocks/components/SideNavSection/SideNavSectionBasic.doc.mjs +14 -0
  192. package/templates/blocks/components/SideNavSection/SideNavSectionBasic.tsx +71 -0
  193. package/templates/blocks/components/StackItem/StackItemFill.doc.mjs +14 -0
  194. package/templates/blocks/components/StackItem/StackItemFill.tsx +28 -0
  195. package/templates/blocks/components/Tab/TabWithSelectedIcon.doc.mjs +13 -0
  196. package/templates/blocks/components/Tab/TabWithSelectedIcon.tsx +39 -0
  197. package/templates/blocks/components/TabMenu/TabMenuBasic.doc.mjs +14 -0
  198. package/templates/blocks/components/TabMenu/TabMenuBasic.tsx +23 -0
  199. package/templates/blocks/components/Table/StickyColumnsHookUsage.doc.mjs +1 -1
  200. package/templates/blocks/components/ToggleButtonGroup/ToggleButtonGroupVertical.doc.mjs +14 -0
  201. package/templates/blocks/components/ToggleButtonGroup/ToggleButtonGroupVertical.tsx +47 -0
  202. package/templates/blocks/components/TopNavHeading/TopNavHeadingBasic.doc.mjs +14 -0
  203. package/templates/blocks/components/TopNavHeading/TopNavHeadingBasic.tsx +22 -0
  204. package/templates/blocks/components/TopNavItem/TopNavItemBasic.doc.mjs +14 -0
  205. package/templates/blocks/components/TopNavItem/TopNavItemBasic.tsx +21 -0
  206. package/templates/blocks/components/TopNavMegaMenu/TopNavMegaMenuBasic.doc.mjs +20 -0
  207. package/templates/blocks/components/TopNavMegaMenu/TopNavMegaMenuBasic.tsx +46 -0
  208. package/templates/blocks/components/TopNavMegaMenuFeaturedCard/TopNavMegaMenuFeaturedCardBasic.doc.mjs +14 -0
  209. package/templates/blocks/components/TopNavMegaMenuFeaturedCard/TopNavMegaMenuFeaturedCardBasic.tsx +16 -0
  210. package/templates/blocks/components/TopNavMegaMenuItem/TopNavMegaMenuItemBasic.doc.mjs +14 -0
  211. package/templates/blocks/components/TopNavMegaMenuItem/TopNavMegaMenuItemBasic.tsx +26 -0
  212. package/templates/blocks/components/TopNavMenu/TopNavMenuBasic.doc.mjs +14 -0
  213. package/templates/blocks/components/TopNavMenu/TopNavMenuBasic.tsx +43 -0
  214. package/templates/blocks/components/TypeaheadItem/TypeaheadItemBasic.doc.mjs +14 -0
  215. package/templates/blocks/components/TypeaheadItem/TypeaheadItemBasic.tsx +43 -0
  216. package/templates/blocks/components/VStack/VStackBasic.doc.mjs +14 -0
  217. package/templates/blocks/components/VStack/VStackBasic.tsx +20 -0
  218. package/templates/pages/kanban-board/page.tsx +729 -0
  219. package/templates/pages/kanban-board/template.doc.mjs +12 -0
  220. package/templates/pages/shell-nav/page.tsx +321 -0
  221. package/templates/pages/shell-nav/template.doc.mjs +12 -0
  222. package/templates/pages/shell-side-nav/page.tsx +241 -0
  223. package/templates/pages/shell-side-nav/template.doc.mjs +12 -0
  224. package/templates/pages/shell-top-nav/page.tsx +224 -0
  225. package/templates/pages/shell-top-nav/template.doc.mjs +12 -0
  226. package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-config-surfaces.test.mjs +0 -116
  227. package/src/codemods/transforms/v0.1.0/migrate-xds-config-surfaces.mjs +0 -230
  228. package/src/commands/gap-report.mjs +0 -464
  229. package/src/commands/gap-report.test.mjs +0 -168
  230. package/src/commands/swizzle-gap-safety.test.mjs +0 -273
  231. package/src/lib/config.mjs +0 -113
  232. package/src/lib/config.test.mjs +0 -91
  233. package/src/types/gap-report.d.ts +0 -29
  234. package/templates/blocks/components/TreeListBranches/TreeListBranchesShowcase.doc.mjs +0 -14
  235. package/templates/blocks/components/TreeListBranches/TreeListBranchesShowcase.tsx +0 -64
  236. package/templates/blocks/components/TreeListItem/TreeListItemShowcase.doc.mjs +0 -14
  237. package/templates/blocks/components/TreeListItem/TreeListItemShowcase.tsx +0 -60
  238. /package/src/codemods/transforms/{v0.0.15 → v0.1.0}/__tests__/drop-xds-prefix-imports.test.mjs +0 -0
@@ -0,0 +1,308 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
4
+ import * as fs from 'node:fs';
5
+ import * as path from 'node:path';
6
+ import {pathToFileURL} from 'node:url';
7
+ import {Project, DEFAULT_ISSUES_URL, findConfigPath} from './project.mjs';
8
+ import {InMemoryConfigCache} from './config-cache.mjs';
9
+ import * as componentDiscovery from './component-discovery.mjs';
10
+
11
+ let tmpDir;
12
+ let originalCwd;
13
+
14
+ const codemodModulePath = path.resolve(
15
+ process.cwd(),
16
+ 'packages/cli/src/codemod.mjs',
17
+ );
18
+ const codemodModuleUrl = pathToFileURL(codemodModulePath).href;
19
+
20
+ /**
21
+ * Scaffold a consumer project (package.json + astryx.config.mjs) under a
22
+ * repo-local temp dir, plus an installed integration package under
23
+ * node_modules. The repo-local location is required so Vite permits dynamic
24
+ * import of the config/integration modules (it blocks /tmp).
25
+ */
26
+ function scaffold({
27
+ integrations = ['@acme/widgets'],
28
+ issuesUrl,
29
+ withComponents = true,
30
+ withTemplates = true,
31
+ withCodemods = true,
32
+ brokenComponent = false,
33
+ brokenCodemod = false,
34
+ integrationIssuesUrl = 'https://example.com/widgets/issues',
35
+ } = {}) {
36
+ fs.writeFileSync(
37
+ path.join(tmpDir, 'package.json'),
38
+ JSON.stringify({name: 'consumer'}),
39
+ );
40
+ const config = {integrations};
41
+ if (issuesUrl) config.issuesUrl = issuesUrl;
42
+ fs.writeFileSync(
43
+ path.join(tmpDir, 'astryx.config.mjs'),
44
+ `export default ${JSON.stringify(config)};\n`,
45
+ );
46
+
47
+ const pkgDir = path.join(tmpDir, 'node_modules', '@acme', 'widgets');
48
+ fs.mkdirSync(pkgDir, {recursive: true});
49
+ fs.writeFileSync(
50
+ path.join(pkgDir, 'package.json'),
51
+ JSON.stringify({name: '@acme/widgets', version: '1.0.0'}),
52
+ );
53
+
54
+ const manifest = {};
55
+ if (withComponents) manifest.components = './components';
56
+ if (withTemplates) manifest.templates = './templates';
57
+ if (withCodemods) manifest.codemods = './codemods';
58
+ if (integrationIssuesUrl) manifest.issuesUrl = integrationIssuesUrl;
59
+ fs.writeFileSync(
60
+ path.join(pkgDir, 'astryx.integration.mjs'),
61
+ `export default ${JSON.stringify(manifest)};\n`,
62
+ );
63
+
64
+ if (withComponents) {
65
+ const compDir = path.join(pkgDir, 'components');
66
+ fs.mkdirSync(compDir, {recursive: true});
67
+ fs.writeFileSync(
68
+ path.join(compDir, 'Widget.doc.mjs'),
69
+ `export const docs = { name: 'Widget', usage: {description: 'A widget'} };\n`,
70
+ );
71
+ if (!brokenComponent) {
72
+ fs.writeFileSync(
73
+ path.join(compDir, 'Widget.tsx'),
74
+ `export function Widget() { return null; }\n`,
75
+ );
76
+ }
77
+ // brokenComponent => doc with no sibling .tsx (invalid_component issue).
78
+ }
79
+
80
+ if (withTemplates) {
81
+ const tDir = path.join(pkgDir, 'templates');
82
+ fs.mkdirSync(tDir, {recursive: true});
83
+ fs.writeFileSync(
84
+ path.join(tDir, 'hero.doc.mjs'),
85
+ `export default { type: 'block', name: 'Hero', description: 'A hero' };\n`,
86
+ );
87
+ fs.writeFileSync(
88
+ path.join(tDir, 'hero.tsx'),
89
+ `export default function Hero() { return null; }\n`,
90
+ );
91
+ }
92
+
93
+ if (withCodemods) {
94
+ const cmDir = path.join(pkgDir, 'codemods', '0.2.0');
95
+ fs.mkdirSync(cmDir, {recursive: true});
96
+ if (brokenCodemod) {
97
+ fs.writeFileSync(
98
+ path.join(cmDir, 'bad.mjs'),
99
+ `export default { not: 'a codemod' };\n`,
100
+ );
101
+ } else {
102
+ fs.writeFileSync(
103
+ path.join(cmDir, 'drop-foo.mjs'),
104
+ `import {createCodemod} from ${JSON.stringify(codemodModuleUrl)};\n` +
105
+ `export default createCodemod({ title: 'Drop foo', transform: (file) => file.source });\n`,
106
+ );
107
+ }
108
+ }
109
+
110
+ return pkgDir;
111
+ }
112
+
113
+ beforeEach(() => {
114
+ originalCwd = process.cwd();
115
+ tmpDir = fs.mkdtempSync(path.join(process.cwd(), '.astryx-project-test-'));
116
+ process.chdir(tmpDir);
117
+ });
118
+
119
+ afterEach(() => {
120
+ process.chdir(originalCwd);
121
+ fs.rmSync(tmpDir, {recursive: true, force: true});
122
+ vi.restoreAllMocks();
123
+ });
124
+
125
+ describe('Project.load', () => {
126
+ it('returns an empty config + no integrations when no config file exists', async () => {
127
+ fs.writeFileSync(
128
+ path.join(tmpDir, 'package.json'),
129
+ JSON.stringify({name: 'consumer'}),
130
+ );
131
+ const project = await Project.load(tmpDir);
132
+ expect(project.config).toEqual({integrations: []});
133
+ expect(project.integrations).toEqual([]);
134
+ expect(project.loadedIntegrations).toEqual([]);
135
+ expect(project.cwd).toBe(tmpDir);
136
+ });
137
+
138
+ it('exposes the validated config surface and loaded integrations', async () => {
139
+ scaffold({issuesUrl: 'https://example.com/issues'});
140
+ const project = await Project.load(tmpDir);
141
+ expect(project.config.issuesUrl).toBe('https://example.com/issues');
142
+ expect(project.integrations).toEqual(['@acme/widgets']);
143
+ expect(project.loadedIntegrations).toHaveLength(1);
144
+ expect(project.loadedIntegrations[0].name).toBe('@acme/widgets');
145
+ });
146
+
147
+ it('rejects an invalid config shape (strict validation)', async () => {
148
+ fs.writeFileSync(
149
+ path.join(tmpDir, 'package.json'),
150
+ JSON.stringify({name: 'consumer'}),
151
+ );
152
+ fs.writeFileSync(
153
+ path.join(tmpDir, 'astryx.config.mjs'),
154
+ `export default { integrations: [42] };\n`,
155
+ );
156
+ await expect(Project.load(tmpDir)).rejects.toThrow(/integrations/);
157
+ });
158
+ });
159
+
160
+ describe('findConfigPath', () => {
161
+ it('rejects multiple config files at the same root', () => {
162
+ fs.writeFileSync(
163
+ path.join(tmpDir, 'package.json'),
164
+ JSON.stringify({name: 'consumer'}),
165
+ );
166
+ fs.writeFileSync(
167
+ path.join(tmpDir, 'astryx.config.mjs'),
168
+ `export default {};\n`,
169
+ );
170
+ fs.writeFileSync(
171
+ path.join(tmpDir, 'astryx.config.js'),
172
+ `module.exports = {};\n`,
173
+ );
174
+ expect(() => findConfigPath(tmpDir)).toThrow(/Multiple Astryx config files/);
175
+ });
176
+ });
177
+
178
+ describe('Project discovery', () => {
179
+ it('components() returns integration ownership records', async () => {
180
+ scaffold();
181
+ const project = await Project.load(tmpDir);
182
+ const comps = await project.components();
183
+ const widget = comps.find(
184
+ c => c.name === 'Widget' && c.package === '@acme/widgets',
185
+ );
186
+ expect(widget).toBeTruthy();
187
+ expect(widget.sourcePath).toMatch(/Widget\.tsx$/);
188
+ });
189
+
190
+ it('templates() returns integration templates type-tagged', async () => {
191
+ scaffold();
192
+ const project = await Project.load(tmpDir);
193
+ const templates = await project.templates();
194
+ const hero = templates.find(t => t.package === '@acme/widgets');
195
+ expect(hero).toBeTruthy();
196
+ expect(hero.type).toBe('block');
197
+ });
198
+
199
+ it('codemods() returns core registry groups + integration groups', async () => {
200
+ scaffold();
201
+ const project = await Project.load(tmpDir);
202
+ const {core, integration} = await project.codemods('0.1.0', '0.2.0');
203
+ expect(Array.isArray(core)).toBe(true);
204
+ expect(integration).toHaveLength(1);
205
+ expect(integration[0].version).toBe('0.2.0');
206
+ expect(integration[0].codemods[0].id).toBe('drop-foo');
207
+ });
208
+
209
+ it('memoizes components() — the second call does not re-walk', async () => {
210
+ scaffold();
211
+ const project = await Project.load(tmpDir);
212
+ const spy = vi.spyOn(componentDiscovery, 'discoverIntegrationComponents');
213
+ const first = await project.components();
214
+ const callsAfterFirst = spy.mock.calls.length;
215
+ const second = await project.components();
216
+ // Same identity (cached value) and no additional discovery walk.
217
+ expect(second).toBe(first);
218
+ expect(spy.mock.calls.length).toBe(callsAfterFirst);
219
+ });
220
+ });
221
+
222
+ describe('Project issues (skip + warn)', () => {
223
+ it('collects issues for a broken integration and skips its contributions', async () => {
224
+ scaffold({brokenComponent: true});
225
+ const project = await Project.load(tmpDir);
226
+ const comps = await project.components();
227
+ // Widget is skipped because its source is missing.
228
+ expect(comps.find(c => c.package === '@acme/widgets')).toBeUndefined();
229
+ const issues = await project.issues();
230
+ expect(issues.length).toBeGreaterThan(0);
231
+ expect(issues.some(i => i.package === '@acme/widgets')).toBe(true);
232
+ });
233
+
234
+ it('does not throw when an integration codemod is broken', async () => {
235
+ scaffold({brokenCodemod: true});
236
+ const project = await Project.load(tmpDir);
237
+ const {integration} = await project.codemods('0.1.0', '0.2.0');
238
+ // Broken integration is skipped, not thrown.
239
+ expect(integration).toHaveLength(0);
240
+ const issues = await project.issues();
241
+ expect(issues.some(i => i.code === 'invalid_codemod')).toBe(true);
242
+ });
243
+
244
+ it('issues() dedupes and is complete on demand', async () => {
245
+ scaffold({brokenComponent: true});
246
+ const project = await Project.load(tmpDir);
247
+ // Call a discovery method first (collects some), then issues().
248
+ await project.components();
249
+ const a = await project.issues();
250
+ const b = await project.issues();
251
+ expect(a).toEqual(b);
252
+ // No duplicate (package, code, message) tuples.
253
+ const seen = new Set(a.map(i => `${i.package}\u0000${i.code}\u0000${i.message}`));
254
+ expect(seen.size).toBe(a.length);
255
+ });
256
+
257
+ it('issues() validates integrations not yet visited by a discovery call', async () => {
258
+ scaffold({brokenComponent: true});
259
+ const project = await Project.load(tmpDir);
260
+ // No discovery call at all — issues() must still surface the broken one.
261
+ const issues = await project.issues();
262
+ expect(issues.some(i => i.package === '@acme/widgets')).toBe(true);
263
+ });
264
+ });
265
+
266
+ describe('Project.issuesUrl routing', () => {
267
+ it('routes core to config.issuesUrl, falling back to the default', async () => {
268
+ scaffold({issuesUrl: 'https://example.com/core'});
269
+ const project = await Project.load(tmpDir);
270
+ expect(project.issuesUrl()).toBe('https://example.com/core');
271
+ expect(project.issuesUrl({package: '@astryxdesign/core'})).toBe(
272
+ 'https://example.com/core',
273
+ );
274
+ });
275
+
276
+ it('falls back to the default core URL when config has none', async () => {
277
+ scaffold();
278
+ const project = await Project.load(tmpDir);
279
+ expect(project.issuesUrl()).toBe(DEFAULT_ISSUES_URL);
280
+ });
281
+
282
+ it('routes an integration package to its manifest issuesUrl', async () => {
283
+ scaffold({integrationIssuesUrl: 'https://example.com/widgets/issues'});
284
+ const project = await Project.load(tmpDir);
285
+ expect(project.issuesUrl({package: '@acme/widgets'})).toBe(
286
+ 'https://example.com/widgets/issues',
287
+ );
288
+ });
289
+
290
+ it('returns undefined for an integration that ships no issuesUrl', async () => {
291
+ scaffold({integrationIssuesUrl: null});
292
+ const project = await Project.load(tmpDir);
293
+ expect(project.issuesUrl({package: '@acme/widgets'})).toBeUndefined();
294
+ });
295
+ });
296
+
297
+ describe('Project caching', () => {
298
+ it('reuses a shared cache across instances for the same config bytes', async () => {
299
+ scaffold();
300
+ const cache = new InMemoryConfigCache();
301
+ const p1 = await Project.load(tmpDir, {cache});
302
+ const comps1 = await p1.components();
303
+ const p2 = await Project.load(tmpDir, {cache});
304
+ const comps2 = await p2.components();
305
+ // Same cached value across Project instances (same key path).
306
+ expect(comps2).toBe(comps1);
307
+ });
308
+ });
@@ -0,0 +1,20 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file site.mjs — the canonical Astryx site origin for CLI code.
5
+ *
6
+ * The published Astryx site is the single source of truth for docs, the blog,
7
+ * and the MCP endpoint. Anything in the CLI that needs to reach the site reads
8
+ * this constant, so the origin is defined in exactly one place. It is
9
+ * intentionally not user-configurable: the CLI should never be pointed at an
10
+ * arbitrary host.
11
+ *
12
+ * (The docsite has its own SITE_URL in apps/docsite; the CLI is a separately
13
+ * published package and can't import from the app, so the origin is mirrored
14
+ * here for CLI use.)
15
+ */
16
+
17
+ export const SITE_URL = 'https://astryx.atmeta.com';
18
+
19
+ /** The site's origin (scheme + host), used for same-origin checks. */
20
+ export const SITE_ORIGIN = new URL(SITE_URL).origin;
@@ -0,0 +1,73 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Static template authoring API.
5
+ *
6
+ * Helpers for authoring Astryx page/block template docs. Like
7
+ * `createConfig`/`createIntegration`, these are tiny runtime identity helpers
8
+ * whose real value is the exported TypeScript surface from
9
+ * `@astryxdesign/cli/template`. They inject the discriminant `type` so a
10
+ * discovered doc always knows whether it is a page or a block. They do NOT
11
+ * validate — validation happens at the load boundary, where integration
12
+ * template discovery runs the module's default export through
13
+ * {@link TemplateEnvelopeSchema} (see `loadModuleWithSchema`).
14
+ */
15
+
16
+ import {z} from 'zod';
17
+
18
+ const PreviewSchema = z
19
+ .object({
20
+ image: z.string().optional(),
21
+ aspectRatio: z.string().optional(),
22
+ })
23
+ .strict();
24
+
25
+ /**
26
+ * Shared authored-template shape. `type` is injected by the create* helpers,
27
+ * so authors never write it. Inline source/sourceFile are intentionally NOT
28
+ * part of v1 — a template's source is the required same-stem sibling file.
29
+ * Exported so integration template discovery can validate the stamped result.
30
+ */
31
+ export const BaseTemplateSchema = z
32
+ .object({
33
+ name: z.string().min(1, 'name is required'),
34
+ description: z.string().min(1, 'description is required'),
35
+ category: z.string().optional(),
36
+ componentsUsed: z.array(z.string()).optional(),
37
+ preview: PreviewSchema.optional(),
38
+ })
39
+ .strict();
40
+
41
+ /**
42
+ * The metadata envelope integration template discovery validates: a stamped
43
+ * template doc. This is the LOAD-boundary contract — a hand-written plain
44
+ * object that matches this shape is accepted (discovery does not check "was it
45
+ * made by the factory", only the shape).
46
+ */
47
+ export const TemplateEnvelopeSchema = BaseTemplateSchema.extend({
48
+ type: z.enum(['page', 'block']),
49
+ });
50
+
51
+ /**
52
+ * Author an Astryx page template doc. Stamp-only: returns the def with
53
+ * `type: 'page'` injected. Validation happens at the load boundary.
54
+ *
55
+ * @template {import('./types/template-api').AstryxPageTemplateInput} T
56
+ * @param {T} def
57
+ * @returns {T & {type: 'page'}}
58
+ */
59
+ export function createPageTemplate(def) {
60
+ return /** @type {T & {type: 'page'}} */ ({...def, type: 'page'});
61
+ }
62
+
63
+ /**
64
+ * Author an Astryx block template doc. Stamp-only: returns the def with
65
+ * `type: 'block'` injected. Validation happens at the load boundary.
66
+ *
67
+ * @template {import('./types/template-api').AstryxBlockTemplateInput} T
68
+ * @param {T} def
69
+ * @returns {T & {type: 'block'}}
70
+ */
71
+ export function createBlockTemplate(def) {
72
+ return /** @type {T & {type: 'block'}} */ ({...def, type: 'block'});
73
+ }
@@ -0,0 +1,127 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ import {describe, it, expect} from 'vitest';
4
+ import {
5
+ createPageTemplate,
6
+ createBlockTemplate,
7
+ TemplateEnvelopeSchema,
8
+ } from './template.mjs';
9
+
10
+ // createPageTemplate/createBlockTemplate are now stamp-only: they inject the
11
+ // `type` discriminant and return the def otherwise unchanged, with NO runtime
12
+ // validation. Validation happens at the LOAD boundary (integration template
13
+ // discovery runs the default export through TemplateEnvelopeSchema), so the
14
+ // rejection cases that used to assert factory throws now assert the envelope
15
+ // schema rejects the same shapes.
16
+
17
+ describe('createPageTemplate (stamp-only)', () => {
18
+ it('returns the def with type "page" injected', () => {
19
+ const t = createPageTemplate({
20
+ name: 'Landing',
21
+ description: 'A landing page.',
22
+ });
23
+ expect(t).toEqual({
24
+ name: 'Landing',
25
+ description: 'A landing page.',
26
+ type: 'page',
27
+ });
28
+ });
29
+
30
+ it('preserves optional fields', () => {
31
+ const t = createPageTemplate({
32
+ name: 'Landing',
33
+ description: 'A landing page.',
34
+ category: 'Marketing',
35
+ componentsUsed: ['Button', 'Card'],
36
+ preview: {image: './preview.png', aspectRatio: '16 / 9'},
37
+ });
38
+ expect(t.category).toBe('Marketing');
39
+ expect(t.componentsUsed).toEqual(['Button', 'Card']);
40
+ expect(t.preview).toEqual({image: './preview.png', aspectRatio: '16 / 9'});
41
+ expect(t.type).toBe('page');
42
+ });
43
+
44
+ it('does NOT validate — returns an invalid def stamped, unchanged', () => {
45
+ const t = createPageTemplate({description: 'x'});
46
+ expect(t.type).toBe('page');
47
+ expect(t.name).toBeUndefined();
48
+ });
49
+ });
50
+
51
+ describe('createBlockTemplate (stamp-only)', () => {
52
+ it('returns the def with type "block" injected', () => {
53
+ const t = createBlockTemplate({
54
+ name: 'Hero',
55
+ description: 'A hero block.',
56
+ });
57
+ expect(t.type).toBe('block');
58
+ expect(t.name).toBe('Hero');
59
+ });
60
+ });
61
+
62
+ describe('TemplateEnvelopeSchema (load-boundary validation)', () => {
63
+ it('accepts a stamped page template', () => {
64
+ const parsed = TemplateEnvelopeSchema.parse(
65
+ createPageTemplate({name: 'Landing', description: 'A landing page.'}),
66
+ );
67
+ expect(parsed.type).toBe('page');
68
+ });
69
+
70
+ it('accepts a PLAIN OBJECT envelope (no factory required)', () => {
71
+ const parsed = TemplateEnvelopeSchema.parse({
72
+ type: 'block',
73
+ name: 'Hero',
74
+ description: 'A hero block.',
75
+ });
76
+ expect(parsed.name).toBe('Hero');
77
+ });
78
+
79
+ it('rejects a missing name', () => {
80
+ expect(() =>
81
+ TemplateEnvelopeSchema.parse({type: 'page', description: 'x'}),
82
+ ).toThrow(/name/);
83
+ });
84
+
85
+ it('rejects a missing description', () => {
86
+ expect(() =>
87
+ TemplateEnvelopeSchema.parse({type: 'page', name: 'x'}),
88
+ ).toThrow(/description/);
89
+ });
90
+
91
+ it('rejects an empty-string name', () => {
92
+ expect(() =>
93
+ TemplateEnvelopeSchema.parse({type: 'page', name: '', description: 'x'}),
94
+ ).toThrow(/name/);
95
+ });
96
+
97
+ it('rejects a missing/invalid type', () => {
98
+ expect(() =>
99
+ TemplateEnvelopeSchema.parse({name: 'x', description: 'y'}),
100
+ ).toThrow();
101
+ expect(() =>
102
+ TemplateEnvelopeSchema.parse({type: 'bogus', name: 'x', description: 'y'}),
103
+ ).toThrow();
104
+ });
105
+
106
+ it('rejects unknown keys (strict)', () => {
107
+ expect(() =>
108
+ TemplateEnvelopeSchema.parse({
109
+ type: 'page',
110
+ name: 'x',
111
+ description: 'y',
112
+ source: './x.tsx',
113
+ }),
114
+ ).toThrow();
115
+ });
116
+
117
+ it('rejects inline sourceFile (not supported in v1)', () => {
118
+ expect(() =>
119
+ TemplateEnvelopeSchema.parse({
120
+ type: 'page',
121
+ name: 'x',
122
+ description: 'y',
123
+ sourceFile: './x.tsx',
124
+ }),
125
+ ).toThrow();
126
+ });
127
+ });
@@ -33,7 +33,6 @@ import type {
33
33
  TemplateShowResponse,
34
34
  TemplateSkeletonResponse,
35
35
  TemplateCopyResponse,
36
- TemplateGetResponse,
37
36
  } from './template';
38
37
  import type {
39
38
  HookListResponse,
@@ -138,6 +137,8 @@ export interface TemplateOptions {
138
137
  show?: boolean;
139
138
  /** Filter templates by kind: 'page' or 'block'. Only applies to list views. */
140
139
  type?: 'page' | 'block';
140
+ /** Narrow to templates from a specific package (id-only lookups across packages are ambiguous). */
141
+ package?: string;
141
142
  targetPath?: string;
142
143
  cwd?: string;
143
144
  }
@@ -153,11 +154,6 @@ export declare function template(
153
154
  options?: TemplateOptions,
154
155
  ): Promise<TemplateResult>;
155
156
 
156
- export declare function getTemplateById(
157
- id: string,
158
- options?: {cwd?: string},
159
- ): Promise<TemplateGetResponse>;
160
-
161
157
  // ── Hook ─────────────────────────────────────────────────────────────
162
158
 
163
159
  export interface HookOptions {
@@ -32,7 +32,6 @@ import type {
32
32
  TemplateShowResponse,
33
33
  TemplateSkeletonResponse,
34
34
  TemplateCopyResponse,
35
- TemplateGetResponse,
36
35
  } from './template';
37
36
  import type {
38
37
  HookListResponse,
@@ -44,14 +43,11 @@ import type {
44
43
  import type {SwizzleListResponse, SwizzleCopyResponse} from './swizzle';
45
44
  import type {ThemeBuildResponse} from './theme';
46
45
  import type {UpgradeListResponse, UpgradeRunResponse} from './upgrade';
47
- import type {
48
- GapReportCategoriesResponse,
49
- GapReportFileResponse,
50
- } from './gap-report';
51
46
  import type {SearchResponse} from './search';
52
47
  import type {ErrorCode} from './error-codes';
53
48
  import type {ManifestResponse} from './manifest';
54
49
  import type {DoctorResponse} from './doctor';
50
+ import type {ValidateIntegrationResponse} from './validate-integration';
55
51
 
56
52
  /**
57
53
  * Structured error. Check `'error' in result` to discriminate.
@@ -93,7 +89,6 @@ export type CLIAnyResponse =
93
89
  | TemplateShowResponse
94
90
  | TemplateSkeletonResponse
95
91
  | TemplateCopyResponse
96
- | TemplateGetResponse
97
92
  | HookListResponse
98
93
  | HookBriefResponse
99
94
  | HookFullResponse
@@ -104,11 +99,10 @@ export type CLIAnyResponse =
104
99
  | ThemeBuildResponse
105
100
  | UpgradeListResponse
106
101
  | UpgradeRunResponse
107
- | GapReportCategoriesResponse
108
- | GapReportFileResponse
109
102
  | SearchResponse
110
103
  | ManifestResponse
111
- | DoctorResponse;
104
+ | DoctorResponse
105
+ | ValidateIntegrationResponse;
112
106
 
113
107
  /** Union of all type discriminator string literals. */
114
108
  export type CLIResponseType = CLIAnyResponse['type'];
@@ -0,0 +1,81 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * Public type surface for the file-based codemod-authoring API exported from
5
+ * `@astryxdesign/cli/codemod`.
6
+ */
7
+
8
+ /** A single source file presented to a codemod's transform. */
9
+ export interface AstryxCodemodFile {
10
+ /** Absolute path to the file being transformed. */
11
+ path: string;
12
+ /** The current source contents of the file. */
13
+ source: string;
14
+ }
15
+
16
+ /** Helpers and context passed to a codemod's transform as the second argument. */
17
+ export interface AstryxCodemodApi {
18
+ /** A jscodeshift instance configured with a parser for the file. */
19
+ jscodeshift: unknown;
20
+ /** Report a statistic (no-op-friendly; provided for jscodeshift parity). */
21
+ stats: (...args: unknown[]) => void;
22
+ /** Report progress (no-op-friendly; provided for jscodeshift parity). */
23
+ report: (...args: unknown[]) => void;
24
+ }
25
+
26
+ /**
27
+ * A codemod's transform. Return the new source to rewrite the file, or
28
+ * `null`/`undefined` to leave the file unchanged.
29
+ */
30
+ export type AstryxCodemodTransform = (
31
+ file: AstryxCodemodFile,
32
+ api: AstryxCodemodApi,
33
+ ) => string | null | undefined;
34
+
35
+ /** Definition accepted by {@link createCodemod}. */
36
+ export interface AstryxCodemodDef {
37
+ /** Short, human-readable title shown in upgrade output. */
38
+ title: string;
39
+ /** Optional longer description. */
40
+ description?: string;
41
+ /** When true, the codemod runs only when explicitly requested. */
42
+ isOptional?: boolean;
43
+ /** File extensions this codemod applies to (e.g. ['.tsx', '.ts']). */
44
+ fileExtensions?: string[];
45
+ /** The transform function. */
46
+ transform: AstryxCodemodTransform;
47
+ }
48
+
49
+ /** Result of {@link createCodemod}. */
50
+ export interface AstryxCodemod extends AstryxCodemodDef {
51
+ isOptional: boolean;
52
+ type: 'code';
53
+ }
54
+
55
+ /** Definition accepted by {@link createConfigCodemod}. */
56
+ export interface AstryxConfigCodemodDef {
57
+ /** Short, human-readable title shown in upgrade output. */
58
+ title: string;
59
+ /** Optional longer description. */
60
+ description?: string;
61
+ /** When true, the codemod runs only when explicitly requested. */
62
+ isOptional?: boolean;
63
+ /** The transform function applied to the astryx.config.* file. */
64
+ transform: AstryxCodemodTransform;
65
+ }
66
+
67
+ /** Result of {@link createConfigCodemod}. */
68
+ export interface AstryxConfigCodemod extends AstryxConfigCodemodDef {
69
+ isOptional: boolean;
70
+ type: 'config';
71
+ }
72
+
73
+ /** Define a file-transforming codemod. */
74
+ export declare function createCodemod<T extends AstryxCodemodDef>(
75
+ def: T,
76
+ ): AstryxCodemod;
77
+
78
+ /** Define a codemod that targets the consumer's astryx.config.* file. */
79
+ export declare function createConfigCodemod<T extends AstryxConfigCodemodDef>(
80
+ def: T,
81
+ ): AstryxConfigCodemod;