@astryxdesign/cli 0.1.7 → 0.1.8-canary.07362a9

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 (389) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +115 -19
  3. package/{src/api → api/blog}/blog.mjs +42 -6
  4. package/{src/api → api/blog}/blog.test.mjs +2 -2
  5. package/api/build/build.mjs +92 -0
  6. package/api/build/build.test.mjs +92 -0
  7. package/{src/api → api/component}/component.mjs +49 -26
  8. package/{src/api → api/discover}/discover.mjs +55 -18
  9. package/{src/api → api/docs}/docs.mjs +47 -21
  10. package/{src/api → api/doctor}/doctor.mjs +13 -10
  11. package/{src/api → api}/error.mjs +2 -2
  12. package/{src/api → api/hook}/hook.mjs +11 -11
  13. package/{src/api → api}/index.mjs +16 -9
  14. package/{src/api → api/integration}/validate-integration.mjs +48 -15
  15. package/{src/api → api/integration}/validate-integration.test.mjs +1 -1
  16. package/{src/api → api/layout}/layout.mjs +66 -17
  17. package/{src/api → api/layout}/layout.test.mjs +1 -1
  18. package/{src/api → api/search}/search.mjs +71 -12
  19. package/api/swizzle/swizzle.mjs +283 -0
  20. package/{src/commands → api/swizzle}/swizzle.test.mjs +26 -1
  21. package/{src/api → api/template}/template-integration.test.mjs +2 -2
  22. package/{src/api → api/template}/template-suffix.test.mjs +3 -3
  23. package/{src/api → api/template}/template.mjs +96 -19
  24. package/{src/api → api/theme}/theme-add.mjs +35 -7
  25. package/{src → authoring}/codemod.mjs +6 -6
  26. package/{src → authoring}/config.test.mjs +1 -1
  27. package/{src → authoring}/doc.mjs +1 -1
  28. package/{src → authoring}/doc.test.mjs +1 -1
  29. package/{src → authoring}/template.mjs +1 -1
  30. package/bin/astryx.mjs +12 -10
  31. package/{src → cli}/commands/blog.mjs +48 -7
  32. package/{src → cli}/commands/build-theme.mjs +133 -115
  33. package/cli/commands/build.mjs +172 -0
  34. package/cli/commands/cli-postinstall.test.mjs +42 -0
  35. package/{src → cli}/commands/component/index.mjs +52 -25
  36. package/{src → cli}/commands/component-ownership.test.mjs +3 -3
  37. package/{src → cli}/commands/component-package.test.mjs +1 -1
  38. package/{src → cli}/commands/component-resolution.test.mjs +2 -2
  39. package/{src → cli}/commands/discover.mjs +23 -12
  40. package/{src → cli}/commands/docs.mjs +42 -10
  41. package/{src → cli}/commands/docs.test.mjs +1 -1
  42. package/{src → cli}/commands/doctor.mjs +3 -3
  43. package/{src → cli}/commands/doctor.test.mjs +2 -2
  44. package/{src → cli}/commands/ensure-core-built.mjs +3 -2
  45. package/{src → cli}/commands/external-showcase.test.mjs +1 -1
  46. package/{src → cli}/commands/hook/index.mjs +40 -17
  47. package/{src → cli}/commands/import-hint-correctness.test.mjs +1 -1
  48. package/cli/commands/init.mjs +195 -0
  49. package/{src → cli}/commands/init.next-steps.test.mjs +1 -1
  50. package/cli/commands/interactive-guard.test.mjs +66 -0
  51. package/{src → cli}/commands/json-contract.test.mjs +2 -2
  52. package/{src → cli}/commands/layout.mjs +59 -19
  53. package/{src → cli}/commands/search.mjs +22 -15
  54. package/{src → cli}/commands/search.test.mjs +1 -1
  55. package/cli/commands/setup-nudge.test.mjs +108 -0
  56. package/cli/commands/swizzle.mjs +103 -0
  57. package/{src → cli}/commands/template.mjs +84 -53
  58. package/{src → cli}/commands/template.path-safety.test.mjs +2 -2
  59. package/{src → cli}/commands/upgrade.integration-policy.test.mjs +1 -1
  60. package/{src → cli}/commands/upgrade.mjs +291 -77
  61. package/cli/commands/upgrade.test.mjs +314 -0
  62. package/{src → cli}/commands/validate-integration.mjs +17 -37
  63. package/{src → cli}/index.mjs +55 -19
  64. package/{src/codemods → codemods}/__tests__/registry.test.mjs +1 -0
  65. package/{src/codemods → codemods}/ensure-jscodeshift.mjs +12 -28
  66. package/{src/codemods → codemods}/integration-discovery.mjs +6 -3
  67. package/{src/codemods → codemods}/integration-discovery.test.mjs +1 -1
  68. package/{src/codemods → codemods}/integration-runner.mjs +8 -3
  69. package/{src/codemods → codemods}/registry.mjs +4 -1
  70. package/{src/codemods → codemods}/run-codemod.mjs +18 -11
  71. package/{src/codemods → codemods}/runner.mjs +24 -14
  72. package/{src/codemods → codemods}/transforms/v0.0.10/remove-size-props.mjs +9 -4
  73. package/{src/codemods → codemods}/transforms/v0.0.12/add-is-icon-only.mjs +27 -22
  74. package/{src/codemods → codemods}/transforms/v0.0.13/icon-name-deprecations.mjs +12 -5
  75. package/{src/codemods → codemods}/transforms/v0.0.13/rename-attachments-to-drawer.mjs +15 -8
  76. package/{src/codemods → codemods}/transforms/v0.0.13/toolbar-density-to-size.mjs +10 -4
  77. package/{src/codemods → codemods}/transforms/v0.0.14/rename-action-props.mjs +11 -5
  78. package/{src/codemods → codemods}/transforms/v0.0.14/rename-section-wash-to-muted.mjs +11 -6
  79. package/{src/codemods → codemods}/transforms/v0.0.14/rename-status-variants.mjs +15 -10
  80. package/{src/codemods → codemods}/transforms/v0.0.15/migrate-item-children-to-endcontent.mjs +13 -8
  81. package/{src/codemods → codemods}/transforms/v0.0.15/migrate-selector-children-to-render-option.mjs +18 -13
  82. package/{src/codemods → codemods}/transforms/v0.0.15/migrate-theme-selectors-to-data-attrs.mjs +8 -4
  83. package/{src/codemods → codemods}/transforms/v0.0.15/rename-date-picker-to-input.mjs +11 -6
  84. package/{src/codemods → codemods}/transforms/v0.0.15/rename-imperative-ref-to-handleRef.mjs +6 -1
  85. package/{src/codemods → codemods}/transforms/v0.0.15/rename-isStreaming-to-isStopShown.mjs +6 -1
  86. package/{src/codemods → codemods}/transforms/v0.0.15/rename-stack-element-to-as.mjs +8 -3
  87. package/{src/codemods → codemods}/transforms/v0.0.2/migrate-badge-dot-to-statusdot.mjs +17 -10
  88. package/{src/codemods → codemods}/transforms/v0.0.2/migrate-gap-to-numeric.mjs +7 -1
  89. package/{src/codemods → codemods}/transforms/v0.0.2/migrate-isFullBleed-to-padding.mjs +9 -4
  90. package/{src/codemods → codemods}/transforms/v0.0.2/migrate-useXDSIcon-to-getIcon.mjs +12 -7
  91. package/{src/codemods → codemods}/transforms/v0.0.2/rename-banner-endButton-to-endContent.mjs +7 -2
  92. package/{src/codemods → codemods}/transforms/v0.0.2/rename-form-tooltip-startIcon.mjs +8 -2
  93. package/{src/codemods → codemods}/transforms/v0.0.2/rename-isShown-to-isOpen.mjs +7 -2
  94. package/{src/codemods → codemods}/transforms/v0.0.2/rename-selector-items-to-options.mjs +7 -2
  95. package/{src/codemods → codemods}/transforms/v0.0.2/rename-sidenav-header-to-heading.mjs +11 -4
  96. package/{src/codemods → codemods}/transforms/v0.0.2/rename-topnav-title-to-heading.mjs +10 -4
  97. package/{src/codemods → codemods}/transforms/v0.0.2/unify-uncontrolled-to-defaultX.mjs +7 -2
  98. package/{src/codemods → codemods}/transforms/v0.0.2/unify-visibility-to-onOpenChange.mjs +10 -5
  99. package/{src/codemods → codemods}/transforms/v0.0.6/migrate-badge-children-to-label.mjs +7 -2
  100. package/{src/codemods → codemods}/transforms/v0.0.6/migrate-collapse-to-collapsible.mjs +9 -3
  101. package/{src/codemods → codemods}/transforms/v0.0.6/migrate-radius-tokens.mjs +11 -5
  102. package/{src/codemods → codemods}/transforms/v0.0.6/migrate-shadow-tokens.mjs +13 -6
  103. package/{src/codemods → codemods}/transforms/v0.0.6/migrate-skeleton-radius.mjs +7 -1
  104. package/{src/codemods → codemods}/transforms/v0.0.6/migrate-token-names.mjs +11 -5
  105. package/{src/codemods → codemods}/transforms/v0.0.7/rename-banner-variant-to-container.mjs +8 -3
  106. package/{src/codemods → codemods}/transforms/v0.0.8/migrate-token-renames.mjs +15 -8
  107. package/{src/codemods → codemods}/transforms/v0.0.8/rename-endslot-to-endcontent.mjs +7 -2
  108. package/{src/codemods → codemods}/transforms/v0.1.0/__tests__/drop-xds-prefix-imports.test.mjs +42 -7
  109. package/{src/codemods → codemods}/transforms/v0.1.0/__tests__/migrate-xds-module-specifiers.test.mjs +43 -0
  110. package/{src/codemods → codemods}/transforms/v0.1.0/drop-xds-prefix-imports.mjs +117 -13
  111. package/{src/codemods → codemods}/transforms/v0.1.0/migrate-xds-css-surfaces.mjs +21 -17
  112. package/{src/codemods → codemods}/transforms/v0.1.0/migrate-xds-declare-module.mjs +7 -2
  113. package/{src/codemods → codemods}/transforms/v0.1.0/migrate-xds-module-specifiers.mjs +86 -15
  114. package/{src/codemods → codemods}/transforms/v0.1.2/rename-text-color-active-to-accent.mjs +15 -9
  115. package/{src/codemods → codemods}/transforms/v0.1.3/migrate-layout-components-to-experimental.mjs +17 -12
  116. package/{src/codemods → codemods}/transforms/v0.1.5/rename-switch-label-spacing-default-to-hug.mjs +15 -9
  117. package/{src/codemods → codemods}/transforms/v0.1.7/migrate-table-tableprops-to-direct-props.mjs +17 -10
  118. package/{src/codemods → codemods}/transforms/v0.1.7/rename-table-renderprops-styles-to-xstyle.mjs +18 -13
  119. package/codemods/transforms/v0.1.8/__tests__/rename-avatar-size-scale.test.mjs +161 -0
  120. package/codemods/transforms/v0.1.8/index.mjs +19 -0
  121. package/codemods/transforms/v0.1.8/rename-avatar-size-scale.mjs +239 -0
  122. package/docs/cli-integrations.doc.mjs +150 -0
  123. package/docs/elevation.doc.mjs +79 -1
  124. package/docs/getting-started.doc.mjs +9 -9
  125. package/docs/internationalization.doc.mjs +92 -46
  126. package/docs/migration.doc.mjs +18 -18
  127. package/docs/principles.doc.dense.mjs +1 -1
  128. package/docs/principles.doc.mjs +6 -6
  129. package/docs/principles.doc.zh.mjs +1 -1
  130. package/docs/styling-libraries.doc.mjs +3 -3
  131. package/docs/styling.doc.mjs +4 -4
  132. package/docs/theme.doc.dense.mjs +2 -2
  133. package/docs/theme.doc.mjs +7 -7
  134. package/docs/theme.doc.zh.mjs +1 -1
  135. package/docs/tokens.doc.mjs +1 -1
  136. package/docs/working-with-ai.doc.mjs +19 -19
  137. package/{src/commands → lib/agent-docs}/agent-docs.mjs +164 -29
  138. package/{src/commands → lib/agent-docs}/agent-docs.path-safety.test.mjs +1 -1
  139. package/{src/commands → lib/agent-docs}/agent-docs.test.mjs +123 -10
  140. package/{src/lib → lib}/cli-error.mjs +2 -2
  141. package/{src/lib → lib}/component-discovery.mjs +44 -2
  142. package/{src/lib → lib}/component-format.mjs +67 -25
  143. package/{src/lib → lib}/component-loader.mjs +27 -13
  144. package/{src/lib → lib}/componentDocOverlay.test.mjs +0 -1
  145. package/{src/lib → lib}/error-codes.test.mjs +1 -1
  146. package/{src/lib → lib}/hook-discovery.mjs +14 -1
  147. package/{src/lib → lib}/hook-format.mjs +29 -5
  148. package/{src/lib → lib}/integration-warnings.mjs +2 -2
  149. package/{src/lib → lib}/integrations.mjs +20 -0
  150. package/{src/lib → lib}/integrations.test.mjs +1 -1
  151. package/{src/lib → lib}/json-contract.test.mjs +1 -1
  152. package/{src/lib → lib}/json-shim.mjs +11 -6
  153. package/{src/lib → lib}/json-shim.test.mjs +1 -1
  154. package/{src/lib → lib}/json.mjs +3 -3
  155. package/{src/lib → lib}/levenshtein.mjs +6 -0
  156. package/{src/lib → lib}/manifest.mjs +7 -5
  157. package/{src/lib → lib}/manifest.test.mjs +2 -2
  158. package/{src/lib → lib}/module-loader.mjs +1 -0
  159. package/{src/lib → lib}/package-scanner.mjs +47 -0
  160. package/{src/lib → lib}/project.mjs +48 -18
  161. package/{src/lib → lib}/project.test.mjs +1 -1
  162. package/{src/lib → lib}/resolve-theme.mjs +5 -0
  163. package/{src/lib → lib}/string-utils.mjs +15 -0
  164. package/lib/term-log.mjs +57 -0
  165. package/{src/lib → lib}/xle/browser.mjs +18 -8
  166. package/{src/lib → lib}/xle/expand.mjs +155 -22
  167. package/{src/lib → lib}/xle/parse.mjs +119 -13
  168. package/{src/lib → lib}/xle/print.mjs +54 -14
  169. package/{src/lib → lib}/xle/registry-core.mjs +28 -3
  170. package/{src/lib → lib}/xle/registry.mjs +9 -4
  171. package/{src/lib → lib}/xle/splice.mjs +13 -2
  172. package/{src/lib → lib}/xle/validate.mjs +95 -14
  173. package/lib/xle/xle-ast.d.ts +214 -0
  174. package/package.json +37 -28
  175. package/scripts/postinstall.mjs +74 -0
  176. package/templates/blocks/components/Avatar/AvatarFallbackChain.tsx +4 -4
  177. package/templates/blocks/components/Avatar/AvatarGroup.tsx +2 -2
  178. package/templates/blocks/components/Avatar/AvatarInitialsFallback.tsx +1 -1
  179. package/templates/blocks/components/Avatar/AvatarShowcase.tsx +5 -5
  180. package/templates/blocks/components/Avatar/AvatarUserCard.tsx +1 -1
  181. package/templates/blocks/components/Avatar/AvatarWithImage.tsx +4 -4
  182. package/templates/blocks/components/Avatar/AvatarWithStatus.tsx +3 -3
  183. package/templates/blocks/components/AvatarGroup/AvatarGroupShowcase.tsx +2 -2
  184. package/templates/blocks/components/AvatarGroupOverflow/AvatarGroupOverflowCustomText.tsx +1 -1
  185. package/templates/blocks/components/AvatarGroupOverflow/AvatarGroupOverflowDefault.tsx +1 -1
  186. package/templates/blocks/components/AvatarGroupOverflow/AvatarGroupOverflowShowcase.tsx +2 -2
  187. package/templates/blocks/components/AvatarStatusDot/AvatarStatusDotShowcase.tsx +3 -3
  188. package/templates/blocks/components/AvatarStatusDot/AvatarStatusDotVariants.tsx +3 -3
  189. package/templates/blocks/components/Banner/BannerFloating.doc.mjs +14 -0
  190. package/templates/blocks/components/Banner/BannerFloating.tsx +16 -0
  191. package/templates/blocks/components/Button/ButtonFloating.doc.mjs +14 -0
  192. package/templates/blocks/components/Button/ButtonFloating.tsx +37 -0
  193. package/templates/blocks/components/ButtonGroup/ButtonGroupFloating.doc.mjs +14 -0
  194. package/templates/blocks/components/ButtonGroup/ButtonGroupFloating.tsx +23 -0
  195. package/templates/blocks/components/Card/CardElevations.doc.mjs +14 -0
  196. package/templates/blocks/components/Card/CardElevations.tsx +32 -0
  197. package/templates/blocks/components/Card/ClickableCardElevated.doc.mjs +14 -0
  198. package/templates/blocks/components/Card/ClickableCardElevated.tsx +21 -0
  199. package/templates/blocks/components/Card/SelectableCardElevated.doc.mjs +14 -0
  200. package/templates/blocks/components/Card/SelectableCardElevated.tsx +37 -0
  201. package/templates/blocks/components/Carousel/CarouselSnap.tsx +1 -1
  202. package/templates/blocks/components/ChatComposer/ChatComposerFlat.doc.mjs +14 -0
  203. package/templates/blocks/components/ChatComposer/ChatComposerFlat.tsx +75 -0
  204. package/templates/blocks/components/ChatMessage/ChatMessageAvatarName.tsx +2 -2
  205. package/templates/blocks/components/ChatMessage/ChatMessageMultiBubble.tsx +1 -1
  206. package/templates/blocks/components/ChatMessageBubble/ChatMessageBubbleGrouping.tsx +1 -1
  207. package/templates/blocks/components/ChatMessageBubble/ChatMessageBubbleMetadata.tsx +1 -1
  208. package/templates/blocks/components/ChatMessageList/ChatMessageListDensity.tsx +5 -9
  209. package/templates/blocks/components/ChatMessageList/ChatMessageListFullFeatured.tsx +1 -1
  210. package/templates/blocks/components/CodeBlock/CodeBlockTerminal.tsx +1 -1
  211. package/templates/blocks/components/HoverCard/HoverCardShowcase.tsx +1 -1
  212. package/templates/blocks/components/IconButton/IconButtonFloating.doc.mjs +14 -0
  213. package/templates/blocks/components/IconButton/IconButtonFloating.tsx +37 -0
  214. package/templates/blocks/components/InternationalizationProvider/InternationalizationProvider01ShippedLocale.doc.mjs +19 -0
  215. package/templates/blocks/components/InternationalizationProvider/InternationalizationProvider01ShippedLocale.tsx +46 -0
  216. package/templates/blocks/components/InternationalizationProvider/InternationalizationProvider02Overrides.doc.mjs +14 -0
  217. package/templates/blocks/components/InternationalizationProvider/InternationalizationProvider02Overrides.tsx +33 -0
  218. package/templates/blocks/components/Item/ItemWithMedia.tsx +2 -2
  219. package/templates/blocks/components/ListItem/ListItemWithMedia.tsx +2 -2
  220. package/templates/blocks/components/OverflowList/OverflowListCappedToolbar.doc.mjs +14 -0
  221. package/templates/blocks/components/OverflowList/OverflowListCappedToolbar.tsx +39 -0
  222. package/templates/blocks/components/OverflowList/OverflowListMultiRowTags.doc.mjs +14 -0
  223. package/templates/blocks/components/OverflowList/OverflowListMultiRowTags.tsx +44 -0
  224. package/templates/blocks/components/Pagination/PaginationDotsCarousel.tsx +2 -6
  225. package/templates/blocks/components/Stack/StackFillItem.tsx +2 -6
  226. package/templates/blocks/components/TabList/TabListTabsWithActions.doc.mjs +1 -1
  227. package/templates/blocks/components/TabList/TabListTabsWithActions.tsx +2 -7
  228. package/templates/blocks/components/Table/TableRowStatusTable.doc.mjs +14 -0
  229. package/templates/blocks/components/Table/TableRowStatusTable.tsx +59 -0
  230. package/templates/blocks/components/Thumbnail/ThumbnailDisabled.tsx +6 -1
  231. package/templates/blocks/components/Thumbnail/ThumbnailGallery.tsx +1 -0
  232. package/templates/blocks/components/Thumbnail/ThumbnailRemovable.tsx +1 -0
  233. package/templates/blocks/components/TypeaheadItem/TypeaheadItemShowcase.tsx +1 -1
  234. package/templates/pages/ai-chat/page.tsx +4 -4
  235. package/templates/pages/dashboard-portfolio/page.tsx +3 -11
  236. package/templates/pages/detail-page/page.tsx +5 -12
  237. package/templates/pages/documentation-design/page.tsx +1 -1
  238. package/templates/pages/messaging-shell/page.tsx +6 -8
  239. package/templates/pages/table-grouped/page.tsx +9 -14
  240. package/templates/pages/table-page/page.tsx +7 -9
  241. package/templates/pages/table-page-heatmap-status/page.tsx +5 -13
  242. package/{src/types → types}/api.contract.assert.ts +17 -8
  243. package/{src/types → types}/api.d.ts +110 -9
  244. package/{src/types → types}/base.d.ts +40 -4
  245. package/types/build.d.ts +53 -0
  246. package/types/codemod.d.ts +166 -0
  247. package/{src/types → types}/component.d.ts +1 -1
  248. package/{src/types → types}/discover.d.ts +1 -1
  249. package/{src/types → types}/docs.d.ts +1 -4
  250. package/{src/types → types}/hook.d.ts +1 -1
  251. package/{src/types → types}/index.d.ts +1 -0
  252. package/types/jscodeshift.d.ts +19 -0
  253. package/types/layout.d.ts +74 -0
  254. package/{src/types → types}/swizzle.d.ts +4 -0
  255. package/{src/types → types}/template-api.d.ts +4 -1
  256. package/types/theme.d.ts +53 -0
  257. package/types/upgrade.d.ts +108 -0
  258. package/utils/package-manager.mjs +178 -0
  259. package/utils/package-manager.test.mjs +220 -0
  260. package/{src/utils → utils}/path-safety.mjs +0 -18
  261. package/{src/utils → utils}/paths.mjs +4 -1
  262. package/{src/utils → utils}/update-check.mjs +5 -4
  263. package/{src/utils → utils}/update-check.test.mjs +3 -3
  264. package/docs/integration-authoring.md +0 -105
  265. package/src/commands/build.mjs +0 -196
  266. package/src/commands/init.mjs +0 -288
  267. package/src/commands/interactive-guard.test.mjs +0 -69
  268. package/src/commands/swizzle.mjs +0 -440
  269. package/src/commands/upgrade.test.mjs +0 -160
  270. package/src/types/codemod.d.ts +0 -81
  271. package/src/types/theme.d.ts +0 -23
  272. package/src/types/upgrade.d.ts +0 -64
  273. package/src/utils/interactive.mjs +0 -76
  274. package/src/utils/interactive.test.mjs +0 -70
  275. package/src/utils/package-manager.mjs +0 -74
  276. package/src/utils/package-manager.test.mjs +0 -113
  277. /package/{src/api → api/docs}/docOverlays.test.mjs +0 -0
  278. /package/{src/api → api/integration}/integration-block-exports.test.mjs +0 -0
  279. /package/{src/api → api/template}/template.test.mjs +0 -0
  280. /package/{src → authoring}/codemod.test.mjs +0 -0
  281. /package/{src → authoring}/config.mjs +0 -0
  282. /package/{src → authoring}/integration.mjs +0 -0
  283. /package/{src → authoring}/template.test.mjs +0 -0
  284. /package/{src → cli}/cli-exit-codes.test.mjs +0 -0
  285. /package/{src → cli}/commands/build-theme.color-scheme.test.mjs +0 -0
  286. /package/{src → cli}/commands/build-theme.import-path.test.mjs +0 -0
  287. /package/{src → cli}/commands/build-theme.path-safety.test.mjs +0 -0
  288. /package/{src → cli}/commands/build-theme.prose.test.mjs +0 -0
  289. /package/{src → cli}/commands/build-theme.variants.test.mjs +0 -0
  290. /package/{src → cli}/commands/build-theme.watch.test.mjs +0 -0
  291. /package/{src → cli}/commands/component.test.mjs +0 -0
  292. /package/{src → cli}/commands/detail-levels.test.mjs +0 -0
  293. /package/{src → cli}/commands/swizzle.path-safety.test.mjs +0 -0
  294. /package/{src → cli}/commands/swizzle.routing.test.mjs +0 -0
  295. /package/{src → cli}/commands/template.test.mjs +0 -0
  296. /package/{src → cli}/commands/upgrade.config-ordering.test.mjs +0 -0
  297. /package/{src → cli}/commands/validate-integration.test.mjs +0 -0
  298. /package/{src → cli}/update-hint-commands.test.mjs +0 -0
  299. /package/{src/codemods → codemods}/__tests__/rename-imperative-ref-to-handleRef.test.mjs +0 -0
  300. /package/{src/codemods → codemods}/__tests__/rename-isStreaming-to-isStopShown.test.mjs +0 -0
  301. /package/{src/codemods → codemods}/__tests__/rename-section-wash-to-muted.test.mjs +0 -0
  302. /package/{src/codemods → codemods}/__tests__/runner.test.mjs +0 -0
  303. /package/{src/codemods → codemods}/__tests__/toolbar-density-to-size.test.mjs +0 -0
  304. /package/{src/codemods → codemods}/__tests__/validation.test.mjs +0 -0
  305. /package/{src/codemods → codemods}/transforms/v0.0.10/__tests__/remove-size-props.test.mjs +0 -0
  306. /package/{src/codemods → codemods}/transforms/v0.0.10/index.mjs +0 -0
  307. /package/{src/codemods → codemods}/transforms/v0.0.12/__tests__/add-is-icon-only.test.mjs +0 -0
  308. /package/{src/codemods → codemods}/transforms/v0.0.12/index.mjs +0 -0
  309. /package/{src/codemods → codemods}/transforms/v0.0.13/__tests__/icon-name-deprecations.test.mjs +0 -0
  310. /package/{src/codemods → codemods}/transforms/v0.0.13/__tests__/rename-attachments-to-drawer.test.mjs +0 -0
  311. /package/{src/codemods → codemods}/transforms/v0.0.13/index.mjs +0 -0
  312. /package/{src/codemods → codemods}/transforms/v0.0.14/__tests__/rename-action-props.test.mjs +0 -0
  313. /package/{src/codemods → codemods}/transforms/v0.0.14/__tests__/rename-status-variants.test.mjs +0 -0
  314. /package/{src/codemods → codemods}/transforms/v0.0.14/index.mjs +0 -0
  315. /package/{src/codemods → codemods}/transforms/v0.0.15/__tests__/migrate-item-children-to-endcontent.test.mjs +0 -0
  316. /package/{src/codemods → codemods}/transforms/v0.0.15/__tests__/migrate-selector-children-to-render-option.test.mjs +0 -0
  317. /package/{src/codemods → codemods}/transforms/v0.0.15/__tests__/migrate-theme-selectors-to-data-attrs.test.mjs +0 -0
  318. /package/{src/codemods → codemods}/transforms/v0.0.15/__tests__/rename-date-picker-to-input.test.mjs +0 -0
  319. /package/{src/codemods → codemods}/transforms/v0.0.15/__tests__/rename-stack-element-to-as.test.mjs +0 -0
  320. /package/{src/codemods → codemods}/transforms/v0.0.15/index.mjs +0 -0
  321. /package/{src/codemods → codemods}/transforms/v0.0.2/__tests__/migrate-badge-dot-to-statusdot.test.mjs +0 -0
  322. /package/{src/codemods → codemods}/transforms/v0.0.2/__tests__/migrate-gap-to-numeric.test.mjs +0 -0
  323. /package/{src/codemods → codemods}/transforms/v0.0.2/__tests__/migrate-isFullBleed-to-padding.test.mjs +0 -0
  324. /package/{src/codemods → codemods}/transforms/v0.0.2/__tests__/migrate-useXDSIcon-to-getIcon.test.mjs +0 -0
  325. /package/{src/codemods → codemods}/transforms/v0.0.2/__tests__/rename-banner-endButton-to-endContent.test.mjs +0 -0
  326. /package/{src/codemods → codemods}/transforms/v0.0.2/__tests__/rename-form-tooltip-startIcon.test.mjs +0 -0
  327. /package/{src/codemods → codemods}/transforms/v0.0.2/__tests__/rename-isShown-to-isOpen.test.mjs +0 -0
  328. /package/{src/codemods → codemods}/transforms/v0.0.2/__tests__/rename-selector-items-to-options.test.mjs +0 -0
  329. /package/{src/codemods → codemods}/transforms/v0.0.2/__tests__/rename-sidenav-header-to-heading.test.mjs +0 -0
  330. /package/{src/codemods → codemods}/transforms/v0.0.2/__tests__/rename-topnav-title-to-heading.test.mjs +0 -0
  331. /package/{src/codemods → codemods}/transforms/v0.0.2/__tests__/unify-uncontrolled-to-defaultX.test.mjs +0 -0
  332. /package/{src/codemods → codemods}/transforms/v0.0.2/__tests__/unify-visibility-to-onOpenChange.test.mjs +0 -0
  333. /package/{src/codemods → codemods}/transforms/v0.0.2/index.mjs +0 -0
  334. /package/{src/codemods → codemods}/transforms/v0.0.6/__tests__/migrate-collapse-to-collapsible.test.mjs +0 -0
  335. /package/{src/codemods → codemods}/transforms/v0.0.6/__tests__/migrate-radius-tokens.test.mjs +0 -0
  336. /package/{src/codemods → codemods}/transforms/v0.0.6/__tests__/migrate-shadow-tokens.test.mjs +0 -0
  337. /package/{src/codemods → codemods}/transforms/v0.0.6/__tests__/migrate-skeleton-radius.test.mjs +0 -0
  338. /package/{src/codemods → codemods}/transforms/v0.0.6/__tests__/migrate-token-names.test.mjs +0 -0
  339. /package/{src/codemods → codemods}/transforms/v0.0.6/index.mjs +0 -0
  340. /package/{src/codemods → codemods}/transforms/v0.0.7/__tests__/rename-banner-variant-to-container.test.mjs +0 -0
  341. /package/{src/codemods → codemods}/transforms/v0.0.7/index.mjs +0 -0
  342. /package/{src/codemods → codemods}/transforms/v0.0.8/__tests__/migrate-token-renames.test.mjs +0 -0
  343. /package/{src/codemods → codemods}/transforms/v0.0.8/__tests__/rename-endslot-to-endcontent.test.mjs +0 -0
  344. /package/{src/codemods → codemods}/transforms/v0.0.8/index.mjs +0 -0
  345. /package/{src/codemods → codemods}/transforms/v0.1.0/__tests__/migrate-xds-css-surfaces.test.mjs +0 -0
  346. /package/{src/codemods → codemods}/transforms/v0.1.0/__tests__/migrate-xds-declare-module.test.mjs +0 -0
  347. /package/{src/codemods → codemods}/transforms/v0.1.0/__tests__/v0.1.0-ordering.test.mjs +0 -0
  348. /package/{src/codemods → codemods}/transforms/v0.1.0/index.mjs +0 -0
  349. /package/{src/codemods → codemods}/transforms/v0.1.2/__tests__/rename-text-color-active-to-accent.test.mjs +0 -0
  350. /package/{src/codemods → codemods}/transforms/v0.1.2/index.mjs +0 -0
  351. /package/{src/codemods → codemods}/transforms/v0.1.3/__tests__/migrate-layout-components-to-experimental.test.mjs +0 -0
  352. /package/{src/codemods → codemods}/transforms/v0.1.3/index.mjs +0 -0
  353. /package/{src/codemods → codemods}/transforms/v0.1.5/__tests__/rename-switch-label-spacing-default-to-hug.test.mjs +0 -0
  354. /package/{src/codemods → codemods}/transforms/v0.1.5/index.mjs +0 -0
  355. /package/{src/codemods → codemods}/transforms/v0.1.7/__tests__/migrate-table-tableprops-to-direct-props.test.mjs +0 -0
  356. /package/{src/codemods → codemods}/transforms/v0.1.7/__tests__/rename-table-renderprops-styles-to-xstyle.test.mjs +0 -0
  357. /package/{src/codemods → codemods}/transforms/v0.1.7/index.mjs +0 -0
  358. /package/{src/lib → lib}/cli-error.test.mjs +0 -0
  359. /package/{src/lib → lib}/component-discovery.importpath.test.mjs +0 -0
  360. /package/{src/lib → lib}/component-format.test.mjs +0 -0
  361. /package/{src/lib → lib}/component-loader.test.mjs +0 -0
  362. /package/{src/lib → lib}/config-cache.mjs +0 -0
  363. /package/{src/lib → lib}/config-cache.test.mjs +0 -0
  364. /package/{src/lib → lib}/config-schema.mjs +0 -0
  365. /package/{src/lib → lib}/error-codes.mjs +0 -0
  366. /package/{src/lib → lib}/integration-warnings.test.mjs +0 -0
  367. /package/{src/lib → lib}/module-loader.test.mjs +0 -0
  368. /package/{src/lib → lib}/node-version.mjs +0 -0
  369. /package/{src/lib → lib}/node-version.test.mjs +0 -0
  370. /package/{src/lib → lib}/parse.mjs +0 -0
  371. /package/{src/lib → lib}/site.mjs +0 -0
  372. /package/{src/lib → lib}/xle/browser.d.ts +0 -0
  373. /package/{src/lib → lib}/xle/xle.test.mjs +0 -0
  374. /package/{src/schemas → schemas}/doc-schema.mjs +0 -0
  375. /package/{src/schemas → schemas}/template-schema.mjs +0 -0
  376. /package/{src/types → types}/config.d.ts +0 -0
  377. /package/{src/types → types}/doc.d.ts +0 -0
  378. /package/{src/types → types}/doctor.d.ts +0 -0
  379. /package/{src/types → types}/error-codes.d.ts +0 -0
  380. /package/{src/types → types}/integration.d.ts +0 -0
  381. /package/{src/types → types}/manifest.d.ts +0 -0
  382. /package/{src/types → types}/search.d.ts +0 -0
  383. /package/{src/types → types}/template.d.ts +0 -0
  384. /package/{src/types → types}/validate-integration.d.ts +0 -0
  385. /package/{src/utils → utils}/github.mjs +0 -0
  386. /package/{src/utils → utils}/path-safety.test.mjs +0 -0
  387. /package/{src/utils → utils}/paths.test.mjs +0 -0
  388. /package/{src/utils → utils}/semver.mjs +0 -0
  389. /package/{src/utils → utils}/semver.test.mjs +0 -0
@@ -0,0 +1,314 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ import {describe, it, expect, beforeEach, afterEach, vi} from 'vitest';
4
+ import * as fs from 'node:fs';
5
+ import * as path from 'node:path';
6
+ import * as os from 'node:os';
7
+ import {Command} from 'commander';
8
+ import {registerUpgrade} from './upgrade.mjs';
9
+ import {generateCompressedIndex} from '../../lib/agent-docs/agent-docs.mjs';
10
+
11
+ let tmpDir;
12
+ let originalCwd;
13
+ let logCalls;
14
+ let stdoutCalls;
15
+ let exitCode;
16
+
17
+ beforeEach(() => {
18
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-upgrade-test-'));
19
+ originalCwd = process.cwd();
20
+ process.chdir(tmpDir);
21
+ logCalls = [];
22
+ stdoutCalls = [];
23
+ exitCode = undefined;
24
+ vi.spyOn(console, 'log').mockImplementation((...args) => {
25
+ logCalls.push(args.join(' '));
26
+ });
27
+ vi.spyOn(console, 'error').mockImplementation(() => {});
28
+ // Some human logs are written straight to process.stdout — capture that too.
29
+ vi.spyOn(process.stdout, 'write').mockImplementation((chunk) => {
30
+ stdoutCalls.push(typeof chunk === 'string' ? chunk : chunk.toString());
31
+ return true;
32
+ });
33
+ // jsonError() calls process.exit(1) directly. Trap it so tests can assert.
34
+ vi.spyOn(process, 'exit').mockImplementation((code) => {
35
+ exitCode = code;
36
+ throw new Error(`__exit ${code}`);
37
+ });
38
+ });
39
+
40
+ afterEach(() => {
41
+ process.chdir(originalCwd);
42
+ fs.rmSync(tmpDir, {recursive: true, force: true});
43
+ vi.restoreAllMocks();
44
+ });
45
+
46
+ function createProgram() {
47
+ const program = new Command();
48
+ program.exitOverride();
49
+ // Mirror the global --json flag from src/index.mjs so program.opts().json
50
+ // resolves the same way in tests.
51
+ program.option('--json', 'Output as typed JSON');
52
+ registerUpgrade(program);
53
+ return program;
54
+ }
55
+
56
+ function writePkg(deps = {}) {
57
+ fs.writeFileSync(
58
+ path.join(tmpDir, 'package.json'),
59
+ JSON.stringify({name: 'fixture', dependencies: deps}, null, 2),
60
+ );
61
+ }
62
+
63
+ function writeInstalledCore(version, packageName = '@astryxdesign/core') {
64
+ const parts = packageName.split('/');
65
+ const dir = path.join(tmpDir, 'node_modules', ...parts);
66
+ fs.mkdirSync(dir, {recursive: true});
67
+ fs.writeFileSync(
68
+ path.join(dir, 'package.json'),
69
+ JSON.stringify({name: packageName, version}, null, 2),
70
+ );
71
+ }
72
+
73
+ function writeSourceFile() {
74
+ fs.mkdirSync(path.join(tmpDir, 'src'), {recursive: true});
75
+ fs.writeFileSync(path.join(tmpDir, 'src', 'index.ts'), 'const x = 1;\n');
76
+ }
77
+
78
+ /** Run a command and capture the parsed JSON response (last printed JSON line). */
79
+ async function runJson(args) {
80
+ const program = createProgram();
81
+ try {
82
+ await program.parseAsync(['node', 'astryx', ...args]);
83
+ } catch (err) {
84
+ if (!String(err?.message || '').startsWith('__exit')) throw err;
85
+ }
86
+ // Find the most recent stringified JSON envelope in console.log output.
87
+ for (let i = logCalls.length - 1; i >= 0; i--) {
88
+ const line = logCalls[i];
89
+ if (line.startsWith('{')) {
90
+ try {
91
+ return JSON.parse(line);
92
+ } catch {
93
+ // not JSON, keep looking
94
+ }
95
+ }
96
+ }
97
+ return null;
98
+ }
99
+
100
+ describe('upgrade gate (semver comparison)', () => {
101
+ it('does NOT block an upgrade from 0.0.9 to installed 0.0.10 (regression)', async () => {
102
+ // The original bug: string compare said '0.0.9' >= '0.0.10', so the
103
+ // gate told users "Already up to date" without --force.
104
+ writePkg();
105
+ writeInstalledCore('0.0.10');
106
+ writeSourceFile();
107
+
108
+ const result = await runJson(['--json', 'upgrade', '--from', '0.0.9', '--path', 'src']);
109
+ expect(result).not.toBeNull();
110
+ expect(result.type === 'upgrade.run' || result.error || logCalls.some(l => l.includes('No codemods'))).toBeTruthy();
111
+ });
112
+
113
+ it('blocks when --from >= installed target by semver (e.g. 0.0.10 → 0.0.9)', async () => {
114
+ writePkg();
115
+ writeInstalledCore('0.0.9');
116
+ const program = createProgram();
117
+ await program.parseAsync(['node', 'astryx', 'upgrade', '--from', '0.0.10']);
118
+ const output = stdoutCalls.join('') + logCalls.join('\n');
119
+ expect(output).toMatch(/up to date|Already/i);
120
+ });
121
+ });
122
+
123
+ describe('upgrade argument validation', () => {
124
+ it('requires --from for upgrade runs', async () => {
125
+ writePkg();
126
+ writeInstalledCore('0.0.15');
127
+ const result = await runJson(['--json', 'upgrade']);
128
+ expect(result).not.toBeNull();
129
+ expect(result.error).toMatch(/Missing required --from/);
130
+ expect(exitCode).toBe(1);
131
+ });
132
+
133
+ it('rejects bogus --from values', async () => {
134
+ writePkg();
135
+ writeInstalledCore('0.0.15');
136
+ const result = await runJson(['--json', 'upgrade', '--from', 'not-a-version']);
137
+ expect(result).not.toBeNull();
138
+ expect(result.error).toMatch(/Invalid --from/);
139
+ expect(exitCode).toBe(1);
140
+ });
141
+
142
+ it('detects the installed target version from @astryxdesign/core', async () => {
143
+ writePkg();
144
+ writeInstalledCore('0.0.15');
145
+ writeSourceFile();
146
+ const result = await runJson(['--json', 'upgrade', '--from', '0.0.14', '--path', 'src']);
147
+ expect(result?.error || '').not.toMatch(/Could not find installed/);
148
+ });
149
+ });
150
+
151
+ describe('upgrade --list dedup', () => {
152
+ it('lists each codemod exactly once', async () => {
153
+ const result = await runJson(['--json', 'upgrade', '--list']);
154
+ expect(result).not.toBeNull();
155
+ expect(result.type).toBe('upgrade.list');
156
+ const names = result.data.map((c) => c.name);
157
+ const unique = new Set(names);
158
+ // The bug: 31 unique codemods printed 9× → 201 entries.
159
+ expect(names.length).toBe(unique.size);
160
+ });
161
+ });
162
+
163
+ // Issue #4168 — `upgrade` must refresh the managed agent-docs block after a
164
+ // version bump, on EVERY path (the block documents the installed library, not
165
+ // the codemod outcome), and must NOT write during a dry run.
166
+ describe('upgrade agent-docs refresh (#4168)', () => {
167
+ /** Write an agent-doc file with a managed block generated for `version`. */
168
+ function writeAgentBlock(rel, version) {
169
+ const filePath = path.join(tmpDir, rel);
170
+ fs.mkdirSync(path.dirname(filePath), {recursive: true});
171
+ fs.writeFileSync(filePath, `# Doc\n\n${generateCompressedIndex(version)}\n`);
172
+ }
173
+
174
+ it('refreshes a stale block with --apply, even when there are no codemods (up-to-date path)', async () => {
175
+ writePkg();
176
+ writeInstalledCore('0.0.15');
177
+ writeAgentBlock('AGENTS.md', '0.0.1');
178
+
179
+ // --from == installed → up-to-date short-circuit: no codemods run, yet the
180
+ // stale block must still be brought current. This is the exact gap in #4168.
181
+ const result = await runJson(['--json', 'upgrade', '--from', '0.0.15', '--apply']);
182
+
183
+ expect(result.type).toBe('upgrade.status');
184
+ expect(result.data.status).toBe('up_to_date');
185
+ expect(result.data.agentDocs.status).toBe('stale');
186
+ expect(result.data.agentDocs.action).toBe('refreshed');
187
+ expect(result.data.agentDocs.refreshed).toBe(true);
188
+ expect(result.data.agentDocs.fromVersions).toEqual(['0.0.1']);
189
+ expect(result.data.agentDocs.files).toContain('AGENTS.md');
190
+
191
+ const content = fs.readFileSync(path.join(tmpDir, 'AGENTS.md'), 'utf-8');
192
+ expect(content).toMatch(/Astryx v0\.0\.15 ·/);
193
+ expect(content).not.toMatch(/Astryx v0\.0\.1 ·/);
194
+ });
195
+
196
+ it('reports a stale block on a dry run WITHOUT writing', async () => {
197
+ writePkg();
198
+ writeInstalledCore('0.0.15');
199
+ writeAgentBlock('AGENTS.md', '0.0.1');
200
+ const before = fs.readFileSync(path.join(tmpDir, 'AGENTS.md'), 'utf-8');
201
+
202
+ const result = await runJson(['--json', 'upgrade', '--from', '0.0.15']); // no --apply
203
+
204
+ expect(result.data.agentDocs.status).toBe('stale');
205
+ expect(result.data.agentDocs.action).toBe('would-refresh');
206
+ expect(result.data.agentDocs.refreshed).toBe(false);
207
+ // The file must be untouched by a dry run.
208
+ expect(fs.readFileSync(path.join(tmpDir, 'AGENTS.md'), 'utf-8')).toBe(before);
209
+ });
210
+
211
+ it('refreshes a stale block on the no-codemods path (--apply)', async () => {
212
+ writePkg();
213
+ writeInstalledCore('0.0.11'); // (0.0.10, 0.0.11] has no registered codemods
214
+ writeSourceFile();
215
+ writeAgentBlock('CLAUDE.md', '0.0.1');
216
+
217
+ const result = await runJson(['--json', 'upgrade', '--from', '0.0.10', '--apply', '--path', 'src']);
218
+
219
+ expect(result.type).toBe('upgrade.status');
220
+ expect(result.data.status).toBe('no_codemods');
221
+ expect(result.data.agentDocs.refreshed).toBe(true);
222
+ expect(fs.readFileSync(path.join(tmpDir, 'CLAUDE.md'), 'utf-8')).toMatch(/Astryx v0\.0\.11 ·/);
223
+ });
224
+
225
+ it('nudges to run init when core is installed but no managed block exists', async () => {
226
+ writePkg();
227
+ writeInstalledCore('0.0.15');
228
+ // No agent-doc files at all.
229
+
230
+ const result = await runJson(['--json', 'upgrade', '--from', '0.0.15', '--apply']);
231
+
232
+ expect(result.data.agentDocs.status).toBe('missing');
233
+ expect(result.data.agentDocs.action).toBe('nudge-init');
234
+ expect(result.data.agentDocs.refreshed).toBe(false);
235
+ // Must NOT silently create docs during an upgrade.
236
+ expect(fs.existsSync(path.join(tmpDir, 'AGENTS.md'))).toBe(false);
237
+ expect(fs.existsSync(path.join(tmpDir, '.claude', 'CLAUDE.md'))).toBe(false);
238
+ });
239
+
240
+ it('treats an agent file without our markers as "never initialized" (missing)', async () => {
241
+ writePkg();
242
+ writeInstalledCore('0.0.15');
243
+ fs.writeFileSync(path.join(tmpDir, 'AGENTS.md'), '# Agents\n\nHand-written, no astryx block.\n');
244
+ const before = fs.readFileSync(path.join(tmpDir, 'AGENTS.md'), 'utf-8');
245
+
246
+ const result = await runJson(['--json', 'upgrade', '--from', '0.0.15', '--apply']);
247
+
248
+ expect(result.data.agentDocs.status).toBe('missing');
249
+ expect(result.data.agentDocs.action).toBe('nudge-init');
250
+ // An unmarked file is left exactly as-is.
251
+ expect(fs.readFileSync(path.join(tmpDir, 'AGENTS.md'), 'utf-8')).toBe(before);
252
+ });
253
+
254
+ it('does not silently no-op a stale block with malformed markers (START without END)', async () => {
255
+ writePkg();
256
+ writeInstalledCore('0.0.15');
257
+ // A corrupted block: START + version header but no matching END marker, so
258
+ // the writer can't safely splice it. This must not crash, corrupt the file,
259
+ // or falsely claim success — it should surface an error and nudge re-init.
260
+ const corrupted =
261
+ '# A\n\n<!-- ASTRYX:START -->\nAstryx v0.0.1 · 9 components\nguidance, but no end marker\n';
262
+ fs.writeFileSync(path.join(tmpDir, 'AGENTS.md'), corrupted);
263
+
264
+ const result = await runJson(['--json', 'upgrade', '--from', '0.0.15', '--apply']);
265
+
266
+ expect(result.data.agentDocs.status).toBe('stale');
267
+ expect(result.data.agentDocs.action).toBe('error');
268
+ expect(result.data.agentDocs.refreshed).toBe(false);
269
+ // File left byte-for-byte intact — never corrupted.
270
+ expect(fs.readFileSync(path.join(tmpDir, 'AGENTS.md'), 'utf-8')).toBe(corrupted);
271
+ });
272
+
273
+ it('stays silent when the block already matches the installed version', async () => {
274
+ writePkg();
275
+ writeInstalledCore('0.0.15');
276
+ writeAgentBlock('AGENTS.md', '0.0.15');
277
+
278
+ const result = await runJson(['--json', 'upgrade', '--from', '0.0.15', '--apply']);
279
+
280
+ expect(result.data.agentDocs.status).toBe('current');
281
+ expect(result.data.agentDocs.action).toBe('none');
282
+ expect(result.data.agentDocs.refreshed).toBe(false);
283
+ });
284
+
285
+ it('migrates a legacy XDS block to the current Astryx block (--apply)', async () => {
286
+ writePkg();
287
+ writeInstalledCore('0.0.15');
288
+ fs.writeFileSync(
289
+ path.join(tmpDir, 'CLAUDE.md'),
290
+ '# Claude\n\n<!-- XDS:START -->\nlegacy index\n<!-- XDS:END -->\n\nMore rules.\n',
291
+ );
292
+
293
+ const result = await runJson(['--json', 'upgrade', '--from', '0.0.15', '--apply']);
294
+
295
+ expect(result.data.agentDocs.status).toBe('stale');
296
+ expect(result.data.agentDocs.refreshed).toBe(true);
297
+ const content = fs.readFileSync(path.join(tmpDir, 'CLAUDE.md'), 'utf-8');
298
+ expect(content).toContain('<!-- ASTRYX:START -->');
299
+ expect(content).toMatch(/Astryx v0\.0\.15 ·/);
300
+ expect(content).not.toContain('legacy index');
301
+ expect(content).toContain('More rules.');
302
+ });
303
+
304
+ it('surfaces agentDocs in the human output on the up-to-date path', async () => {
305
+ writePkg();
306
+ writeInstalledCore('0.0.15');
307
+ writeAgentBlock('AGENTS.md', '0.0.1');
308
+
309
+ const program = createProgram();
310
+ await program.parseAsync(['node', 'astryx', 'upgrade', '--from', '0.0.15', '--apply']);
311
+ const output = stdoutCalls.join('') + logCalls.join('\n');
312
+ expect(output).toMatch(/Agent docs refreshed/i);
313
+ });
314
+ });
@@ -14,37 +14,34 @@
14
14
  * an integration package is not a failure).
15
15
  */
16
16
 
17
- import {jsonOut, humanLog} from '../lib/json.mjs';
17
+ import {jsonOut, humanLog} from '../../lib/json.mjs';
18
18
  import {
19
- validateLocalIntegration,
20
- validateInstalledIntegration,
19
+ validateIntegration,
21
20
  summarizeIssues,
22
- } from '../api/validate-integration.mjs';
21
+ } from '../../api/integration/validate-integration.mjs';
23
22
 
24
23
  /**
25
24
  * Render a validation result for humans.
26
- * @param {import('../api/validate-integration.mjs').ValidateResult} result
25
+ * @param {import('../../types/validate-integration').ValidateIntegrationResponse['data']} data
27
26
  */
28
- function printHuman(result) {
27
+ function printHuman(data) {
29
28
  const label =
30
- result.version != null
31
- ? `${result.name}@${result.version}`
32
- : result.name;
29
+ data.version != null ? `${data.name}@${data.version}` : data.name;
33
30
  humanLog(`Validating integration: ${label}`);
34
31
 
35
- if (result.issues.length === 0) {
32
+ if (data.issues.length === 0) {
36
33
  humanLog('\n\u2713 No issues found.');
37
34
  return;
38
35
  }
39
36
 
40
37
  humanLog('');
41
- for (const issue of result.issues) {
38
+ for (const issue of data.issues) {
42
39
  humanLog(` ${issue.severity} ${issue.code}: ${issue.message}`);
43
40
  }
44
41
 
45
- const {errors, warnings} = summarizeIssues(result.issues);
42
+ const {errors, warnings} = summarizeIssues(data.issues);
46
43
  humanLog(
47
- `\n${result.issues.length} issue(s): ${errors} error(s), ${warnings} warning(s)`,
44
+ `\n${data.issues.length} issue(s): ${errors} error(s), ${warnings} warning(s)`,
48
45
  );
49
46
  }
50
47
 
@@ -74,35 +71,18 @@ export function registerValidateIntegration(program) {
74
71
  .action(async pkg => {
75
72
  const json = program.opts().json || false;
76
73
 
77
- const result = pkg
78
- ? await validateInstalledIntegration(pkg)
79
- : await validateLocalIntegration();
80
-
81
- // No-arg + no local manifest: guidance, not an error.
82
- if (!result.found) {
83
- if (json) {
84
- jsonOut('integration.validate', {
85
- name: null,
86
- version: null,
87
- issues: [],
88
- });
89
- } else {
90
- humanLog(NO_MANIFEST_GUIDANCE);
91
- }
92
- return;
93
- }
74
+ const result = await validateIntegration(pkg);
94
75
 
95
76
  if (json) {
96
- jsonOut('integration.validate', {
97
- name: result.name ?? null,
98
- version: result.version ?? null,
99
- issues: result.issues,
100
- });
77
+ jsonOut(result.type, result.data);
78
+ } else if (result.data.name === null) {
79
+ // No-arg + no local manifest: guidance, not an error.
80
+ humanLog(NO_MANIFEST_GUIDANCE);
101
81
  } else {
102
- printHuman(result);
82
+ printHuman(result.data);
103
83
  }
104
84
 
105
- const {errors} = summarizeIssues(result.issues);
85
+ const {errors} = summarizeIssues(result.data.issues);
106
86
  if (errors > 0) {
107
87
  process.exitCode = 1;
108
88
  }
@@ -11,14 +11,15 @@ import {Command, Option} from 'commander';
11
11
  import {fileURLToPath} from 'node:url';
12
12
  import * as fs from 'node:fs';
13
13
  import * as path from 'node:path';
14
- import {checkForUpdate} from './utils/update-check.mjs';
15
- import {getRunPrefix} from './utils/package-manager.mjs';
16
- import {API_VERSION, setJsonMode} from './lib/json.mjs';
17
- import {buildManifest} from './lib/manifest.mjs';
18
- import {cliError} from './lib/cli-error.mjs';
19
- import {ERROR_CODES} from './lib/error-codes.mjs';
20
- import {levenshteinDistance} from './lib/string-utils.mjs';
21
- import {installJsonShim} from './lib/json-shim.mjs';
14
+ import {checkForUpdate} from '../utils/update-check.mjs';
15
+ import {getCliInvocation} from '../utils/package-manager.mjs';
16
+ import {API_VERSION, setJsonMode} from '../lib/json.mjs';
17
+ import {buildManifest} from '../lib/manifest.mjs';
18
+ import {cliError} from '../lib/cli-error.mjs';
19
+ import {ERROR_CODES} from '../lib/error-codes.mjs';
20
+ import {levenshteinDistance} from '../lib/string-utils.mjs';
21
+ import {installJsonShim} from '../lib/json-shim.mjs';
22
+ import {isAstryxInitialized} from '../lib/agent-docs/agent-docs.mjs';
22
23
 
23
24
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
24
25
 
@@ -55,6 +56,7 @@ export const JSON_SUPPORTED = new Set([
55
56
  'docs',
56
57
  'discover',
57
58
  'search',
59
+ 'build',
58
60
  'swizzle',
59
61
  'template',
60
62
  'hook',
@@ -101,7 +103,7 @@ program
101
103
  if (extras.length > 0) {
102
104
  const unknown = String(extras[0]);
103
105
  const known = (program.commands || [])
104
- .filter((c) => !c._hidden && c.name() !== 'help')
106
+ .filter((c) => !(/** @type {any} */ (c)._hidden) && c.name() !== 'help')
105
107
  .map((c) => c.name());
106
108
  const close = known
107
109
  .map((name) => ({name, distance: levenshteinDistance(unknown.toLowerCase(), name.toLowerCase())}))
@@ -161,6 +163,7 @@ program
161
163
  */
162
164
  function fullCommandName(actionCommand) {
163
165
  const parts = [];
166
+ /** @type {import('commander').Command | null} */
164
167
  let cmd = actionCommand;
165
168
  while (cmd && cmd !== program) {
166
169
  parts.unshift(cmd.name());
@@ -174,7 +177,7 @@ function fullCommandName(actionCommand) {
174
177
  *
175
178
  * If --json is set on a command that is not on the JSON_SUPPORTED allowlist,
176
179
  * emit a structured error envelope and exit 1 — without running the command's
177
- * action (so no filesystem mutations, no clack prompts, no spawned processes).
180
+ * action (so no filesystem mutations, no interactive prompts, no spawned processes).
178
181
  *
179
182
  * This is the single source of truth for "command does not support --json".
180
183
  * Individual commands should NOT re-check this; they may assume that if their
@@ -237,6 +240,40 @@ program.hook('postAction', (thisCommand, actionCommand) => {
237
240
  }
238
241
  });
239
242
 
243
+ /**
244
+ * Enforcement layer 3 — setup nudge. If this project hasn't run `astryx init`
245
+ * yet (no Astryx marker in any agent-doc file — see isAstryxInitialized), remind
246
+ * the user/agent that setup is missing.
247
+ *
248
+ * Uses `preAction` (not postAction) so it fires for EVERY valid command — even
249
+ * ones whose action errors or calls process.exit (postAction is skipped then).
250
+ *
251
+ * Suppressed in --json: that is machine output with a strict clean stdout+stderr
252
+ * contract (json-shim.test: "error envelopes have empty stderr"), and --json
253
+ * consumers parse stdout, not stderr — a stderr nudge would not reach them anyway.
254
+ * The core/cli postinstall layers already nudge at install time regardless of
255
+ * --json; a machine-readable nudge could later be an envelope field. Also skipped
256
+ * for the installer commands themselves and outside a project (no package.json).
257
+ */
258
+ const SETUP_NUDGE_EXEMPT = new Set(['init', 'agent-docs']);
259
+ program.hook('preAction', (thisCommand, actionCommand) => {
260
+ try {
261
+ if (program.opts().json) return; // machine mode — keep --json output clean
262
+ if (SETUP_NUDGE_EXEMPT.has(actionCommand.name())) return;
263
+ const cwd = process.cwd();
264
+ if (!fs.existsSync(path.join(cwd, 'package.json'))) return; // not a project
265
+ if (isAstryxInitialized(cwd)) return; // already set up — stay quiet
266
+ // Same wording as the core/cli postinstall nudges. #4151's getCliInvocation()
267
+ // renders the correct form for THIS project — scoped `npx @astryxdesign/cli`
268
+ // one-off, or `<pm> astryx` when installed — never the bare `npx astryx` footgun.
269
+ console.error(
270
+ `\nNext step: run \`${getCliInvocation(cwd)} init\` to finish setup and install the Astryx agent prompt.`,
271
+ );
272
+ } catch {
273
+ // Never let the nudge break a command.
274
+ }
275
+ });
276
+
240
277
  /**
241
278
  * Command registry — each command is lazy-loaded so a broken command
242
279
  * doesn't take down the entire CLI.
@@ -272,10 +309,10 @@ for (const cmd of commands) {
272
309
  // Command fails to load but CLI still works
273
310
  program
274
311
  .command(cmd.name)
275
- .description(`(failed to load: ${e.message})`)
312
+ .description(`(failed to load: ${/** @type {any} */ (e).message})`)
276
313
  .action(() => {
277
314
  console.error(`Command "${cmd.name}" failed to load:`);
278
- console.error(e.message);
315
+ console.error(/** @type {any} */ (e).message);
279
316
  process.exit(1);
280
317
  });
281
318
  }
@@ -305,25 +342,24 @@ program
305
342
  console.log(` ${c.name}${tag}`);
306
343
  if (c.description) console.log(` ${c.description}`);
307
344
  }
308
- console.log(`\nRun \`astryx manifest --json\` for the full structured manifest.\n`);
345
+ console.log(`\nRun \`${getCliInvocation()} manifest --json\` for the full structured manifest.\n`);
309
346
  });
310
347
 
311
348
  // Hidden command used by package.json postinstall scripts
312
349
  program
313
350
  .command('postinstall', {hidden: true})
314
351
  .action(() => {
315
- const run = getRunPrefix();
316
- const r = `${run} xds`;
317
- const pad = (s, len) => s + ' '.repeat(Math.max(0, len - s.length));
352
+ const r = getCliInvocation();
353
+ const pad = (/** @type {string} */ s, /** @type {number} */ len) => s + ' '.repeat(Math.max(0, len - s.length));
318
354
  const W = 49; // inner width of the box
319
- const line = (s) => ` │ ${pad(s, W)}│`;
355
+ const line = (/** @type {string} */ s) => ` │ ${pad(s, W)}│`;
320
356
  console.log(`
321
357
  ╭${'─'.repeat(W + 2)}╮
322
358
  ${line('')}
323
359
  ${line(' Design system installed!')}
324
360
  ${line('')}
325
361
  ${line(' Get started:')}
326
- ${line(` ${r} init Interactive setup`)}
362
+ ${line(` ${r} init Setup + AI agent docs`)}
327
363
  ${line(` ${r} --help See all commands`)}
328
364
  ${line('')}
329
365
  ${line(' Or run directly:')}
@@ -342,5 +378,5 @@ ${line('')}
342
378
  // patch outputHelp on every command (root + subcommands). The shim
343
379
  // extends the --json contract to cover Commander's parse-time short
344
380
  // circuits (parse errors, unknown options, --help, unknown commands).
345
- // See packages/cli/src/lib/json-shim.mjs for the rationale.
381
+ // See packages/cli/lib/json-shim.mjs for the rationale.
346
382
  installJsonShim(program);
@@ -21,6 +21,7 @@ describe('registry', () => {
21
21
  '0.1.3',
22
22
  '0.1.5',
23
23
  '0.1.7',
24
+ '0.1.8',
24
25
  ]);
25
26
  });
26
27
  });
@@ -3,16 +3,15 @@
3
3
  /**
4
4
  * @file Lazy jscodeshift installer
5
5
  *
6
- * Checks if jscodeshift is available and offers to install it on-demand.
6
+ * Checks if jscodeshift is available and installs it on-demand.
7
7
  * Keeps the CLI lean — jscodeshift is only needed for codemods.
8
8
  *
9
- * In non-interactive environments (CI, LLM agents), the interactive prompt
10
- * is skipped. Pass `installDeps: true` to auto-install without prompting,
11
- * or the command will fail with a helpful error message.
9
+ * Non-interactive by default (no prompts): pass `installDeps: true` to
10
+ * auto-install, otherwise the command fails fast with a helpful error.
12
11
  */
13
12
 
14
13
  import {execSync} from 'node:child_process';
15
- import * as p from '@clack/prompts';
14
+ import * as p from '../lib/term-log.mjs';
16
15
  import {detectPackageManager} from '../utils/package-manager.mjs';
17
16
 
18
17
  /**
@@ -30,32 +29,17 @@ export async function ensureJscodeshift({installDeps = false, silent = false} =
30
29
  } catch {
31
30
  log.warn('jscodeshift is required for codemods but not installed.');
32
31
 
33
- const isInteractive = process.stdout.isTTY && !process.env.CI;
34
-
35
32
  if (installDeps) {
36
- // Explicit opt-in — install without prompting
33
+ // Explicit opt-in — install without prompting.
37
34
  return installJscodeshift(silent);
38
35
  }
39
36
 
40
- if (!isInteractive || silent) {
41
- // Non-interactive environment (or --json) — fail fast with a helpful message
42
- log.error(
43
- 'Cannot run codemods without jscodeshift. ' +
44
- 'Use --install-deps to auto-install in non-interactive environments.',
45
- );
46
- return false;
47
- }
48
-
49
- // Interactive TTY — prompt as before
50
- const shouldInstall = await p.confirm({
51
- message: 'Install jscodeshift now?',
52
- initialValue: true,
53
- });
54
- if (p.isCancel(shouldInstall) || !shouldInstall) {
55
- p.log.error('Cannot run codemods without jscodeshift.');
56
- return false;
57
- }
58
- return installJscodeshift(silent);
37
+ // Non-interactive by default: fail fast with a helpful message instead of
38
+ // prompting (the CLI never blocks on a TTY).
39
+ log.error(
40
+ 'Cannot run codemods without jscodeshift. Use --install-deps to auto-install.',
41
+ );
42
+ return false;
59
43
  }
60
44
  }
61
45
 
@@ -80,7 +64,7 @@ function installJscodeshift(silent = false) {
80
64
  log.success('jscodeshift installed.');
81
65
  return true;
82
66
  } catch (err) {
83
- log.error(`Failed to install jscodeshift: ${err.message}`);
67
+ log.error(`Failed to install jscodeshift: ${/** @type {any} */ (err).message}`);
84
68
  return false;
85
69
  }
86
70
  }
@@ -27,7 +27,7 @@
27
27
  import * as fs from 'node:fs';
28
28
  import * as path from 'node:path';
29
29
  import {loadModuleWithSchema} from '../lib/module-loader.mjs';
30
- import {CodemodEnvelopeSchema} from '../codemod.mjs';
30
+ import {CodemodEnvelopeSchema} from '../authoring/codemod.mjs';
31
31
  import {semverCompare} from '../utils/semver.mjs';
32
32
 
33
33
  /** File extensions recognized as codemod modules. */
@@ -41,6 +41,7 @@ const CODEMOD_EXTENSIONS = ['.ts', '.mjs', '.js'];
41
41
  * @returns {Array<{id: string, file: string}>}
42
42
  */
43
43
  function collectCodemodFiles(versionDir) {
44
+ /** @type {Array<{id: string, file: string}>} */
44
45
  const out = [];
45
46
 
46
47
  /** @param {string} dir */
@@ -74,7 +75,7 @@ function collectCodemodFiles(versionDir) {
74
75
  * for that version (across all integrations).
75
76
  */
76
77
  export async function discoverIntegrationCodemods(loadedIntegrations = []) {
77
- /** @type {Map<string, Array<{id: string, type: string, codemod: object, package: string}>>} */
78
+ /** @type {Map<string, Array<{id: string, type: 'code'|'config', codemod: object, package: string}>>} */
78
79
  const byVersion = new Map();
79
80
 
80
81
  for (const integration of loadedIntegrations ?? []) {
@@ -158,10 +159,12 @@ export async function discoverIntegrationCodemods(loadedIntegrations = []) {
158
159
  */
159
160
  export function selectIntegrationCodemods(byVersion, from, to) {
160
161
  const versions = [...byVersion.keys()].sort(semverCompare);
162
+ /** @type {Array<{version: string, codemods: Array<object>}>} */
161
163
  const results = [];
162
164
  for (const version of versions) {
163
165
  if (semverCompare(version, from) > 0 && semverCompare(version, to) <= 0) {
164
- results.push({version, codemods: byVersion.get(version)});
166
+ const codemods = byVersion.get(version);
167
+ if (codemods) results.push({version, codemods});
165
168
  }
166
169
  }
167
170
  return results;