@astryxdesign/cli 0.6.3 → 0.6.4-canary.06c8fa3

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 (670) hide show
  1. package/CHANGELOG.md +194 -0
  2. package/README.md +121 -85
  3. package/api/blog/blog.doc.mjs +1 -0
  4. package/api/build/_adapter.d.mts +50 -0
  5. package/api/build/_adapter.mjs +60 -0
  6. package/api/build/build.doc.mjs +16 -9
  7. package/api/build/build.test.mjs +197 -8
  8. package/api/build/build.type.d.mts +91 -2
  9. package/api/build/build.type.mjs +52 -8
  10. package/api/build/help/help.d.mts +12 -5
  11. package/api/build/help/help.mjs +69 -6
  12. package/api/build/kit/kit.d.mts +4 -1
  13. package/api/build/kit/kit.mjs +165 -49
  14. package/api/build/kit/rank.d.mts +44 -0
  15. package/api/build/kit/rank.mjs +432 -0
  16. package/api/build/kit/rank.test.mjs +196 -0
  17. package/api/component/_adapter.d.mts +31 -12
  18. package/api/component/_adapter.mjs +79 -15
  19. package/api/component/component.d.mts +6 -3
  20. package/api/component/component.doc.mjs +36 -13
  21. package/api/component/component.mjs +339 -22
  22. package/api/component/component.test.mjs +38 -0
  23. package/api/component/component.type.d.mts +47 -11
  24. package/api/component/component.type.mjs +76 -24
  25. package/api/component/detail/blocks/blocks.d.mts +2 -1
  26. package/api/component/detail/blocks/blocks.mjs +4 -3
  27. package/api/component/list/list.d.mts +0 -5
  28. package/api/component/list/list.mjs +40 -11
  29. package/api/discover/_adapter.d.mts +114 -6
  30. package/api/discover/_adapter.mjs +372 -17
  31. package/api/discover/_adapter.test.mjs +215 -0
  32. package/api/discover/_catalog-view.d.mts +115 -0
  33. package/api/discover/_catalog-view.mjs +203 -0
  34. package/api/discover/_catalog-view.test.mjs +128 -0
  35. package/api/discover/detail/detail.d.mts +18 -6
  36. package/api/discover/detail/detail.mjs +67 -13
  37. package/api/discover/detail/detail.test.mjs +85 -0
  38. package/api/discover/detail/item/item.d.mts +26 -0
  39. package/api/discover/detail/item/item.mjs +78 -0
  40. package/api/discover/detail/item/item.test.mjs +73 -0
  41. package/api/discover/discover.d.mts +3 -9
  42. package/api/discover/discover.doc.mjs +62 -18
  43. package/api/discover/discover.mjs +220 -36
  44. package/api/discover/discover.test.mjs +11 -2
  45. package/api/discover/discover.type.d.mts +150 -11
  46. package/api/discover/discover.type.mjs +107 -17
  47. package/api/discover/list/list.d.mts +20 -6
  48. package/api/discover/list/list.mjs +45 -12
  49. package/api/discover/list/list.test.mjs +46 -0
  50. package/api/discover/search/search.d.mts +18 -16
  51. package/api/discover/search/search.mjs +102 -56
  52. package/api/discover/search/search.test.mjs +144 -10
  53. package/api/docs/_adapter.d.mts +272 -41
  54. package/api/docs/_adapter.mjs +985 -108
  55. package/api/docs/compiled-topics.test.mjs +78 -0
  56. package/api/docs/detail/detail.mjs +22 -63
  57. package/api/docs/detail/section/section.d.mts +1 -1
  58. package/api/docs/detail/section/section.mjs +54 -19
  59. package/api/docs/detail/section/section.test.mjs +50 -0
  60. package/api/docs/docs.d.mts +10 -3
  61. package/api/docs/docs.doc.mjs +55 -16
  62. package/api/docs/docs.mjs +53 -10
  63. package/api/docs/docs.test.mjs +166 -4
  64. package/api/docs/docs.type.d.mts +221 -5
  65. package/api/docs/docs.type.mjs +153 -11
  66. package/api/docs/index/index.d.mts +18 -0
  67. package/api/docs/index/index.mjs +40 -0
  68. package/api/docs/index/index.test.mjs +62 -0
  69. package/api/docs/integration-tree.test.mjs +555 -0
  70. package/api/docs/integrationDocs.test.mjs +114 -8
  71. package/api/docs/list/list.mjs +28 -12
  72. package/api/docs/node/node.d.mts +43 -0
  73. package/api/docs/node/node.mjs +192 -0
  74. package/api/docs/reference-blocks.test.mjs +406 -0
  75. package/api/doctor/doctor.d.mts +104 -1
  76. package/api/doctor/doctor.doc.mjs +1 -0
  77. package/api/doctor/doctor.mjs +635 -7
  78. package/api/doctor/doctor.test.mjs +732 -11
  79. package/api/gap-report/gap-report.doc.mjs +8 -4
  80. package/api/hook/_adapter.mjs +19 -5
  81. package/api/hook/hook.doc.mjs +1 -0
  82. package/api/hook/hook.type.d.mts +3 -3
  83. package/api/hook/hook.type.mjs +11 -11
  84. package/api/hook/list/list.d.mts +2 -2
  85. package/api/hook/list/list.mjs +69 -17
  86. package/api/index.d.mts +2 -3
  87. package/api/index.mjs +5 -4
  88. package/api/init/init.doc.mjs +6 -1
  89. package/api/init/init.test.mjs +41 -1
  90. package/api/init/remove/remove.mjs +1 -1
  91. package/api/init/run/run.mjs +20 -10
  92. package/api/integration/add-contribution.component-names.test.mjs +120 -0
  93. package/api/integration/add-contribution.d.mts +2 -1
  94. package/api/integration/add-contribution.mjs +130 -15
  95. package/api/integration/add-contribution.test.mjs +258 -7
  96. package/api/integration/add-helpers.d.mts +5 -2
  97. package/api/integration/add-helpers.mjs +36 -9
  98. package/api/integration/add-theme.mjs +34 -64
  99. package/api/integration/add-theme.test.mjs +105 -21
  100. package/api/integration/authoring-checks.mjs +138 -28
  101. package/api/integration/authoring-checks.test.mjs +179 -7
  102. package/api/integration/authoring-checks.type.mjs +6 -1
  103. package/api/integration/integration-authoring.type.d.mts +3 -1
  104. package/api/integration/integration-authoring.type.mjs +2 -0
  105. package/api/integration/integration-block-exports.test.mjs +10 -6
  106. package/api/integration/integrationAdd.doc.mjs +14 -4
  107. package/api/integration/integrationAddAgentDoc.doc.mjs +1 -0
  108. package/api/integration/integrationAddCodemod.doc.mjs +1 -0
  109. package/api/integration/integrationAddComponent.doc.mjs +2 -1
  110. package/api/integration/integrationAddDoc.doc.mjs +8 -1
  111. package/api/integration/integrationAddTemplate.doc.mjs +1 -0
  112. package/api/integration/integrationAddTheme.doc.mjs +6 -5
  113. package/api/integration/integrationComponentConflicts.doc.mjs +1 -0
  114. package/api/integration/integrationDocConflicts.doc.mjs +2 -1
  115. package/api/integration/integrationPackCheck.doc.mjs +2 -1
  116. package/api/integration/integrationTemplateConflicts.doc.d.mts +3 -0
  117. package/api/integration/integrationTemplateConflicts.doc.mjs +4 -0
  118. package/api/integration/pack-check.lifecycle-output.test.mjs +105 -0
  119. package/api/integration/pack-check.mjs +111 -10
  120. package/api/integration/pack-check.test.mjs +387 -47
  121. package/api/integration/pack-check.type.d.mts +26 -2
  122. package/api/integration/pack-check.type.mjs +14 -1
  123. package/api/integration/summarizeIssues.doc.mjs +1 -0
  124. package/api/integration/template-conflict-compatibility.test.mjs +73 -0
  125. package/api/integration/validate-integration-fixes.test.mjs +1389 -0
  126. package/api/integration/validate-integration.mjs +52 -102
  127. package/api/integration/validate-integration.test.mjs +179 -26
  128. package/api/integration/validate-unread-theme-folders.test.mjs +110 -0
  129. package/api/integration/validateIntegration.doc.mjs +3 -2
  130. package/api/json/assertResponse.doc.mjs +1 -0
  131. package/api/json/envelope-types.test.mjs +76 -0
  132. package/api/json/index.ts +2 -1
  133. package/api/json/isError.doc.mjs +1 -0
  134. package/api/json/parseResponse.doc.mjs +3 -2
  135. package/api/search/search-return-type.test.mjs +54 -0
  136. package/api/search/search.d.mts +62 -11
  137. package/api/search/search.doc.mjs +8 -2
  138. package/api/search/search.mjs +471 -83
  139. package/api/search/search.test.mjs +142 -1
  140. package/api/search/search.type.d.mts +15 -3
  141. package/api/search/search.type.mjs +5 -2
  142. package/api/swizzle/copy/copy.mjs +28 -11
  143. package/api/swizzle/swizzle.doc.mjs +2 -1
  144. package/api/swizzle/swizzle.type.d.mts +2 -2
  145. package/api/swizzle/swizzle.type.mjs +2 -2
  146. package/api/template/copy/copy.mjs +17 -23
  147. package/api/template/copy/copy.test.mjs +17 -0
  148. package/api/template/list/list.mjs +1 -0
  149. package/api/template/table-floating-bulk-actions.test.mjs +66 -0
  150. package/api/template/template-integration.test.mjs +1008 -3
  151. package/api/template/template-suffix.test.mjs +41 -21
  152. package/api/template/template.d.mts +1 -1
  153. package/api/template/template.doc.mjs +30 -8
  154. package/api/template/template.mjs +46 -9
  155. package/api/template/template.type.d.mts +12 -14
  156. package/api/template/template.type.mjs +15 -14
  157. package/api/theme/_adapter.d.mts +2 -3
  158. package/api/theme/_adapter.mjs +4 -5
  159. package/api/theme/add/add.binary.test.mjs +84 -0
  160. package/api/theme/add/add.mjs +31 -22
  161. package/api/theme/add/add.rollback.test.mjs +158 -0
  162. package/api/theme/add/add.staging.test.mjs +83 -0
  163. package/api/theme/add/add.test.mjs +14 -1
  164. package/api/theme/build/build.family.test.mjs +7 -12
  165. package/api/theme/build/build.mjs +140 -59
  166. package/api/theme/build/build.public-component-vars.test.mjs +1 -1
  167. package/api/theme/build/build.receipt-doc.test.mjs +111 -0
  168. package/api/theme/build/build.rollback.test.mjs +148 -0
  169. package/api/theme/build/build.test.mjs +127 -0
  170. package/api/theme/build/font-warning.mjs +3 -3
  171. package/api/theme/build/font-warning.test.mjs +5 -2
  172. package/api/theme/generateTonalPalette.doc.mjs +1 -0
  173. package/api/theme/integration-themes.test.mjs +39 -28
  174. package/api/theme/list/list.test.mjs +19 -20
  175. package/api/theme/listThemes.doc.mjs +6 -5
  176. package/api/theme/palette/generate/generate.mjs +8 -3
  177. package/api/theme/palette/generate/generate.test.mjs +96 -0
  178. package/api/theme/palette/generate/generator.d.mts +10 -13
  179. package/api/theme/palette/generate/generator.mjs +15 -4
  180. package/api/theme/palette/generate/generator.test.mjs +10 -0
  181. package/api/theme/template/template.mjs +11 -2
  182. package/api/theme/template/template.test.mjs +20 -0
  183. package/api/theme/theme.type.d.mts +170 -11
  184. package/api/theme/theme.type.mjs +94 -27
  185. package/api/theme/themeAdd.doc.mjs +4 -3
  186. package/api/theme/themeBuild.doc.mjs +8 -4
  187. package/api/theme/themeList.doc.mjs +6 -3
  188. package/api/theme/themeListAvailable.doc.mjs +5 -3
  189. package/api/theme/themePaletteGenerate.doc.mjs +1 -0
  190. package/api/theme/themeTargets.doc.mjs +1 -0
  191. package/api/theme/themeTemplate.doc.mjs +6 -2
  192. package/api/upgrade/_adapter.d.mts +32 -5
  193. package/api/upgrade/_adapter.mjs +139 -22
  194. package/api/upgrade/list/list.mjs +2 -1
  195. package/api/upgrade/list/list.test.mjs +73 -0
  196. package/api/upgrade/project-context.test.mjs +272 -0
  197. package/api/upgrade/provider-agreement.test.mjs +152 -0
  198. package/api/upgrade/run/files-changed.test.mjs +111 -0
  199. package/api/upgrade/run/run.mjs +359 -60
  200. package/api/upgrade/status/status.mjs +2 -2
  201. package/api/upgrade/upgrade.doc.mjs +12 -5
  202. package/api/upgrade/upgrade.type.d.mts +43 -5
  203. package/api/upgrade/upgrade.type.mjs +29 -13
  204. package/assets/codemods/__tests__/registry.test.mjs +1 -0
  205. package/assets/codemods/__tests__/runner.test.mjs +332 -8
  206. package/assets/codemods/file-count.test.mjs +163 -0
  207. package/assets/codemods/integration-discovery.mjs +48 -4
  208. package/assets/codemods/integration-discovery.test.mjs +73 -0
  209. package/assets/codemods/integration-runner.mjs +59 -7
  210. package/assets/codemods/integration-runner.protection.test.mjs +153 -0
  211. package/assets/codemods/registry.mjs +1 -0
  212. package/assets/codemods/run-codemod.mjs +177 -34
  213. package/assets/codemods/runner.mjs +353 -104
  214. package/assets/codemods/term-log.mjs +32 -8
  215. package/assets/codemods/term-log.test.mjs +19 -1
  216. package/assets/codemods/transform-prop.mjs +109 -0
  217. package/assets/codemods/transform-prop.test.mjs +95 -0
  218. package/assets/codemods/transforms/v0.0.14/__tests__/rename-status-variants.test.mjs +86 -165
  219. package/assets/codemods/transforms/v0.0.14/rename-status-variants.mjs +72 -210
  220. package/assets/codemods/transforms/v0.1.8/__tests__/rename-avatar-size-scale.test.mjs +83 -115
  221. package/assets/codemods/transforms/v0.1.8/rename-avatar-size-scale.mjs +57 -186
  222. package/assets/codemods/transforms/v0.3.0/__tests__/unwrap-authoring-factories.test.mjs +27 -5
  223. package/assets/codemods/transforms/v0.3.0/unwrap-authoring-factories.mjs +20 -5
  224. package/assets/codemods/transforms/v0.6.0/__tests__/next-codemods.test.mjs +47 -0
  225. package/assets/codemods/transforms/v0.6.0/rename-resizable-pixel-bounds.mjs +57 -10
  226. package/assets/codemods/transforms/v0.6.4/__tests__/migrate-native-picker-to-presentation.test.mjs +63 -0
  227. package/assets/codemods/transforms/v0.6.4/__tests__/migrate-theme-catalog-to-descriptors.test.mjs +220 -0
  228. package/assets/codemods/transforms/v0.6.4/index.mjs +31 -0
  229. package/assets/codemods/transforms/v0.6.4/migrate-native-picker-to-presentation.mjs +148 -0
  230. package/assets/codemods/transforms/v0.6.4/migrate-theme-catalog-to-descriptors.mjs +141 -0
  231. package/assets/docs/README.md +9 -0
  232. package/assets/docs/authoring.doc.mjs +14 -0
  233. package/assets/docs/getting-started.doc.mjs +2 -2
  234. package/assets/docs/internationalization.doc.mjs +7 -5
  235. package/assets/docs/layout.doc.dense.mjs +2 -2
  236. package/assets/docs/layout.doc.mjs +1 -1
  237. package/assets/docs/principles.doc.mjs +6 -6
  238. package/assets/docs/styling-libraries.doc.mjs +4 -4
  239. package/assets/docs/styling.doc.mjs +4 -4
  240. package/assets/docs/theme.doc.mjs +5 -5
  241. package/assets/docs/tokens.doc.mjs +1 -1
  242. package/assets/docs/tree/api.doc.mjs +30 -0
  243. package/assets/docs/tree/cli.doc.mjs +23 -0
  244. package/assets/docs/tree/commands.doc.mjs +25 -0
  245. package/assets/docs/tree/component-lookups.doc.mjs +149 -0
  246. package/assets/docs/{cli-integrations.doc.mjs → tree/integrations.doc.mjs} +144 -26
  247. package/assets/docs/tree/integrations.test.mjs +62 -0
  248. package/assets/docs/tree/writing-docs.doc.mjs +286 -0
  249. package/assets/docs/working-with-ai.doc.mjs +4 -4
  250. package/assets/templates/blocks/components/CheckboxList/CheckboxListSelectAllPattern.doc.mjs +1 -1
  251. package/assets/templates/blocks/components/CheckboxList/CheckboxListSelectAllPattern.tsx +1 -3
  252. package/assets/templates/blocks/components/DateInput/DateInputDateRange.tsx +1 -1
  253. package/assets/templates/blocks/components/InternationalizationProvider/InternationalizationProvider01ShippedLocale.tsx +1 -1
  254. package/assets/templates/blocks/components/Item/ItemDocumentTabs.doc.mjs +14 -0
  255. package/assets/templates/blocks/components/Item/ItemDocumentTabs.tsx +100 -0
  256. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaInlineRail.doc.mjs +14 -0
  257. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaInlineRail.tsx +61 -0
  258. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaOverscrollChaining.doc.mjs +14 -0
  259. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaOverscrollChaining.tsx +126 -0
  260. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaShowcase.doc.mjs +15 -0
  261. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaShowcase.tsx +86 -0
  262. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyGroupHeaders.doc.mjs +14 -0
  263. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyGroupHeaders.tsx +99 -0
  264. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyPassthrough.doc.mjs +14 -0
  265. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyPassthrough.tsx +122 -0
  266. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaTwoAxisBoard.doc.mjs +14 -0
  267. package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaTwoAxisBoard.tsx +95 -0
  268. package/assets/templates/blocks/components/Table/TableBulkActionsTable.doc.mjs +14 -0
  269. package/assets/templates/blocks/components/Table/TableBulkActionsTable.tsx +71 -0
  270. package/assets/templates/blocks/components/Table/TableFloatingBulkActionsTable.doc.mjs +19 -0
  271. package/assets/templates/blocks/components/Table/TableFloatingBulkActionsTable.tsx +139 -0
  272. package/assets/templates/blocks/components/TimeInput/TimeInputConstrained.tsx +1 -0
  273. package/assets/templates/blocks/components/Timer/TimerFormats.doc.mjs +14 -0
  274. package/assets/templates/blocks/components/Timer/TimerFormats.tsx +34 -0
  275. package/assets/templates/blocks/components/Timer/TimerInline.doc.mjs +14 -0
  276. package/assets/templates/blocks/components/Timer/TimerInline.tsx +14 -0
  277. package/assets/templates/blocks/components/Timer/TimerShowcase.doc.mjs +13 -0
  278. package/assets/templates/blocks/components/Timer/TimerShowcase.tsx +47 -0
  279. package/assets/templates/blocks/components/Timer/TimerTypography.doc.mjs +14 -0
  280. package/assets/templates/blocks/components/Timer/TimerTypography.tsx +31 -0
  281. package/assets/templates/blocks/components/Toolbar/ToolbarTableFilter.doc.mjs +19 -3
  282. package/assets/templates/blocks/components/Toolbar/ToolbarTableFilter.tsx +383 -65
  283. package/assets/templates/pages/table-tree/page.tsx +1704 -0
  284. package/assets/templates/pages/table-tree/template.doc.mjs +12 -0
  285. package/assets/templates/themes/butter/butterTheme.doc.mjs +11 -0
  286. package/assets/templates/themes/chocolate/chocolateTheme.doc.mjs +11 -0
  287. package/assets/templates/themes/gothic/gothicTheme.doc.mjs +11 -0
  288. package/assets/templates/themes/matcha/matchaTheme.doc.mjs +11 -0
  289. package/assets/templates/themes/neutral/neutralTheme.doc.mjs +11 -0
  290. package/assets/templates/themes/stone/stoneTheme.doc.mjs +11 -0
  291. package/assets/templates/themes/y2k/y2kTheme.doc.mjs +11 -0
  292. package/authoring/_shared/contract.ts +22 -0
  293. package/authoring/codemod/codemod.doc.mjs +7 -2
  294. package/authoring/codemod/parse.d.mts +8 -8
  295. package/authoring/codemod/parse.mjs +8 -6
  296. package/authoring/codemod/type.ts +12 -0
  297. package/authoring/config/config.doc.mjs +11 -3
  298. package/authoring/config/debug-composition.test.mjs +92 -0
  299. package/authoring/config/parse.d.mts +15 -13
  300. package/authoring/config/parse.mjs +27 -8
  301. package/authoring/config/parse.test.mjs +8 -0
  302. package/authoring/config/type.ts +31 -8
  303. package/authoring/debug/debug.doc.d.mts +11 -0
  304. package/authoring/debug/debug.doc.mjs +182 -0
  305. package/authoring/debug/parse.d.mts +8 -8
  306. package/authoring/debug/parse.mjs +3 -3
  307. package/authoring/discover/discover.doc.d.mts +13 -0
  308. package/authoring/discover/discover.doc.mjs +138 -0
  309. package/authoring/discover/parse.d.mts +24 -0
  310. package/authoring/discover/parse.mjs +128 -0
  311. package/authoring/discover/parse.test.mjs +124 -0
  312. package/authoring/discover/type.ts +87 -0
  313. package/authoring/doctypes/_schema.d.mts +790 -23
  314. package/authoring/doctypes/_schema.mjs +543 -39
  315. package/authoring/doctypes/base/graph-fields.doc.d.mts +9 -0
  316. package/authoring/doctypes/base/graph-fields.doc.mjs +64 -0
  317. package/authoring/doctypes/base/type.ts +41 -0
  318. package/authoring/doctypes/command/command.doc.mjs +5 -4
  319. package/authoring/doctypes/command/parse.d.mts +2 -2
  320. package/authoring/doctypes/command/parse.mjs +1 -1
  321. package/authoring/doctypes/command/type.ts +6 -5
  322. package/authoring/doctypes/component/component.doc.mjs +6 -3
  323. package/authoring/doctypes/component/parse.d.mts +2 -2
  324. package/authoring/doctypes/component/parse.mjs +1 -1
  325. package/authoring/doctypes/component/type.ts +6 -5
  326. package/authoring/doctypes/doctypes-new.test.mjs +48 -6
  327. package/authoring/doctypes/enum/enum.doc.mjs +1 -1
  328. package/authoring/doctypes/enum/parse.d.mts +2 -2
  329. package/authoring/doctypes/enum/parse.mjs +1 -1
  330. package/authoring/doctypes/enum/type.ts +4 -2
  331. package/authoring/doctypes/function/function.doc.mjs +7 -2
  332. package/authoring/doctypes/function/parse.d.mts +2 -2
  333. package/authoring/doctypes/function/parse.mjs +1 -1
  334. package/authoring/doctypes/function/type.ts +9 -4
  335. package/authoring/doctypes/hook/hook.doc.mjs +4 -0
  336. package/authoring/doctypes/hook/parse.d.mts +2 -2
  337. package/authoring/doctypes/hook/parse.mjs +1 -1
  338. package/authoring/doctypes/hook/type.ts +5 -4
  339. package/authoring/doctypes/legacy.d.mts +8 -6
  340. package/authoring/doctypes/legacy.mjs +5 -4
  341. package/authoring/doctypes/load-contract.test.mjs +233 -0
  342. package/authoring/doctypes/namespace/namespace.doc.d.mts +9 -0
  343. package/authoring/doctypes/namespace/namespace.doc.mjs +128 -0
  344. package/authoring/doctypes/namespace/parse.d.mts +12 -0
  345. package/authoring/doctypes/namespace/parse.mjs +25 -0
  346. package/authoring/doctypes/namespace/parse.test.mjs +163 -0
  347. package/authoring/doctypes/namespace/type.ts +74 -0
  348. package/authoring/doctypes/parse.d.mts +22 -18
  349. package/authoring/doctypes/parse.mjs +22 -11
  350. package/authoring/doctypes/parse.test.mjs +77 -3
  351. package/authoring/doctypes/reference/parse.d.mts +2 -2
  352. package/authoring/doctypes/reference/parse.mjs +8 -5
  353. package/authoring/doctypes/reference/reference.doc.mjs +48 -6
  354. package/authoring/doctypes/reference/type.ts +70 -7
  355. package/authoring/doctypes/schema/parse.d.mts +2 -2
  356. package/authoring/doctypes/schema/parse.mjs +1 -1
  357. package/authoring/doctypes/schema/schema.doc.mjs +1 -1
  358. package/authoring/doctypes/schema/type.ts +4 -4
  359. package/authoring/doctypes/template/parse.d.mts +94 -1
  360. package/authoring/doctypes/template/parse.mjs +40 -2
  361. package/authoring/doctypes/template/parse.test.mjs +26 -2
  362. package/authoring/doctypes/template/template.doc.mjs +13 -3
  363. package/authoring/doctypes/template/type.ts +13 -2
  364. package/authoring/doctypes/theme/parse.d.mts +35 -0
  365. package/authoring/doctypes/theme/parse.mjs +76 -0
  366. package/authoring/doctypes/theme/theme.doc.d.mts +9 -0
  367. package/authoring/doctypes/theme/theme.doc.mjs +79 -0
  368. package/authoring/doctypes/theme/type.ts +42 -0
  369. package/authoring/doctypes/types.ts +12 -10
  370. package/authoring/gap-report/gap-report.doc.d.mts +12 -0
  371. package/authoring/gap-report/gap-report.doc.mjs +183 -0
  372. package/authoring/gap-report/parse.d.mts +10 -10
  373. package/authoring/gap-report/parse.mjs +6 -6
  374. package/authoring/gap-report/type.ts +1 -1
  375. package/authoring/identity/identity.doc.d.mts +9 -0
  376. package/authoring/identity/identity.doc.mjs +61 -0
  377. package/authoring/identity/type.ts +132 -0
  378. package/authoring/index.d.mts +3 -0
  379. package/authoring/index.d.ts +62 -17
  380. package/authoring/index.mjs +4 -1
  381. package/authoring/integration/integration.doc.mjs +15 -8
  382. package/authoring/integration/parse.d.mts +2 -2
  383. package/authoring/integration/parse.mjs +1 -1
  384. package/authoring/integration/parse.test.mjs +10 -1
  385. package/authoring/integration/schema.d.mts +6 -4
  386. package/authoring/integration/schema.mjs +9 -3
  387. package/authoring/integration/type.ts +19 -8
  388. package/authoring/shadcn/receipt.d.mts +6 -6
  389. package/clients/cli/__tests__/cliManifest.test.ts +27 -29
  390. package/clients/cli/command-load-failure.test.mjs +83 -0
  391. package/clients/cli/command-result-coverage.test.mjs +7 -7
  392. package/clients/cli/commands/blog.doc.mjs +1 -1
  393. package/clients/cli/commands/blog.mjs +23 -8
  394. package/clients/cli/commands/blog.test.mjs +42 -1
  395. package/clients/cli/commands/build-theme.adaptations.test.mjs +100 -1
  396. package/clients/cli/commands/build-theme.ascii-output.test.mjs +161 -0
  397. package/clients/cli/commands/build-theme.flag-docs.test.mjs +110 -0
  398. package/clients/cli/commands/build-theme.mjs +16 -50
  399. package/clients/cli/commands/build-theme.path-safety.test.mjs +86 -1
  400. package/clients/cli/commands/build-theme.variants.test.mjs +76 -0
  401. package/clients/cli/commands/build.doc.mjs +16 -8
  402. package/clients/cli/commands/build.exit-codes-doc.test.mjs +50 -0
  403. package/clients/cli/commands/build.mjs +137 -114
  404. package/clients/cli/commands/build.playbook.test.mjs +75 -0
  405. package/clients/cli/commands/build.text-fields.test.mjs +81 -0
  406. package/clients/cli/commands/component/index.mjs +153 -61
  407. package/clients/cli/commands/component-batch.test.mjs +341 -0
  408. package/clients/cli/commands/component-ownership.test.mjs +92 -3
  409. package/clients/cli/commands/component-package.test.mjs +46 -0
  410. package/clients/cli/commands/component-resolution.test.mjs +21 -0
  411. package/clients/cli/commands/component.doc.mjs +24 -7
  412. package/clients/cli/commands/component.test.mjs +19 -0
  413. package/clients/cli/commands/debug-result-summary.test.mjs +2 -2
  414. package/clients/cli/commands/detail-levels.test.mjs +2 -2
  415. package/clients/cli/commands/discover.broken-integration.test.mjs +52 -5
  416. package/clients/cli/commands/discover.components-flag.test.mjs +97 -0
  417. package/clients/cli/commands/discover.doc.mjs +55 -9
  418. package/clients/cli/commands/discover.mjs +393 -118
  419. package/clients/cli/commands/discover.sources.test.mjs +267 -0
  420. package/clients/cli/commands/discover.text-projection.test.mjs +103 -0
  421. package/clients/cli/commands/docs.doc.mjs +28 -6
  422. package/clients/cli/commands/docs.mjs +240 -26
  423. package/clients/cli/commands/docs.test.mjs +222 -1
  424. package/clients/cli/commands/doctor-integration-components.doc.mjs +1 -1
  425. package/clients/cli/commands/doctor-integration-docs.doc.mjs +3 -3
  426. package/clients/cli/commands/doctor-integration-templates.doc.mjs +15 -8
  427. package/clients/cli/commands/doctor-integration-validate.doc.mjs +1 -1
  428. package/clients/cli/commands/doctor-integration.doc.mjs +1 -1
  429. package/clients/cli/commands/doctor-integration.package-json.test.mjs +53 -0
  430. package/clients/cli/commands/doctor-integration.test.mjs +90 -8
  431. package/clients/cli/commands/doctor.doc.mjs +1 -1
  432. package/clients/cli/commands/doctor.mjs +59 -32
  433. package/clients/cli/commands/doctor.test.mjs +42 -0
  434. package/clients/cli/commands/gap-report.doc.mjs +17 -6
  435. package/clients/cli/commands/gap-report.test.mjs +72 -0
  436. package/clients/cli/commands/hook/index.mjs +7 -17
  437. package/clients/cli/commands/hook.doc.mjs +1 -1
  438. package/clients/cli/commands/hook.text-projection.test.mjs +45 -0
  439. package/clients/cli/commands/init.doc.mjs +20 -9
  440. package/clients/cli/commands/init.flag-help.test.mjs +153 -0
  441. package/clients/cli/commands/integration-add.controls.test.mjs +132 -0
  442. package/clients/cli/commands/integration-add.doc.mjs +32 -6
  443. package/clients/cli/commands/integration-authoring.test.mjs +13 -9
  444. package/clients/cli/commands/integration-pack.doc.mjs +1 -1
  445. package/clients/cli/commands/integration-real-world.test.mjs +3 -9
  446. package/clients/cli/commands/integration.doc.mjs +1 -1
  447. package/clients/cli/commands/integration.mjs +1 -0
  448. package/clients/cli/commands/interactive-guard.test.mjs +101 -24
  449. package/clients/cli/commands/json-contract.test.mjs +33 -0
  450. package/clients/cli/commands/manifest.doc.mjs +1 -1
  451. package/clients/cli/commands/no-prompt-wording.test.mjs +94 -0
  452. package/clients/cli/commands/search.doc.mjs +7 -4
  453. package/clients/cli/commands/search.mjs +28 -9
  454. package/clients/cli/commands/search.test.mjs +75 -0
  455. package/clients/cli/commands/setup-nudge.test.mjs +6 -0
  456. package/clients/cli/commands/swizzle.doc.mjs +3 -2
  457. package/clients/cli/commands/swizzle.path-safety.test.mjs +42 -0
  458. package/clients/cli/commands/template.doc.mjs +52 -13
  459. package/clients/cli/commands/template.flag-help.test.mjs +117 -0
  460. package/clients/cli/commands/template.mjs +4 -91
  461. package/clients/cli/commands/template.path-help.test.mjs +40 -0
  462. package/clients/cli/commands/text-json-parity.test.mjs +702 -0
  463. package/clients/cli/commands/theme-add.doc.mjs +4 -3
  464. package/clients/cli/commands/theme-build.doc.mjs +8 -7
  465. package/clients/cli/commands/theme-list.doc.mjs +2 -2
  466. package/clients/cli/commands/theme-palette-generate.doc.mjs +9 -5
  467. package/clients/cli/commands/theme-palette-generate.test.mjs +19 -0
  468. package/clients/cli/commands/theme-palette.doc.mjs +1 -1
  469. package/clients/cli/commands/theme-targets.behavior.test.mjs +4 -3
  470. package/clients/cli/commands/theme-targets.doc.mjs +1 -1
  471. package/clients/cli/commands/theme-template.behavior.test.mjs +12 -0
  472. package/clients/cli/commands/theme-template.doc.mjs +2 -2
  473. package/clients/cli/commands/theme.doc.mjs +1 -1
  474. package/clients/cli/commands/upgrade.ascii-output.test.mjs +87 -0
  475. package/clients/cli/commands/upgrade.doc.mjs +22 -10
  476. package/clients/cli/commands/upgrade.file-protection.test.mjs +228 -0
  477. package/clients/cli/commands/upgrade.flag-help.test.mjs +188 -0
  478. package/clients/cli/commands/upgrade.hook-output.test.mjs +88 -0
  479. package/clients/cli/commands/upgrade.mjs +29 -7
  480. package/clients/cli/formatters/index.mjs +164 -1
  481. package/clients/cli/formatters/index.test.mjs +97 -0
  482. package/clients/cli/index.mjs +21 -34
  483. package/clients/cli/latest-version-env.test.mjs +50 -0
  484. package/clients/cli/lib/cli-error.test.mjs +7 -0
  485. package/clients/cli/lib/component-format.mjs +9 -9
  486. package/clients/cli/lib/component-format.test.mjs +1 -1
  487. package/clients/cli/lib/define-command.mjs +32 -6
  488. package/clients/cli/lib/doc-text-ascii.test.mjs +82 -0
  489. package/clients/cli/lib/exit-codes.test.mjs +90 -0
  490. package/clients/cli/lib/hook-format.mjs +19 -10
  491. package/clients/cli/lib/json-shim.mjs +62 -16
  492. package/clients/cli/lib/json-shim.test.mjs +69 -0
  493. package/clients/cli/lib/manifest.d.ts +2 -0
  494. package/clients/cli/lib/manifest.mjs +39 -10
  495. package/clients/cli/lib/manifest.test.mjs +22 -2
  496. package/clients/cli/lib/parse-error-format.test.mjs +81 -0
  497. package/foundation/agent-docs/agent-docs.d.mts +7 -2
  498. package/foundation/agent-docs/agent-docs.mjs +82 -12
  499. package/foundation/agent-docs/agent-docs.path-safety.test.mjs +266 -4
  500. package/foundation/agent-docs/agent-docs.test.mjs +19 -1
  501. package/foundation/config/integration-debug.test.mjs +28 -3
  502. package/foundation/config/project-themes.test.mjs +11 -19
  503. package/foundation/config/project.d.mts +20 -11
  504. package/foundation/config/project.mjs +263 -91
  505. package/foundation/config/project.test.mjs +270 -21
  506. package/foundation/discovery/authoring-self-docs.d.mts +87 -0
  507. package/foundation/discovery/authoring-self-docs.mjs +237 -0
  508. package/foundation/discovery/authoring-self-docs.test.mjs +170 -0
  509. package/foundation/discovery/authoring-surface.d.mts +74 -0
  510. package/foundation/discovery/authoring-surface.mjs +525 -0
  511. package/foundation/discovery/authoring-surface.test.mjs +392 -0
  512. package/foundation/discovery/cli-self-docs.d.mts +119 -0
  513. package/foundation/discovery/cli-self-docs.mjs +490 -0
  514. package/foundation/discovery/cli-self-docs.test.mjs +375 -0
  515. package/foundation/discovery/component-discovery.d.mts +39 -1
  516. package/foundation/discovery/component-discovery.mjs +50 -1
  517. package/foundation/discovery/component-loader.d.mts +35 -38
  518. package/foundation/discovery/component-loader.mjs +53 -222
  519. package/foundation/discovery/docs-discovery.d.mts +119 -11
  520. package/foundation/discovery/docs-discovery.mjs +423 -108
  521. package/foundation/discovery/docs-discovery.test.mjs +365 -20
  522. package/foundation/discovery/docs-output-budget.d.mts +28 -0
  523. package/foundation/discovery/docs-output-budget.mjs +50 -0
  524. package/foundation/discovery/docs-section-key.d.mts +116 -0
  525. package/foundation/discovery/docs-section-key.mjs +322 -0
  526. package/foundation/discovery/docs-section-key.test.mjs +246 -0
  527. package/foundation/discovery/template-adapter.d.mts +113 -11
  528. package/foundation/discovery/template-adapter.fixture-refs.test.mjs +248 -0
  529. package/foundation/discovery/template-adapter.integration-isolation.test.mjs +94 -0
  530. package/foundation/discovery/template-adapter.mjs +775 -84
  531. package/foundation/discovery/template-adapter.test.mjs +57 -0
  532. package/foundation/discovery/template-conflict-release.d.mts +13 -0
  533. package/foundation/discovery/template-conflict-release.mjs +40 -0
  534. package/foundation/discovery/template-conflict-release.test.mjs +40 -0
  535. package/foundation/discovery/theme-discovery.d.mts +67 -7
  536. package/foundation/discovery/theme-discovery.mjs +916 -186
  537. package/foundation/discovery/theme-discovery.test.mjs +613 -219
  538. package/foundation/discovery/theming-targets.test.mjs +4 -0
  539. package/foundation/doc-compiler/bundle.d.mts +47 -0
  540. package/foundation/doc-compiler/bundle.mjs +278 -0
  541. package/foundation/doc-compiler/bundle.test.mjs +266 -0
  542. package/foundation/doc-compiler/compile.d.mts +343 -0
  543. package/foundation/doc-compiler/compile.mjs +558 -0
  544. package/foundation/doc-compiler/diagnostics.d.mts +126 -0
  545. package/foundation/doc-compiler/diagnostics.mjs +305 -0
  546. package/foundation/doc-compiler/doc-compiler.test.mjs +714 -0
  547. package/foundation/doc-compiler/doc-loads.test.mjs +1630 -0
  548. package/foundation/doc-compiler/import.d.mts +24 -0
  549. package/foundation/doc-compiler/import.mjs +59 -0
  550. package/foundation/doc-compiler/inputs.d.mts +102 -0
  551. package/foundation/doc-compiler/inputs.mjs +291 -0
  552. package/foundation/doc-compiler/inputs.test.mjs +299 -0
  553. package/foundation/doc-compiler/ir.d.mts +22 -0
  554. package/foundation/doc-compiler/ir.mjs +471 -0
  555. package/foundation/doc-compiler/lenses.d.mts +36 -0
  556. package/foundation/doc-compiler/lenses.mjs +173 -0
  557. package/foundation/doc-compiler/links.d.mts +162 -0
  558. package/foundation/doc-compiler/links.mjs +294 -0
  559. package/foundation/doc-compiler/links.test.mjs +192 -0
  560. package/foundation/doc-compiler/lower-doc.test.mjs +495 -0
  561. package/foundation/doc-compiler/overlays.d.mts +37 -0
  562. package/foundation/doc-compiler/overlays.mjs +206 -0
  563. package/foundation/doc-compiler/parse-readable.d.mts +9 -0
  564. package/foundation/doc-compiler/parse-readable.mjs +29 -0
  565. package/foundation/doc-compiler/read.d.mts +127 -0
  566. package/foundation/doc-compiler/read.mjs +325 -0
  567. package/foundation/doc-compiler/read.test.mjs +313 -0
  568. package/foundation/doc-compiler/source.d.mts +33 -0
  569. package/foundation/doc-compiler/source.mjs +128 -0
  570. package/foundation/doc-compiler/tree.d.mts +288 -0
  571. package/foundation/doc-compiler/tree.mjs +876 -0
  572. package/foundation/doc-compiler/tree.test.mjs +606 -0
  573. package/foundation/fs/file-protection.d.mts +33 -0
  574. package/foundation/fs/file-protection.mjs +825 -0
  575. package/foundation/fs/file-protection.test.mjs +250 -0
  576. package/foundation/fs/module-loader.d.mts +1 -0
  577. package/foundation/fs/module-loader.mjs +50 -1
  578. package/foundation/fs/module-loader.stdout.test.mjs +332 -0
  579. package/foundation/fs/path-safety.d.mts +3 -2
  580. package/foundation/fs/path-safety.mjs +49 -19
  581. package/foundation/fs/path-safety.test.mjs +50 -0
  582. package/foundation/fs/publish-file-hardlink-unavailable.test.mjs +129 -94
  583. package/foundation/identity/provider-identity.d.mts +90 -0
  584. package/foundation/identity/provider-identity.mjs +320 -0
  585. package/foundation/identity/provider-identity.test.mjs +254 -0
  586. package/foundation/identity/providers.d.mts +7 -0
  587. package/foundation/identity/providers.mjs +16 -0
  588. package/foundation/integrations/autolink.d.mts +58 -1
  589. package/foundation/integrations/autolink.mjs +143 -45
  590. package/foundation/integrations/autolink.test.mjs +1 -1
  591. package/foundation/integrations/cli-requirement.d.mts +45 -0
  592. package/foundation/integrations/cli-requirement.mjs +154 -0
  593. package/foundation/integrations/cli-requirement.test.mjs +84 -0
  594. package/foundation/integrations/contribution-fixes.d.mts +145 -0
  595. package/foundation/integrations/contribution-fixes.mjs +1284 -0
  596. package/foundation/integrations/contribution-inventory.d.mts +3 -2
  597. package/foundation/integrations/contribution-inventory.mjs +27 -24
  598. package/foundation/integrations/contribution-inventory.test.mjs +86 -27
  599. package/foundation/integrations/integration-warnings.d.mts +9 -2
  600. package/foundation/integrations/integration-warnings.mjs +52 -21
  601. package/foundation/integrations/integration-warnings.test.mjs +74 -1
  602. package/foundation/integrations/integrations.d.mts +63 -3
  603. package/foundation/integrations/integrations.mjs +122 -9
  604. package/foundation/integrations/integrations.test.mjs +415 -1
  605. package/foundation/integrations/provider-conflicts.test.mjs +125 -0
  606. package/foundation/integrations/provider-ledger.test.mjs +275 -0
  607. package/foundation/integrations/provider-resolution.d.mts +152 -0
  608. package/foundation/integrations/provider-resolution.mjs +576 -0
  609. package/foundation/integrations/provider-resolution.test.mjs +369 -0
  610. package/foundation/integrations/theme-descriptor.d.mts +8 -0
  611. package/foundation/integrations/theme-descriptor.mjs +44 -0
  612. package/foundation/integrations/validate-contributions.d.mts +2 -0
  613. package/foundation/integrations/validate-contributions.mjs +131 -29
  614. package/foundation/response/base.d.ts +8 -4
  615. package/foundation/response/batch.type.d.mts +33 -0
  616. package/foundation/response/batch.type.mjs +34 -0
  617. package/foundation/response/error-codes.d.mts +3 -1
  618. package/foundation/response/error-codes.d.ts +2 -0
  619. package/foundation/response/error-codes.doc.mjs +13 -4
  620. package/foundation/response/error-codes.mjs +8 -2
  621. package/foundation/response/error-codes.test.mjs +137 -10
  622. package/foundation/response/json-contract.test.mjs +57 -17
  623. package/foundation/response/json.d.mts +4 -2
  624. package/foundation/response/json.mjs +8 -10
  625. package/foundation/response/response-types.doc.d.mts +5 -1
  626. package/foundation/response/response-types.doc.mjs +46 -38
  627. package/foundation/response/response-types.doc.test.mjs +181 -0
  628. package/foundation/response/response.doc.mjs +1 -1
  629. package/foundation/text/string-utils.d.mts +8 -0
  630. package/foundation/text/string-utils.mjs +40 -10
  631. package/foundation/xle/browser.d.mts +3 -3
  632. package/foundation/xle/browser.mjs +3 -3
  633. package/foundation/xle/expand.d.mts +2 -0
  634. package/foundation/xle/expand.mjs +6 -5
  635. package/foundation/xle/expand.test.mjs +54 -0
  636. package/foundation/xle/parse.mjs +1 -1
  637. package/foundation/xle/print.mjs +2 -2
  638. package/foundation/xle/splice.mjs +1 -1
  639. package/foundation/xle/xle.test.mjs +13 -0
  640. package/package.json +10 -11
  641. package/api/layout/_adapter.d.mts +0 -34
  642. package/api/layout/_adapter.mjs +0 -133
  643. package/api/layout/check/check.d.mts +0 -16
  644. package/api/layout/check/check.mjs +0 -40
  645. package/api/layout/expand/expand.d.mts +0 -22
  646. package/api/layout/expand/expand.mjs +0 -153
  647. package/api/layout/grammar/grammar.d.mts +0 -13
  648. package/api/layout/grammar/grammar.mjs +0 -86
  649. package/api/layout/layout.d.mts +0 -6
  650. package/api/layout/layout.mjs +0 -17
  651. package/api/layout/layout.test.mjs +0 -297
  652. package/api/layout/layout.type.d.mts +0 -89
  653. package/api/layout/layout.type.mjs +0 -103
  654. package/api/layout/layoutCheck.doc.d.mts +0 -11
  655. package/api/layout/layoutCheck.doc.mjs +0 -84
  656. package/api/layout/layoutExpand.doc.d.mts +0 -11
  657. package/api/layout/layoutExpand.doc.mjs +0 -106
  658. package/api/layout/layoutGrammar.doc.d.mts +0 -11
  659. package/api/layout/layoutGrammar.doc.mjs +0 -56
  660. package/assets/templates/themes/manifest.json +0 -95
  661. package/clients/cli/commands/layout-check.doc.mjs +0 -54
  662. package/clients/cli/commands/layout-expand.doc.mjs +0 -66
  663. package/clients/cli/commands/layout-grammar.doc.mjs +0 -30
  664. package/clients/cli/commands/layout.doc.mjs +0 -34
  665. package/clients/cli/commands/layout.error-codes.test.mjs +0 -66
  666. package/clients/cli/commands/layout.exit-parity.test.mjs +0 -41
  667. package/clients/cli/commands/layout.mjs +0 -263
  668. package/clients/cli/lib/update-check.mjs +0 -83
  669. package/clients/cli/lib/update-check.test.mjs +0 -137
  670. package/clients/cli/update-hint-commands.test.mjs +0 -54
@@ -0,0 +1,188 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file `astryx upgrade` documents how its flags interact and when it exits 1,
5
+ * and behaves as documented. Option text is read from the generated manifest
6
+ * (the same strings `--help` prints); exit codes from the CommandDoc.
7
+ */
8
+
9
+ import {describe, it, expect, beforeEach, afterEach} from 'vitest';
10
+ import * as fs from 'node:fs';
11
+ import * as os from 'node:os';
12
+ import * as path from 'node:path';
13
+ import {runCli} from '../../../test-utils/run-cli.mjs';
14
+ import {doc as upgradeDoc} from './upgrade.doc.mjs';
15
+
16
+ const SLOW = 30_000;
17
+
18
+ /** @type {string} */
19
+ let tmpDir;
20
+
21
+ beforeEach(() => {
22
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-upgrade-flags-'));
23
+ fs.writeFileSync(path.join(tmpDir, 'package.json'), JSON.stringify({name: 'consumer'}));
24
+ fs.mkdirSync(path.join(tmpDir, 'src'));
25
+ fs.writeFileSync(path.join(tmpDir, 'src', 'index.ts'), 'const foo = 1;\n');
26
+ });
27
+
28
+ afterEach(() => {
29
+ fs.rmSync(tmpDir, {recursive: true, force: true});
30
+ });
31
+
32
+ /** @param {string} name @param {string} version */
33
+ function installPackage(name, version) {
34
+ const dir = path.join(tmpDir, 'node_modules', ...name.split('/'));
35
+ fs.mkdirSync(dir, {recursive: true});
36
+ fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({name, version}));
37
+ }
38
+
39
+ /** @param {string} rel @param {string} body */
40
+ function write(rel, body) {
41
+ fs.mkdirSync(path.dirname(path.join(tmpDir, rel)), {recursive: true});
42
+ fs.writeFileSync(path.join(tmpDir, rel), body);
43
+ }
44
+
45
+ /**
46
+ * upgrade's option descriptions from the manifest, keyed by long flag.
47
+ * @returns {Promise<Record<string, string>>}
48
+ */
49
+ async function upgradeOptions() {
50
+ const {stdout} = await runCli(['--json', 'manifest'], tmpDir);
51
+ const upgrade = JSON.parse(stdout).data.commands.find(
52
+ (/** @type {{name: string}} */ c) => c.name === 'upgrade',
53
+ );
54
+ return Object.fromEntries(
55
+ upgrade.options.map((/** @type {{flag: string, description: string}} */ o) => [
56
+ o.flag.match(/--[a-z-]+/)?.[0],
57
+ o.description,
58
+ ]),
59
+ );
60
+ }
61
+
62
+ /** @param {string[]} args */
63
+ async function upgradeJson(args) {
64
+ const result = await runCli(['--json', 'upgrade', ...args], tmpDir);
65
+ return {status: result.status, envelope: JSON.parse(result.stdout)};
66
+ }
67
+
68
+ const exit1 = () => upgradeDoc.exitCodes?.find(e => e.code === 1)?.when ?? '';
69
+
70
+ const MIGRATION_FLAGS = [
71
+ ['--from', '0.5.0'],
72
+ ['--force'],
73
+ ['--codemod', 'x'],
74
+ ['--skip-codemod', 'x'],
75
+ ['--integration', 'x'],
76
+ ['--install-deps'],
77
+ ];
78
+
79
+ describe('astryx upgrade flag interactions', () => {
80
+ it('--list: help says it ignores every other flag but --registry, and it does', async () => {
81
+ const options = await upgradeOptions();
82
+ expect(options['--list']).toMatch(/Every other flag is ignored, except --registry/);
83
+
84
+ installPackage('@astryxdesign/core', '0.6.0');
85
+ const listed = await upgradeJson([
86
+ '--list',
87
+ '--from',
88
+ 'bogus',
89
+ '--codemod',
90
+ 'bogus',
91
+ '--apply',
92
+ '--path',
93
+ '../outside',
94
+ ]);
95
+ expect(listed.status).toBe(0);
96
+ expect(listed.envelope.type).toBe('upgrade.list');
97
+
98
+ const refused = await upgradeJson(['--list', '--registry']);
99
+ expect(refused.status).toBe(1);
100
+ expect(refused.envelope.code).toBe('ERR_INVALID_ARGUMENT');
101
+ }, SLOW);
102
+
103
+ it('--registry: help names every flag it refuses, and each exits 1', async () => {
104
+ const options = await upgradeOptions();
105
+ for (const [flag] of MIGRATION_FLAGS) expect(options['--registry']).toContain(flag);
106
+ expect(options['--registry']).toContain('--list');
107
+ expect(options['--registry']).toContain('ERR_INVALID_ARGUMENT');
108
+
109
+ for (const flag of MIGRATION_FLAGS) {
110
+ const {status, envelope} = await upgradeJson(['--registry', ...flag]);
111
+ expect(status, flag[0]).toBe(1);
112
+ expect(envelope.code, flag[0]).toBe('ERR_INVALID_ARGUMENT');
113
+ }
114
+ }, SLOW);
115
+
116
+ it('--codemod: help says it runs optional codemods and skips the composition check, and it does', async () => {
117
+ const options = await upgradeOptions();
118
+ expect(options['--codemod']).toMatch(/Optional codemods run only when named here/);
119
+ expect(options['--codemod']).toMatch(/skips the check of ShadCN-copied compositions/);
120
+
121
+ installPackage('@astryxdesign/core', '0.0.15');
122
+ // An unreadable receipt leaves a registry item unresolved.
123
+ write('src/components/.astryx/broken.json', '{}');
124
+
125
+ const normal = await upgradeJson(['--from', '0.0.14']);
126
+ expect(normal.status).toBe(1);
127
+ expect(normal.envelope.data.registryCompositions.invalid).toBe(1);
128
+
129
+ // 0.0.15 ships one optional codemod, which only runs when named.
130
+ const named = await upgradeJson(['--from', '0.0.14', '--codemod', 'migrate-theme-selectors-to-data-attrs']);
131
+ expect(named.status).toBe(0);
132
+ expect(named.envelope.data.codemods).toBe(1);
133
+ expect(named.envelope.data.registryCompositions).toBeUndefined();
134
+ expect(normal.envelope.data.codemods).toBe(6);
135
+ }, SLOW);
136
+
137
+ it('--from: help names the legacy @xds/core target, which upgrade falls back to', async () => {
138
+ const options = await upgradeOptions();
139
+ expect(options['--from']).toMatch(/legacy @xds\/core/);
140
+
141
+ installPackage('@xds/core', '0.0.20');
142
+ const {status, envelope} = await upgradeJson(['--from', '0.0.19']);
143
+ expect(status).toBe(0);
144
+ expect(envelope.data.to).toBe('0.0.20');
145
+ }, SLOW);
146
+ });
147
+
148
+ describe('astryx upgrade exit codes', () => {
149
+ it('lists a missing core, which exits 1 in both modes', async () => {
150
+ expect(exit1()).toMatch(/no installed @astryxdesign\/core/);
151
+ const text = await runCli(['upgrade', '--from', '0.5.0'], tmpDir);
152
+ const json = await upgradeJson(['--from', '0.5.0']);
153
+ expect(text.status).toBe(1);
154
+ expect(json.status).toBe(1);
155
+ expect(json.envelope.code).toBe('ERR_VERSION_DETECT');
156
+ }, SLOW);
157
+
158
+ it('lists an astryx.config no codemod repairs, which exits 1', async () => {
159
+ expect(exit1()).toMatch(/astryx\.config that fails validation/);
160
+ installPackage('@astryxdesign/core', '0.6.0');
161
+ write('astryx.config.mjs', "export default {theme: 'neutral'};\n");
162
+ const {status, envelope} = await upgradeJson(['--from', '0.5.0']);
163
+ expect(status).toBe(1);
164
+ expect(envelope.code).toBe('ERR_INVALID_ARGUMENT');
165
+ }, SLOW);
166
+
167
+ it('lists a post-codemod hook failure, which exits 1', async () => {
168
+ expect(exit1()).toMatch(/post-codemod hook failure/);
169
+ installPackage('@astryxdesign/core', '0.6.0');
170
+ write(
171
+ 'src/panel.tsx',
172
+ "import {useResizable} from '@astryxdesign/core';\nexport const r = () => useResizable({minSizePx: 200});\n",
173
+ );
174
+ write(
175
+ 'astryx.config.mjs',
176
+ "export default {hooks: {postCodemod: [{buildCommand: () => { throw new Error('formatter crashed'); }}]}};\n",
177
+ );
178
+ const {status, envelope} = await upgradeJson(['--from', '0.5.0']);
179
+ expect(status).toBe(1);
180
+ expect(envelope.code).toBe('ERR_CODEMOD_FAILED');
181
+ expect(envelope.error).toMatch(/Post-codemod hook failed/);
182
+ }, SLOW);
183
+
184
+ it('lists a missing jscodeshift and the refused flag combinations', () => {
185
+ expect(exit1()).toMatch(/jscodeshift missing and not installed/);
186
+ expect(exit1()).toMatch(/--registry with --list or a migration flag/);
187
+ });
188
+ });
@@ -0,0 +1,88 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Subprocess test: a post-codemod hook that prints must not corrupt the
5
+ * `upgrade --json` envelope. Stdout carries exactly one envelope; the hook's
6
+ * output goes to stderr.
7
+ */
8
+
9
+ import {describe, it, expect, beforeEach, afterEach} from 'vitest';
10
+ import {spawnSync} from 'node:child_process';
11
+ import * as fs from 'node:fs';
12
+ import * as path from 'node:path';
13
+ import * as os from 'node:os';
14
+ import {fileURLToPath} from 'node:url';
15
+
16
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
17
+ const CLI = path.resolve(__dirname, '..', 'bin', 'astryx.mjs');
18
+
19
+ let tmpDir;
20
+
21
+ /** @param {string} rel @param {string} body */
22
+ function write(rel, body) {
23
+ const file = path.join(tmpDir, rel);
24
+ fs.mkdirSync(path.dirname(file), {recursive: true});
25
+ fs.writeFileSync(file, body);
26
+ }
27
+
28
+ /** @param {string[]} args */
29
+ function runCli(args) {
30
+ return spawnSync(process.execPath, [CLI, ...args], {
31
+ cwd: tmpDir,
32
+ encoding: 'utf8',
33
+ timeout: 60_000,
34
+ stdio: ['ignore', 'pipe', 'pipe'],
35
+ env: {...process.env, FORCE_COLOR: '0', CI: ''},
36
+ });
37
+ }
38
+
39
+ beforeEach(() => {
40
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-upgrade-hook-output-'));
41
+ write('package.json', JSON.stringify({name: 'consumer', private: true}));
42
+ write(
43
+ 'node_modules/@astryxdesign/core/package.json',
44
+ JSON.stringify({name: '@astryxdesign/core', version: '0.6.0'}),
45
+ );
46
+ write(
47
+ 'src/panel.tsx',
48
+ "import {useResizable} from '@astryxdesign/core';\n" +
49
+ 'export const usePanel = () => useResizable({defaultSize: 250, minSizePx: 200});\n',
50
+ );
51
+ write(
52
+ 'astryx.config.mjs',
53
+ 'export default {hooks: {postCodemod: [{name: "noisy", buildCommand: ({files}) => {\n' +
54
+ ' console.log("hook saw " + files.join(","));\n' +
55
+ ' process.stdout.write("hook raw write\\n");\n' +
56
+ ' return null;\n' +
57
+ '}}]}};\n',
58
+ );
59
+ });
60
+
61
+ afterEach(() => {
62
+ fs.rmSync(tmpDir, {recursive: true, force: true});
63
+ });
64
+
65
+ describe('upgrade post-codemod hook output', () => {
66
+ it('keeps --json stdout to one envelope and sends hook output to stderr', () => {
67
+ const r = runCli([
68
+ '--json',
69
+ 'upgrade',
70
+ '--from',
71
+ '0.5.0',
72
+ '--codemod',
73
+ 'rename-resizable-pixel-bounds',
74
+ '--apply',
75
+ ]);
76
+
77
+ expect(r.signal).toBeNull();
78
+ expect(r.status).toBe(0);
79
+ const envelope = JSON.parse(r.stdout);
80
+ expect(envelope.type).toBe('upgrade.run');
81
+ expect(envelope.data.filesChanged).toBe(1);
82
+ expect(r.stderr).toContain('hook saw src/panel.tsx');
83
+ expect(r.stderr).toContain('hook raw write');
84
+ expect(fs.readFileSync(path.join(tmpDir, 'src/panel.tsx'), 'utf8')).toContain(
85
+ 'minSize: 200',
86
+ );
87
+ });
88
+ });
@@ -13,6 +13,7 @@
13
13
  */
14
14
 
15
15
  import {jsonOut, jsonError} from '../../../foundation/response/json.mjs';
16
+ import {emit, section, records} from '../formatters/index.mjs';
16
17
  import {logger} from '../../../api/logger.mjs';
17
18
  import {AstryxError} from '../../../api/error.mjs';
18
19
  import {defineCommand} from '../lib/define-command.mjs';
@@ -33,27 +34,45 @@ export function registerUpgrade(program) {
33
34
  */
34
35
  async options => {
35
36
  const json = program.opts().json || false;
36
- logger.setSilent(json);
37
+ const isList = Boolean(options.list);
38
+ // Silence the logger for --list so the handler renders from the
39
+ // result (parity with --json), and for --json as before.
40
+ // Re-enable on error so handled failures still print.
41
+ logger.setSilent(json || isList);
37
42
 
38
43
  /** @type {import('../../../api/upgrade/upgrade.type.mjs').UpgradeListResponse | import('../../../api/upgrade/upgrade.type.mjs').UpgradeRegistryResponse | import('../../../api/upgrade/upgrade.type.mjs').UpgradeStatusResponse | import('../../../api/upgrade/upgrade.type.mjs').UpgradeRunResponse} */
39
44
  let result;
40
45
  try {
41
46
  result = await upgradeApi(options, {cwd: process.cwd()});
42
47
  } catch (e) {
43
- // Handled failures throw AstryxError: --json emits the structured
44
- // envelope (byte-identical to the old inline jsonError) + exits 1;
45
- // human mode already saw the error line + outro via the logger, so we
46
- // just set the exit code. Anything else is unexpected — let the
47
- // top-level boundary in bin/astryx.mjs format it as before.
48
+ // Re-enable the logger so the error is visible in text mode.
49
+ // For --list the logger was silent; for other modes the API
50
+ // already printed the error before throwing.
51
+ logger.setSilent(json);
48
52
  if (e instanceof AstryxError) {
49
53
  if (json) jsonError(e.message, undefined, e.code);
50
- else process.exitCode = 1;
54
+ else {
55
+ // The logger was silent only for --list; replay the error there.
56
+ // Non-list failures were already printed by the API.
57
+ if (isList) logger.error(e.message);
58
+ process.exitCode = 1;
59
+ }
51
60
  return NO_RESULT_SET;
52
61
  }
53
62
  throw e;
54
63
  }
55
64
 
56
65
  if (json) jsonOut(result);
66
+ else if (result.type === 'upgrade.list') {
67
+ // Render the list from the result, not the API logger, so the text
68
+ // carries the same fields the JSON does (name, title, version, optional).
69
+ emit(
70
+ section('Available codemods'),
71
+ records(result.data, {
72
+ fields: ['name', 'title', 'version', 'optional'],
73
+ }),
74
+ );
75
+ }
57
76
 
58
77
  const registrySummary =
59
78
  result.type === 'upgrade.registry'
@@ -65,6 +84,9 @@ export function registerUpgrade(program) {
65
84
  ? result.data.registryCompositions
66
85
  : undefined;
67
86
  if (registrySummary && !registrySummary.ok) process.exitCode = 1;
87
+ if (result.type === 'upgrade.run' && result.data.complete === false) {
88
+ process.exitCode = 1;
89
+ }
68
90
 
69
91
  // `--list` is the one lookup here: which migrations exist for the
70
92
  // range. Everything else migrates the project — an effect, reported by
@@ -11,7 +11,8 @@
11
11
  * which fields to show and in what order.
12
12
  *
13
13
  * Constraints (deliberately narrow):
14
- * - Plain ASCII only. No color, no TTY detection, no width wrapping. Output is
14
+ * - Plain ASCII only. No color and no TTY detection. Long lines wrap at a
15
+ * fixed {@link WRAP_WIDTH}, never at the terminal's width, so output is
15
16
  * byte-for-byte deterministic whether printed or piped to an agent.
16
17
  * - Renderers return an opaque {@link Block}; `emit` accepts ONLY Blocks, so a
17
18
  * stray string can't leak onto stdout (the compiler rejects `emit('x')`).
@@ -33,6 +34,50 @@ export const BULLET = '-';
33
34
  export const ERR = '!!';
34
35
  export const WARN = '!';
35
36
 
37
+ /** The column long human-output lines wrap at. */
38
+ export const WRAP_WIDTH = 120;
39
+
40
+ /** The widest first column an inline record pads to; a longer value overhangs. */
41
+ const INLINE_LEAD_MAX = 32;
42
+
43
+ /**
44
+ * Characters a terminal draws two columns wide: CJK ideographs and
45
+ * punctuation, kana, hangul, and fullwidth forms. A line may break between
46
+ * any two of them.
47
+ */
48
+ const WIDE_CHAR =
49
+ /[\u1100-\u115f\u2e80-\u303e\u3041-\u33ff\u3400-\u4dbf\u4e00-\u9fff\ua000-\ua4cf\uac00-\ud7a3\uf900-\ufaff\ufe30-\ufe4f\uff00-\uff60\uffe0-\uffe6]/u;
50
+
51
+ /**
52
+ * How many terminal columns a string takes.
53
+ * @param {string} s
54
+ * @returns {number}
55
+ */
56
+ export function displayWidth(s) {
57
+ let width = 0;
58
+ for (const ch of String(s)) width += WIDE_CHAR.test(ch) ? 2 : 1;
59
+ return width;
60
+ }
61
+
62
+ /**
63
+ * Cut a line to `width` columns, ending it with `...` when anything was cut.
64
+ * @param {string} line
65
+ * @param {number} width
66
+ * @returns {string}
67
+ */
68
+ function truncateToWidth(line, width) {
69
+ if (displayWidth(line) <= width) return line;
70
+ let out = '';
71
+ let used = 0;
72
+ for (const ch of line) {
73
+ const w = WIDE_CHAR.test(ch) ? 2 : 1;
74
+ if (used + w > width - 3) break;
75
+ out += ch;
76
+ used += w;
77
+ }
78
+ return `${out.trimEnd()}...`;
79
+ }
80
+
36
81
  /**
37
82
  * An opaque, renderer-produced block of output. Nominal via a private field:
38
83
  * nothing outside this file can construct one, so `emit` can trust that whatever
@@ -67,6 +112,13 @@ export class Block {
67
112
  * @property {Record<string, string>} [labels] - Rename a key for display.
68
113
  * @property {Record<string, (value: any) => string>} [format] - Transform a
69
114
  * value before rendering (e.g. prefix a command with the package manager).
115
+ * @property {'stacked' | 'inline'} [layout] - `stacked` (the default): one
116
+ * `key: value` line per field, a blank line between records. `inline`: one
117
+ * record per line, the first field in a padded column and the rest joined by
118
+ * ` - `, for a list a reader scans.
119
+ * @property {'wrap' | 'truncate'} [overflow] - What an inline record longer
120
+ * than {@link WRAP_WIDTH} does: `wrap` (the default) continues under the
121
+ * first column; `truncate` cuts it to one line.
70
122
  */
71
123
 
72
124
  /** @param {unknown} v @returns {boolean} */
@@ -100,9 +152,77 @@ function toAscii(s) {
100
152
  .replace(/[\u2018\u2019]/g, "'")
101
153
  .replace(/[\u201C\u201D]/g, '"')
102
154
  .replace(/\u2026/g, '...')
155
+ .replace(/\u2192/g, '->')
156
+ .replace(/\u2190/g, '<-')
103
157
  .replace(/\u00a0/g, ' ');
104
158
  }
105
159
 
160
+ /**
161
+ * Word-wrap text at `width`, keeping its own line breaks. A word longer than
162
+ * the width stays whole on a line of its own. Continuation lines start with
163
+ * `indent`.
164
+ * @param {string} input
165
+ * @param {{width?: number, indent?: string}} [options]
166
+ * @returns {string}
167
+ */
168
+ export function wrapText(input, {width = WRAP_WIDTH, indent = ''} = {}) {
169
+ return String(input)
170
+ .split('\n')
171
+ .map(line => wrapLine(line, width, indent))
172
+ .join('\n');
173
+ }
174
+
175
+ /**
176
+ * @param {string} line
177
+ * @param {number} width
178
+ * @param {string} indent
179
+ * @returns {string}
180
+ */
181
+ function wrapLine(line, width, indent) {
182
+ if (displayWidth(line) <= width) return line;
183
+ const lead = /^\s*/.exec(line)?.[0] ?? '';
184
+ // Tokens a line may break between: words, and each wide character on its
185
+ // own, since CJK text has no spaces to break at. `gap` is what joined a
186
+ // token to the one before it.
187
+ /** @type {{text: string, gap: string}[]} */
188
+ const tokens = [];
189
+ let gap = '';
190
+ let word = '';
191
+ const flush = () => {
192
+ if (word === '') return;
193
+ tokens.push({text: word, gap});
194
+ gap = '';
195
+ word = '';
196
+ };
197
+ for (const ch of line.slice(lead.length)) {
198
+ if (ch === ' ') {
199
+ flush();
200
+ gap = ' ';
201
+ } else if (WIDE_CHAR.test(ch)) {
202
+ flush();
203
+ tokens.push({text: ch, gap});
204
+ gap = '';
205
+ } else {
206
+ word += ch;
207
+ }
208
+ }
209
+ flush();
210
+ /** @type {string[]} */
211
+ const out = [];
212
+ let current = lead;
213
+ tokens.forEach(({text: token, gap: before}, i) => {
214
+ const joined = i === 0 ? current + token : current + before + token;
215
+ if (i > 0 && displayWidth(joined) > width && current.trim() !== '') {
216
+ out.push(current);
217
+ current = indent + token;
218
+ } else {
219
+ current = joined;
220
+ }
221
+ });
222
+ out.push(current);
223
+ return out.join('\n');
224
+ }
225
+
106
226
  /**
107
227
  * A group label, optionally with an explanatory subtitle rendered on the line(s)
108
228
  * directly beneath the heading (no blank line between). Whatever list/records
@@ -177,10 +297,53 @@ export function record(obj, options = {}) {
177
297
  * @returns {Block}
178
298
  */
179
299
  export function records(items, options = {}) {
300
+ if (options.layout === 'inline') return inlineRecords(items, options);
180
301
  const blocks = items.map(o => record(o, options).toString()).filter(Boolean);
181
302
  return new Block(blocks.join('\n\n'));
182
303
  }
183
304
 
305
+ /**
306
+ * @param {any[]} items
307
+ * @param {RecordOptions} options
308
+ * @returns {Block}
309
+ */
310
+ function inlineRecords(items, options) {
311
+ const [lead, ...rest] = (options.fields ?? Object.keys(items[0] ?? {})).filter(
312
+ k => !options.omit?.includes(k),
313
+ );
314
+ if (lead == null) return new Block('');
315
+ /** @param {any} o @param {string} k */
316
+ const value = (o, k) => {
317
+ const fmt = options.format?.[k];
318
+ return fmt ? fmt(o[k]) : renderValue(o[k]);
319
+ };
320
+ const leads = items.map(o => (isEmpty(o[lead]) ? '' : value(o, lead)));
321
+ const column = Math.min(
322
+ Math.max(0, ...leads.map(l => displayWidth(l))),
323
+ INLINE_LEAD_MAX,
324
+ );
325
+ const indent = ' '.repeat(column + 2);
326
+ const lines = items.map((o, i) => {
327
+ const tail = toAscii(
328
+ rest
329
+ .filter(k => !isEmpty(o[k]))
330
+ .map(k => value(o, k))
331
+ .join(' - '),
332
+ );
333
+ if (tail === '') return toAscii(leads[i]);
334
+ const head = toAscii(`${leads[i].padEnd(column)} `);
335
+ if (options.overflow === 'truncate') {
336
+ return truncateToWidth(head + tail, WRAP_WIDTH);
337
+ }
338
+ // Wrap only the tail, so the padded first column survives the wrap.
339
+ const [first, ...more] = wrapText(tail, {
340
+ width: Math.max(20, WRAP_WIDTH - displayWidth(head)),
341
+ }).split('\n');
342
+ return [head + first, ...more.map(line => indent + line)].join('\n');
343
+ });
344
+ return new Block(lines.join('\n'));
345
+ }
346
+
184
347
  /**
185
348
  * A verbatim block — source dumps, layout skeletons, or a markdown doc. Output
186
349
  * is byte-for-byte (NOT ASCII-normalized), so it survives piping
@@ -8,6 +8,9 @@ import {
8
8
  list,
9
9
  record,
10
10
  records,
11
+ wrapText,
12
+ displayWidth,
13
+ WRAP_WIDTH,
11
14
  code,
12
15
  Block,
13
16
  BULLET,
@@ -114,6 +117,12 @@ describe('ASCII normalization', () => {
114
117
  expect(list(['Avatar \u2014 Group']).toString()).toBe('- Avatar - Group');
115
118
  });
116
119
 
120
+ it('converts Unicode arrows to ASCII arrows', () => {
121
+ expect(text('Rename items \u2192 options').toString()).toBe('Rename items -> options');
122
+ expect(text('options \u2190 items').toString()).toBe('options <- items');
123
+ expect(record({title: 'a \u2192 b'}).toString()).toBe('title: a -> b');
124
+ });
125
+
117
126
  it('leaves code verbatim (no normalization)', () => {
118
127
  expect(code('a \u2014 b').toString()).toBe('a \u2014 b');
119
128
  });
@@ -148,3 +157,91 @@ describe('emit', () => {
148
157
  expect(spy).not.toHaveBeenCalled();
149
158
  });
150
159
  });
160
+
161
+ describe('wrapText', () => {
162
+ it('leaves short lines and its own line breaks alone', () => {
163
+ expect(wrapText('a b\nc')).toBe('a b\nc');
164
+ });
165
+
166
+ it('wraps at the width with a hanging indent', () => {
167
+ expect(wrapText('aaa bbb ccc', {width: 7, indent: ' '})).toBe('aaa bbb\n ccc');
168
+ });
169
+
170
+ it('keeps a word longer than the width whole', () => {
171
+ expect(wrapText('x '.concat('y'.repeat(20)), {width: 10})).toBe(
172
+ `x\n${'y'.repeat(20)}`,
173
+ );
174
+ });
175
+ });
176
+
177
+ describe('records inline layout', () => {
178
+ const items = [
179
+ {id: 'a', title: 'Alpha', summary: 'First.'},
180
+ {id: 'bbb', title: 'Beta', summary: ''},
181
+ ];
182
+
183
+ it('puts one record on each line under a padded first column', () => {
184
+ expect(
185
+ records(items, {fields: ['id', 'title', 'summary'], layout: 'inline'}).toString(),
186
+ ).toBe('a Alpha - First.\nbbb Beta');
187
+ });
188
+
189
+ it('wraps a long record under the first column', () => {
190
+ const out = records([{id: 'a', text: 'word '.repeat(40).trim()}], {
191
+ layout: 'inline',
192
+ }).toString();
193
+ expect(out.split('\n').length).toBeGreaterThan(1);
194
+ expect(out.split('\n').every(line => line.length <= WRAP_WIDTH)).toBe(true);
195
+ expect(out.split('\n')[1].startsWith(' word')).toBe(true);
196
+ });
197
+
198
+ it('keeps the first column padded on a record that wraps', () => {
199
+ const out = records(
200
+ [
201
+ {id: 'a', text: 'word '.repeat(40).trim()},
202
+ {id: 'longer', text: 'x'},
203
+ ],
204
+ {layout: 'inline'},
205
+ ).toString();
206
+ const lines = out.split('\n');
207
+ expect(lines[0].startsWith(`a${' '.repeat(7)}word`)).toBe(true);
208
+ expect(lines[1].startsWith(' '.repeat(8) + 'word')).toBe(true);
209
+ expect(lines.at(-1)).toBe('longer x');
210
+ expect(lines.every(line => line.length <= WRAP_WIDTH)).toBe(true);
211
+ });
212
+
213
+ it('cuts a long record to one line when asked', () => {
214
+ const out = records([{id: 'a', text: 'word '.repeat(40).trim()}], {
215
+ layout: 'inline',
216
+ overflow: 'truncate',
217
+ }).toString();
218
+ expect(out.includes('\n')).toBe(false);
219
+ expect(out.length).toBeLessThanOrEqual(WRAP_WIDTH);
220
+ expect(out.endsWith('...')).toBe(true);
221
+ });
222
+ });
223
+
224
+ describe('wide characters', () => {
225
+ it('counts CJK characters as two columns', () => {
226
+ expect(displayWidth('亮/暗模式')).toBe(9);
227
+ });
228
+
229
+ it('wraps CJK text, which has no spaces, by columns', () => {
230
+ const out = wrapText('中'.repeat(100), {width: 20});
231
+ expect(out.split('\n').every(line => displayWidth(line) <= 20)).toBe(true);
232
+ expect(out.split('\n')).toHaveLength(10);
233
+ });
234
+
235
+ it('keeps spaces between Latin words next to CJK', () => {
236
+ expect(wrapText('运行 astryx docs 查看', {width: 200})).toBe('运行 astryx docs 查看');
237
+ });
238
+
239
+ it('cuts an inline record to columns, not characters', () => {
240
+ const out = records([{id: 'a', text: '中'.repeat(100)}], {
241
+ layout: 'inline',
242
+ overflow: 'truncate',
243
+ }).toString();
244
+ expect(displayWidth(out)).toBeLessThanOrEqual(WRAP_WIDTH);
245
+ expect(out.endsWith('...')).toBe(true);
246
+ });
247
+ });