@astryxdesign/cli 0.6.3-canary.db4e378 → 0.6.3-canary.db7b338

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 (276) hide show
  1. package/README.md +22 -1
  2. package/api/blog/blog.doc.mjs +1 -0
  3. package/api/build/build.doc.mjs +1 -0
  4. package/api/component/component.doc.mjs +1 -0
  5. package/api/discover/discover.doc.mjs +1 -0
  6. package/api/docs/_adapter.d.mts +3 -8
  7. package/api/docs/_adapter.mjs +13 -105
  8. package/api/docs/detail/section/section.mjs +9 -15
  9. package/api/docs/detail/section/section.test.mjs +15 -6
  10. package/api/docs/docs.doc.mjs +1 -0
  11. package/api/docs/list/list.mjs +3 -7
  12. package/api/doctor/doctor.d.mts +25 -3
  13. package/api/doctor/doctor.doc.mjs +1 -0
  14. package/api/doctor/doctor.mjs +186 -14
  15. package/api/doctor/doctor.test.mjs +332 -7
  16. package/api/gap-report/gap-report.doc.mjs +1 -0
  17. package/api/hook/_adapter.mjs +19 -5
  18. package/api/hook/hook.doc.mjs +1 -0
  19. package/api/hook/list/list.d.mts +1 -1
  20. package/api/hook/list/list.mjs +69 -17
  21. package/api/init/init.doc.mjs +1 -0
  22. package/api/integration/add-contribution.mjs +7 -7
  23. package/api/integration/add-contribution.test.mjs +37 -3
  24. package/api/integration/add-theme.mjs +34 -64
  25. package/api/integration/add-theme.test.mjs +105 -21
  26. package/api/integration/authoring-checks.test.mjs +8 -4
  27. package/api/integration/integration-block-exports.test.mjs +10 -6
  28. package/api/integration/integrationAdd.doc.mjs +1 -0
  29. package/api/integration/integrationAddAgentDoc.doc.mjs +1 -0
  30. package/api/integration/integrationAddCodemod.doc.mjs +1 -0
  31. package/api/integration/integrationAddComponent.doc.mjs +1 -0
  32. package/api/integration/integrationAddDoc.doc.mjs +1 -0
  33. package/api/integration/integrationAddTemplate.doc.mjs +1 -0
  34. package/api/integration/integrationAddTheme.doc.mjs +6 -5
  35. package/api/integration/integrationComponentConflicts.doc.mjs +1 -0
  36. package/api/integration/integrationDocConflicts.doc.mjs +1 -0
  37. package/api/integration/integrationPackCheck.doc.mjs +1 -0
  38. package/api/integration/integrationTemplateConflicts.doc.mjs +1 -0
  39. package/api/integration/pack-check.test.mjs +17 -47
  40. package/api/integration/summarizeIssues.doc.mjs +1 -0
  41. package/api/integration/validate-integration-fixes.test.mjs +1389 -0
  42. package/api/integration/validate-integration.mjs +50 -102
  43. package/api/integration/validate-integration.test.mjs +85 -23
  44. package/api/integration/validate-unread-theme-folders.test.mjs +110 -0
  45. package/api/integration/validateIntegration.doc.mjs +1 -0
  46. package/api/json/assertResponse.doc.mjs +1 -0
  47. package/api/json/isError.doc.mjs +1 -0
  48. package/api/json/parseResponse.doc.mjs +1 -0
  49. package/api/layout/layoutCheck.doc.mjs +1 -0
  50. package/api/layout/layoutExpand.doc.mjs +1 -0
  51. package/api/layout/layoutGrammar.doc.mjs +1 -0
  52. package/api/search/search.doc.mjs +1 -0
  53. package/api/search/search.mjs +16 -6
  54. package/api/swizzle/swizzle.doc.mjs +1 -0
  55. package/api/template/template-suffix.test.mjs +41 -21
  56. package/api/template/template.doc.mjs +1 -0
  57. package/api/theme/_adapter.d.mts +2 -3
  58. package/api/theme/_adapter.mjs +4 -5
  59. package/api/theme/add/add.binary.test.mjs +10 -17
  60. package/api/theme/add/add.test.mjs +14 -1
  61. package/api/theme/generateTonalPalette.doc.mjs +1 -0
  62. package/api/theme/integration-themes.test.mjs +39 -28
  63. package/api/theme/list/list.test.mjs +19 -20
  64. package/api/theme/listThemes.doc.mjs +6 -5
  65. package/api/theme/themeAdd.doc.mjs +4 -3
  66. package/api/theme/themeBuild.doc.mjs +1 -0
  67. package/api/theme/themeList.doc.mjs +6 -3
  68. package/api/theme/themeListAvailable.doc.mjs +5 -3
  69. package/api/theme/themePaletteGenerate.doc.mjs +1 -0
  70. package/api/theme/themeTargets.doc.mjs +1 -0
  71. package/api/theme/themeTemplate.doc.mjs +1 -0
  72. package/api/upgrade/_adapter.d.mts +32 -5
  73. package/api/upgrade/_adapter.mjs +97 -69
  74. package/api/upgrade/provider-agreement.test.mjs +152 -0
  75. package/api/upgrade/run/run.mjs +356 -59
  76. package/api/upgrade/upgrade.doc.mjs +6 -1
  77. package/api/upgrade/upgrade.type.d.mts +34 -0
  78. package/api/upgrade/upgrade.type.mjs +15 -0
  79. package/assets/codemods/__tests__/runner.test.mjs +330 -8
  80. package/assets/codemods/integration-discovery.mjs +8 -2
  81. package/assets/codemods/integration-discovery.test.mjs +15 -0
  82. package/assets/codemods/integration-runner.mjs +56 -4
  83. package/assets/codemods/integration-runner.protection.test.mjs +153 -0
  84. package/assets/codemods/run-codemod.mjs +177 -34
  85. package/assets/codemods/runner.mjs +350 -102
  86. package/assets/codemods/transforms/next/__tests__/migrate-native-picker-to-presentation.test.mjs +63 -0
  87. package/assets/codemods/transforms/next/__tests__/migrate-theme-catalog-to-descriptors.test.mjs +220 -0
  88. package/assets/codemods/transforms/next/index.mjs +19 -1
  89. package/assets/codemods/transforms/next/migrate-native-picker-to-presentation.mjs +148 -0
  90. package/assets/codemods/transforms/next/migrate-theme-catalog-to-descriptors.mjs +141 -0
  91. package/assets/docs/cli-integrations.doc.mjs +30 -30
  92. package/assets/docs/cli.doc.mjs +15 -0
  93. package/assets/docs/theme.doc.mjs +1 -1
  94. package/assets/templates/blocks/components/DateInput/DateInputDateRange.tsx +1 -1
  95. package/assets/templates/blocks/components/Item/ItemDocumentTabs.doc.mjs +14 -0
  96. package/assets/templates/blocks/components/Item/ItemDocumentTabs.tsx +100 -0
  97. package/assets/templates/blocks/components/TimeInput/TimeInputConstrained.tsx +1 -0
  98. package/assets/templates/themes/butter/butterTheme.doc.mjs +11 -0
  99. package/assets/templates/themes/chocolate/chocolateTheme.doc.mjs +11 -0
  100. package/assets/templates/themes/gothic/gothicTheme.doc.mjs +11 -0
  101. package/assets/templates/themes/matcha/matchaTheme.doc.mjs +11 -0
  102. package/assets/templates/themes/neutral/neutralTheme.doc.mjs +11 -0
  103. package/assets/templates/themes/stone/stoneTheme.doc.mjs +11 -0
  104. package/assets/templates/themes/y2k/y2kTheme.doc.mjs +11 -0
  105. package/authoring/codemod/codemod.doc.mjs +1 -1
  106. package/authoring/codemod/type.ts +12 -0
  107. package/authoring/config/config.doc.mjs +1 -1
  108. package/authoring/debug/debug.doc.d.mts +11 -0
  109. package/authoring/debug/debug.doc.mjs +182 -0
  110. package/authoring/doctypes/_schema.d.mts +71 -141
  111. package/authoring/doctypes/_schema.mjs +29 -2
  112. package/authoring/doctypes/base/graph-fields.doc.mjs +1 -1
  113. package/authoring/doctypes/command/command.doc.mjs +1 -1
  114. package/authoring/doctypes/command/type.ts +1 -1
  115. package/authoring/doctypes/component/type.ts +2 -2
  116. package/authoring/doctypes/doctypes-new.test.mjs +48 -6
  117. package/authoring/doctypes/enum/enum.doc.mjs +1 -1
  118. package/authoring/doctypes/enum/type.ts +1 -1
  119. package/authoring/doctypes/function/function.doc.mjs +1 -1
  120. package/authoring/doctypes/function/type.ts +1 -1
  121. package/authoring/doctypes/hook/type.ts +2 -2
  122. package/authoring/doctypes/load-contract.test.mjs +3 -2
  123. package/authoring/doctypes/namespace/namespace.doc.mjs +2 -2
  124. package/authoring/doctypes/namespace/parse.test.mjs +23 -25
  125. package/authoring/doctypes/namespace/type.ts +5 -2
  126. package/authoring/doctypes/parse.d.mts +4 -2
  127. package/authoring/doctypes/parse.mjs +10 -5
  128. package/authoring/doctypes/reference/reference.doc.mjs +6 -4
  129. package/authoring/doctypes/reference/type.ts +9 -10
  130. package/authoring/doctypes/schema/schema.doc.mjs +1 -1
  131. package/authoring/doctypes/schema/type.ts +1 -2
  132. package/authoring/doctypes/template/template.doc.mjs +3 -3
  133. package/authoring/doctypes/theme/parse.d.mts +35 -0
  134. package/authoring/doctypes/theme/parse.mjs +76 -0
  135. package/authoring/doctypes/theme/theme.doc.d.mts +9 -0
  136. package/authoring/doctypes/theme/theme.doc.mjs +79 -0
  137. package/authoring/doctypes/theme/type.ts +42 -0
  138. package/authoring/doctypes/types.ts +2 -1
  139. package/authoring/gap-report/gap-report.doc.d.mts +12 -0
  140. package/authoring/gap-report/gap-report.doc.mjs +183 -0
  141. package/authoring/index.d.mts +1 -0
  142. package/authoring/index.d.ts +7 -4
  143. package/authoring/index.mjs +2 -1
  144. package/authoring/integration/integration.doc.mjs +2 -2
  145. package/authoring/integration/type.ts +3 -9
  146. package/clients/cli/__tests__/cliManifest.test.ts +27 -29
  147. package/clients/cli/commands/blog.doc.mjs +1 -1
  148. package/clients/cli/commands/build-theme.adaptations.test.mjs +100 -1
  149. package/clients/cli/commands/build-theme.mjs +10 -44
  150. package/clients/cli/commands/build.doc.mjs +1 -1
  151. package/clients/cli/commands/component.doc.mjs +1 -1
  152. package/clients/cli/commands/discover.doc.mjs +1 -1
  153. package/clients/cli/commands/docs.doc.mjs +1 -1
  154. package/clients/cli/commands/docs.mjs +15 -58
  155. package/clients/cli/commands/docs.test.mjs +11 -14
  156. package/clients/cli/commands/doctor-integration-components.doc.mjs +1 -1
  157. package/clients/cli/commands/doctor-integration-docs.doc.mjs +1 -1
  158. package/clients/cli/commands/doctor-integration-templates.doc.mjs +1 -1
  159. package/clients/cli/commands/doctor-integration-validate.doc.mjs +1 -1
  160. package/clients/cli/commands/doctor-integration.doc.mjs +1 -1
  161. package/clients/cli/commands/doctor-integration.test.mjs +15 -7
  162. package/clients/cli/commands/doctor.doc.mjs +1 -1
  163. package/clients/cli/commands/gap-report.doc.mjs +1 -1
  164. package/clients/cli/commands/hook.doc.mjs +1 -1
  165. package/clients/cli/commands/init.doc.mjs +1 -1
  166. package/clients/cli/commands/integration-add.controls.test.mjs +1 -1
  167. package/clients/cli/commands/integration-add.doc.mjs +1 -1
  168. package/clients/cli/commands/integration-pack.doc.mjs +1 -1
  169. package/clients/cli/commands/integration-real-world.test.mjs +3 -9
  170. package/clients/cli/commands/integration.doc.mjs +1 -1
  171. package/clients/cli/commands/layout-check.doc.mjs +1 -1
  172. package/clients/cli/commands/layout-expand.doc.mjs +1 -1
  173. package/clients/cli/commands/layout-grammar.doc.mjs +1 -1
  174. package/clients/cli/commands/layout.doc.mjs +1 -1
  175. package/clients/cli/commands/manifest.doc.mjs +1 -1
  176. package/clients/cli/commands/search.doc.mjs +1 -1
  177. package/clients/cli/commands/swizzle.doc.mjs +1 -1
  178. package/clients/cli/commands/template.doc.mjs +1 -1
  179. package/clients/cli/commands/text-json-parity.test.mjs +718 -0
  180. package/clients/cli/commands/theme-add.doc.mjs +2 -2
  181. package/clients/cli/commands/theme-build.doc.mjs +1 -1
  182. package/clients/cli/commands/theme-list.doc.mjs +2 -2
  183. package/clients/cli/commands/theme-palette-generate.doc.mjs +3 -3
  184. package/clients/cli/commands/theme-palette.doc.mjs +1 -1
  185. package/clients/cli/commands/theme-targets.behavior.test.mjs +4 -3
  186. package/clients/cli/commands/theme-targets.doc.mjs +1 -1
  187. package/clients/cli/commands/theme-template.doc.mjs +1 -1
  188. package/clients/cli/commands/theme.doc.mjs +1 -1
  189. package/clients/cli/commands/upgrade.doc.mjs +3 -2
  190. package/clients/cli/commands/upgrade.file-protection.test.mjs +228 -0
  191. package/clients/cli/commands/upgrade.mjs +29 -7
  192. package/clients/cli/formatters/index.mjs +2 -0
  193. package/clients/cli/formatters/index.test.mjs +6 -0
  194. package/clients/cli/lib/hook-format.mjs +14 -5
  195. package/foundation/config/project-themes.test.mjs +11 -19
  196. package/foundation/config/project.d.mts +8 -0
  197. package/foundation/config/project.mjs +50 -21
  198. package/foundation/config/project.test.mjs +3 -16
  199. package/foundation/discovery/authoring-self-docs.d.mts +6 -0
  200. package/foundation/discovery/authoring-self-docs.mjs +17 -7
  201. package/foundation/discovery/authoring-self-docs.test.mjs +3 -2
  202. package/foundation/discovery/authoring-surface.d.mts +74 -0
  203. package/foundation/discovery/authoring-surface.mjs +525 -0
  204. package/foundation/discovery/authoring-surface.test.mjs +392 -0
  205. package/foundation/discovery/cli-self-docs.d.mts +113 -0
  206. package/foundation/discovery/cli-self-docs.mjs +514 -0
  207. package/foundation/discovery/cli-self-docs.test.mjs +437 -0
  208. package/foundation/discovery/component-loader.d.mts +35 -38
  209. package/foundation/discovery/component-loader.mjs +53 -222
  210. package/foundation/discovery/docs-discovery.d.mts +3 -2
  211. package/foundation/discovery/docs-discovery.mjs +14 -17
  212. package/foundation/discovery/docs-discovery.test.mjs +15 -15
  213. package/foundation/discovery/docs-section-key.d.mts +19 -8
  214. package/foundation/discovery/docs-section-key.mjs +119 -32
  215. package/foundation/discovery/docs-section-key.test.mjs +41 -19
  216. package/foundation/discovery/template-adapter.d.mts +7 -0
  217. package/foundation/discovery/template-adapter.mjs +27 -40
  218. package/foundation/discovery/template-adapter.test.mjs +15 -0
  219. package/foundation/discovery/theme-discovery.d.mts +67 -7
  220. package/foundation/discovery/theme-discovery.mjs +916 -186
  221. package/foundation/discovery/theme-discovery.test.mjs +613 -219
  222. package/foundation/doc-compiler/bundle.d.mts +47 -0
  223. package/foundation/doc-compiler/bundle.mjs +218 -0
  224. package/foundation/doc-compiler/bundle.test.mjs +255 -0
  225. package/foundation/doc-compiler/compile.d.mts +200 -0
  226. package/foundation/doc-compiler/compile.mjs +259 -9
  227. package/foundation/doc-compiler/diagnostics.d.mts +126 -0
  228. package/foundation/doc-compiler/diagnostics.mjs +277 -0
  229. package/foundation/doc-compiler/doc-compiler.test.mjs +28 -1
  230. package/foundation/doc-compiler/doc-loads.test.mjs +1642 -0
  231. package/foundation/doc-compiler/import.d.mts +24 -0
  232. package/foundation/doc-compiler/import.mjs +59 -0
  233. package/foundation/doc-compiler/inputs.d.mts +102 -0
  234. package/foundation/doc-compiler/inputs.mjs +286 -0
  235. package/foundation/doc-compiler/inputs.test.mjs +299 -0
  236. package/foundation/doc-compiler/ir.d.mts +13 -0
  237. package/foundation/doc-compiler/ir.mjs +189 -12
  238. package/foundation/doc-compiler/lower-doc.test.mjs +492 -0
  239. package/foundation/doc-compiler/overlays.d.mts +37 -0
  240. package/foundation/doc-compiler/overlays.mjs +206 -0
  241. package/foundation/doc-compiler/parse-readable.d.mts +9 -0
  242. package/foundation/doc-compiler/parse-readable.mjs +29 -0
  243. package/foundation/doc-compiler/read.d.mts +126 -0
  244. package/foundation/doc-compiler/read.mjs +320 -0
  245. package/foundation/doc-compiler/read.test.mjs +313 -0
  246. package/foundation/doc-compiler/source.d.mts +33 -0
  247. package/foundation/doc-compiler/source.mjs +128 -0
  248. package/foundation/fs/file-protection.d.mts +33 -0
  249. package/foundation/fs/file-protection.mjs +825 -0
  250. package/foundation/fs/file-protection.test.mjs +250 -0
  251. package/foundation/integrations/autolink.d.mts +58 -1
  252. package/foundation/integrations/autolink.mjs +143 -57
  253. package/foundation/integrations/contribution-fixes.d.mts +145 -0
  254. package/foundation/integrations/contribution-fixes.mjs +1284 -0
  255. package/foundation/integrations/contribution-inventory.d.mts +1 -1
  256. package/foundation/integrations/contribution-inventory.mjs +17 -20
  257. package/foundation/integrations/contribution-inventory.test.mjs +67 -27
  258. package/foundation/integrations/integrations.d.mts +3 -0
  259. package/foundation/integrations/integrations.mjs +11 -97
  260. package/foundation/integrations/provider-ledger.test.mjs +275 -0
  261. package/foundation/integrations/provider-resolution.d.mts +152 -0
  262. package/foundation/integrations/provider-resolution.mjs +576 -0
  263. package/foundation/integrations/provider-resolution.test.mjs +369 -0
  264. package/foundation/integrations/theme-descriptor.d.mts +8 -0
  265. package/foundation/integrations/theme-descriptor.mjs +44 -0
  266. package/foundation/integrations/validate-contributions.mjs +104 -10
  267. package/foundation/response/error-codes.d.mts +3 -1
  268. package/foundation/response/error-codes.d.ts +2 -0
  269. package/foundation/response/error-codes.doc.mjs +12 -2
  270. package/foundation/response/error-codes.mjs +7 -1
  271. package/foundation/response/error-codes.test.mjs +55 -11
  272. package/foundation/response/response-types.doc.mjs +1 -1
  273. package/foundation/response/response.doc.mjs +1 -1
  274. package/foundation/text/string-utils.mjs +22 -10
  275. package/package.json +10 -9
  276. package/assets/templates/themes/manifest.json +0 -95
@@ -0,0 +1,299 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Every authored descriptor becomes exactly one compiler input.
5
+ *
6
+ * The repo check is the completeness oracle for what Core and the CLI ship:
7
+ * every `.doc.mjs` under a descriptor root is read once, and nothing else is.
8
+ */
9
+
10
+ import {afterEach, beforeEach, describe, expect, it} from 'vitest';
11
+ import * as fs from 'node:fs';
12
+ import * as os from 'node:os';
13
+ import * as path from 'node:path';
14
+ import {Project} from '../config/project.mjs';
15
+ import {CLI_ROOT} from '../fs/paths.mjs';
16
+ import {collectDocInputs, packageSource} from './inputs.mjs';
17
+
18
+ const REPO_ROOT = path.resolve(CLI_ROOT, '..', '..');
19
+ const SKIP = new Set(['__tests__', '__fixtures__', 'fixtures', 'node_modules']);
20
+
21
+ /**
22
+ * Every `.doc.mjs` under a directory, by real path.
23
+ * @param {string} dir
24
+ * @param {Set<string>} into
25
+ */
26
+ function docFiles(dir, into) {
27
+ for (const entry of fs.readdirSync(dir, {withFileTypes: true})) {
28
+ const full = path.join(dir, entry.name);
29
+ if (entry.isDirectory()) {
30
+ if (!SKIP.has(entry.name)) docFiles(full, into);
31
+ } else if (entry.name.endsWith('.doc.mjs')) {
32
+ into.add(fs.realpathSync(full));
33
+ }
34
+ }
35
+ return into;
36
+ }
37
+
38
+ let tmpDir;
39
+
40
+ beforeEach(() => {
41
+ // Inside the repo, so the project finds this checkout's Core.
42
+ tmpDir = fs.mkdtempSync(path.join(REPO_ROOT, '.astryx-doc-inputs-'));
43
+ fs.writeFileSync(
44
+ path.join(tmpDir, 'package.json'),
45
+ JSON.stringify({name: 'consumer', version: '1.0.0'}),
46
+ );
47
+ });
48
+
49
+ afterEach(() => {
50
+ fs.rmSync(tmpDir, {recursive: true, force: true});
51
+ });
52
+
53
+ describe('collectDocInputs over what this repo ships', () => {
54
+ it('reads every descriptor Core and the CLI ship exactly once', async () => {
55
+ const {inputs, problems} = await collectDocInputs(
56
+ await Project.load(tmpDir),
57
+ );
58
+ expect(problems).toEqual([]);
59
+
60
+ // What ships comes from the packages themselves, not from the module under
61
+ // test: Core's source, and every directory the CLI's package.json ships.
62
+ const shipped = new Set();
63
+ docFiles(path.join(REPO_ROOT, 'packages', 'core', 'src'), shipped);
64
+ const {files} = JSON.parse(
65
+ fs.readFileSync(path.join(CLI_ROOT, 'package.json'), 'utf-8'),
66
+ );
67
+ for (const entry of files) {
68
+ const full = path.join(CLI_ROOT, entry);
69
+ if (fs.statSync(full).isDirectory()) docFiles(full, shipped);
70
+ }
71
+ const read = inputs.map(input => input.file);
72
+ const rel = (/** @type {string} */ file) => path.relative(REPO_ROOT, file);
73
+ expect(read.filter(file => !shipped.has(file)).map(rel)).toEqual([]);
74
+ expect([...shipped].filter(file => !read.includes(file)).map(rel)).toEqual(
75
+ [],
76
+ );
77
+ expect(new Set(read).size).toBe(read.length);
78
+ expect(new Set(inputs.map(input => input.id)).size).toBe(inputs.length);
79
+ }, 120_000);
80
+
81
+ it('pins the core docs that `component <Name>` reads but the list leaves out', async () => {
82
+ const {inputs} = await collectDocInputs(await Project.load(tmpDir));
83
+ // Each is readable by name; listing them would change `component --list`,
84
+ // so a new entry here is a decision, not a silent drift.
85
+ expect(
86
+ inputs
87
+ .filter(input => input.listed === false)
88
+ .map(input => input.name)
89
+ .sort(),
90
+ ).toEqual([
91
+ 'Chat',
92
+ 'ChatComposerTokenElement',
93
+ 'Code',
94
+ 'ContextMenuItem',
95
+ 'Heading',
96
+ 'Indicator',
97
+ 'Layer',
98
+ 'NavMenu',
99
+ 'Resizable',
100
+ 'SyntaxTheme',
101
+ ]);
102
+ });
103
+
104
+ it('keys templates by type and directory, not by their display title', async () => {
105
+ const {inputs} = await collectDocInputs(await Project.load(tmpDir));
106
+ const byName = new Map(
107
+ inputs
108
+ .filter(input => input.root === 'templates')
109
+ .map(input => [input.name, input]),
110
+ );
111
+ // A showcase shares its title with the block it shows; both stay inputs.
112
+ expect(byName.get('block/BannerStatuses')?.source).toBe(
113
+ '@astryxdesign/cli/assets/templates/blocks/components/Banner/BannerStatuses.doc.mjs',
114
+ );
115
+ expect(byName.get('block/BannerShowcase')?.source).toBe(
116
+ '@astryxdesign/cli/assets/templates/blocks/components/Banner/BannerShowcase.doc.mjs',
117
+ );
118
+ });
119
+ });
120
+
121
+ describe('collectDocInputs over an integration', () => {
122
+ /**
123
+ * @param {string} rel
124
+ * @param {string} text
125
+ */
126
+ function write(rel, text) {
127
+ const file = path.join(tmpDir, 'node_modules', '@acme', 'kit', rel);
128
+ fs.mkdirSync(path.dirname(file), {recursive: true});
129
+ fs.writeFileSync(file, text);
130
+ }
131
+
132
+ it('lists each descriptor once, owned by the package, with where it ships', async () => {
133
+ fs.writeFileSync(
134
+ path.join(tmpDir, 'package.json'),
135
+ JSON.stringify({
136
+ name: 'consumer',
137
+ version: '1.0.0',
138
+ dependencies: {'@acme/kit': '^1.0.0'},
139
+ }),
140
+ );
141
+ write(
142
+ 'package.json',
143
+ JSON.stringify({name: '@acme/kit', version: '1.0.0'}),
144
+ );
145
+ write(
146
+ 'astryx.integration.mjs',
147
+ "export default {components: './components', templates: './templates', docs: './docs', themes: './themes'};\n",
148
+ );
149
+ write(
150
+ 'components/AcmeCard.tsx',
151
+ 'export function AcmeCard() { return null; }\n',
152
+ );
153
+ write(
154
+ 'components/AcmeCard.doc.mjs',
155
+ "export default {type: 'component', name: 'AcmeCard', displayName: 'Acme Card', usage: {description: 'A card.'}, props: []};\n",
156
+ );
157
+ write(
158
+ 'templates/hero.tsx',
159
+ 'export default function Hero() { return null; }\n',
160
+ );
161
+ write(
162
+ 'templates/hero.doc.mjs',
163
+ "export default {type: 'block', name: 'Hero block', description: 'A hero.'};\n",
164
+ );
165
+ const topic = (/** @type {object} */ fields) =>
166
+ `export default ${JSON.stringify({
167
+ type: 'generic',
168
+ title: 'Acme',
169
+ description: 'Acme notes.',
170
+ sections: [{title: 'Acme', content: [{type: 'prose', text: 'Hi.'}]}],
171
+ ...fields,
172
+ })};\n`;
173
+ write('docs/acme-guide.doc.mjs', topic({name: 'acme-guide'}));
174
+ write(
175
+ 'docs/theme-notes.doc.mjs',
176
+ topic({name: 'theme-notes', extends: 'theme'}),
177
+ );
178
+ write('themes/ocean/oceanTheme.ts', 'export const oceanTheme = {};\n');
179
+ write(
180
+ 'themes/ocean/oceanTheme.doc.mjs',
181
+ "/** @type {import('@astryxdesign/cli/authoring').ThemeDoc} */\nexport default {type: 'theme', name: 'ocean', displayName: 'Ocean', description: 'Blue.', maintained: true};\n",
182
+ );
183
+
184
+ const project = await Project.load(tmpDir);
185
+ expect(await project.issues()).toEqual([]);
186
+ const {inputs, problems} = await collectDocInputs(project);
187
+ expect(problems).toEqual([]);
188
+ expect(
189
+ inputs
190
+ .filter(input => input.owner === '@acme/kit')
191
+ .map(({id, source, role}) => ({id, source, role})),
192
+ ).toEqual([
193
+ {
194
+ id: '@acme/kit:components:AcmeCard',
195
+ source: '@acme/kit/components/AcmeCard.doc.mjs',
196
+ role: undefined,
197
+ },
198
+ {
199
+ id: '@acme/kit:templates:block/hero',
200
+ source: '@acme/kit/templates/hero.doc.mjs',
201
+ role: undefined,
202
+ },
203
+ {
204
+ id: '@acme/kit:themes:ocean',
205
+ source: '@acme/kit/themes/ocean/oceanTheme.doc.mjs',
206
+ role: undefined,
207
+ },
208
+ {
209
+ id: '@acme/kit:docs:theme+theme-notes',
210
+ source: '@acme/kit/docs/theme-notes.doc.mjs',
211
+ role: 'extension',
212
+ },
213
+ {
214
+ id: '@acme/kit:docs:acme-guide',
215
+ source: '@acme/kit/docs/acme-guide.doc.mjs',
216
+ role: 'base',
217
+ },
218
+ ]);
219
+ });
220
+ });
221
+
222
+ describe('collectDocInputs problems', () => {
223
+ /** @param {object} overrides */
224
+ const project = overrides =>
225
+ /** @type {any} */ ({
226
+ cwd: os.tmpdir(),
227
+ components: async () => [],
228
+ templates: async () => [],
229
+ themes: async () => [],
230
+ docs: async () => ({entries: () => []}),
231
+ ...overrides,
232
+ });
233
+
234
+ it('reports a file two roots both claim, and reads it once', async () => {
235
+ const file = path.join(tmpDir, 'Shared.doc.mjs');
236
+ fs.writeFileSync(file, 'export default {};\n');
237
+ const {inputs, problems} = await collectDocInputs(
238
+ project({
239
+ components: async () => [
240
+ {name: 'Shared', package: '@acme/kit', docPath: file},
241
+ ],
242
+ templates: async () => [
243
+ {
244
+ type: 'block',
245
+ dirName: 'shared',
246
+ package: '@acme/kit',
247
+ docPath: file,
248
+ },
249
+ ],
250
+ }),
251
+ );
252
+ expect(
253
+ inputs.filter(input => input.file === fs.realpathSync(file)),
254
+ ).toHaveLength(1);
255
+ expect(problems).toEqual([
256
+ {
257
+ code: 'duplicate_file',
258
+ provider: '@acme/kit',
259
+ source: expect.stringMatching(/\/Shared\.doc\.mjs$/),
260
+ message: expect.stringContaining(
261
+ 'is read as templates "block/shared" and as components "Shared"',
262
+ ),
263
+ },
264
+ ]);
265
+ });
266
+
267
+ it('reports two files under one input id', async () => {
268
+ const one = path.join(tmpDir, 'a', 'Card.doc.mjs');
269
+ const two = path.join(tmpDir, 'b', 'Card.doc.mjs');
270
+ for (const file of [one, two]) {
271
+ fs.mkdirSync(path.dirname(file), {recursive: true});
272
+ fs.writeFileSync(file, 'export default {};\n');
273
+ }
274
+ const {problems} = await collectDocInputs(
275
+ project({
276
+ components: async () => [
277
+ {name: 'Card', package: '@acme/kit', docPath: one},
278
+ {name: 'Card', package: '@acme/kit', docPath: two},
279
+ ],
280
+ }),
281
+ );
282
+ expect(problems).toEqual([
283
+ {
284
+ code: 'duplicate_id',
285
+ provider: '@acme/kit',
286
+ source: expect.stringMatching(/\/Card\.doc\.mjs$/),
287
+ message: expect.stringContaining('both read as components "Card"'),
288
+ },
289
+ ]);
290
+ });
291
+ });
292
+
293
+ describe('packageSource', () => {
294
+ it('names the package a file ships in and its path inside it', () => {
295
+ expect(
296
+ packageSource(path.join(CLI_ROOT, 'api', 'docs', 'docs.doc.mjs')),
297
+ ).toBe('@astryxdesign/cli/api/docs/docs.doc.mjs');
298
+ });
299
+ });
@@ -7,3 +7,16 @@
7
7
  * @returns {import('./compile.mjs').CompiledReferenceNode}
8
8
  */
9
9
  export function parseCompiledReferenceNode(value: unknown): import("./compile.mjs").CompiledReferenceNode;
10
+ /**
11
+ * Validate a compiled node of any kind.
12
+ * @param {unknown} value
13
+ * @returns {import('./compile.mjs').CompiledDocNode | import('./compile.mjs').CompiledReferenceNode}
14
+ */
15
+ export function parseCompiledDocNode(value: unknown): import("./compile.mjs").CompiledDocNode | import("./compile.mjs").CompiledReferenceNode;
16
+ /**
17
+ * Validate a compiled docs bundle: every node, every diagnostic, and one node
18
+ * per id.
19
+ * @param {unknown} value
20
+ * @returns {import('./bundle.mjs').CompiledDocsBundle}
21
+ */
22
+ export function parseCompiledDocsBundle(value: unknown): import("./bundle.mjs").CompiledDocsBundle;
@@ -1,13 +1,14 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
3
  /**
4
- * @file The compiled-node contract and its sealed parser.
4
+ * @file The compiled-doc contract and its sealed parsers.
5
5
  *
6
- * @input Any value that claims to be a compiled reference node — typically one
7
- * read back from JSON.
6
+ * @input Any value that claims to be a compiled node or a compiled docs bundle
7
+ * — typically one read back from JSON.
8
8
  * @output The same value once it validates; a thrown Error naming the problems
9
- * otherwise. An unsupported schema version fails with its own message before
10
- * anything else is checked.
9
+ * otherwise, carrying a compiler diagnostic (`unsupported_schema` or
10
+ * `invalid_bundle`). An unsupported schema version fails with its own message
11
+ * before anything else is checked.
11
12
  * @position The load boundary for compiled nodes that did not come straight
12
13
  * from ./compile.mjs in this process. The value is returned as given, not
13
14
  * rebuilt, so key order (which response JSON follows) survives. A node is
@@ -15,7 +16,8 @@
15
16
  */
16
17
 
17
18
  import {SECTION_KEY_RE} from '../discovery/docs-section-key.mjs';
18
- import {COMPILED_DOC_SCHEMA_VERSION} from './compile.mjs';
19
+ import {COMPILED_DOC_KINDS, COMPILED_DOC_SCHEMA_VERSION} from './compile.mjs';
20
+ import {diagnostic, diagnosticProblem} from './diagnostics.mjs';
19
21
 
20
22
  const NODE_FIELDS = new Set([
21
23
  'schemaVersion',
@@ -65,15 +67,13 @@ const isPackageName = value =>
65
67
  */
66
68
  export function parseCompiledReferenceNode(value) {
67
69
  const node = /** @type {any} */ (value);
68
- if (node?.schemaVersion !== COMPILED_DOC_SCHEMA_VERSION) {
69
- throw new Error(
70
- `Compiled doc schema version ${JSON.stringify(node?.schemaVersion)} is not supported; this CLI reads version ${COMPILED_DOC_SCHEMA_VERSION}. Compile the docs again with this CLI.`,
71
- );
72
- }
70
+ const skew = schemaVersionProblem(node);
71
+ if (skew) throw compiledDocError('unsupported_schema', skew);
73
72
  const problems = jsonProblems(node, 'node');
74
73
  if (problems.length === 0) problems.push(...structureProblems(node));
75
74
  if (problems.length > 0) {
76
- throw new Error(
75
+ throw compiledDocError(
76
+ 'invalid_bundle',
77
77
  `Invalid compiled doc node: ${problems.slice(0, MAX_PROBLEMS).join('; ')}`,
78
78
  );
79
79
  }
@@ -285,3 +285,180 @@ function resolutionProblem(resolved) {
285
285
  return `status: expected resolved, unknown-topic or unknown-section, got ${JSON.stringify(resolved.status)}`;
286
286
  }
287
287
  }
288
+
289
+ const DOC_NODE_FIELDS = new Set([
290
+ 'schemaVersion',
291
+ 'kind',
292
+ 'stage',
293
+ 'id',
294
+ 'lang',
295
+ 'provenance',
296
+ 'doc',
297
+ ]);
298
+ const BUNDLE_FIELDS = new Set([
299
+ 'schemaVersion',
300
+ 'lang',
301
+ 'nodes',
302
+ 'diagnostics',
303
+ ]);
304
+
305
+ /**
306
+ * A package-relative source, never a location on one machine.
307
+ * @param {unknown} value
308
+ * @returns {boolean}
309
+ */
310
+ const isPackageSource = value =>
311
+ isPackageName(value) &&
312
+ !/(^|\/)\.\.(\/|$)/.test(/** @type {string} */ (value));
313
+
314
+ /**
315
+ * An Error for a value that is not a compiled doc, carrying the diagnostic a
316
+ * reader reports.
317
+ * @param {'unsupported_schema' | 'invalid_bundle'} code
318
+ * @param {string} message
319
+ * @returns {Error & {diagnostic: import('./diagnostics.mjs').CompilerDiagnostic}}
320
+ */
321
+ function compiledDocError(code, message) {
322
+ const error = /** @type {Error & {diagnostic: any}} */ (new Error(message));
323
+ error.diagnostic = diagnostic(code, {message});
324
+ return error;
325
+ }
326
+
327
+ /**
328
+ * @param {unknown} value
329
+ */
330
+ function schemaVersionProblem(value) {
331
+ const version = /** @type {any} */ (value)?.schemaVersion;
332
+ return version === COMPILED_DOC_SCHEMA_VERSION
333
+ ? null
334
+ : `Compiled doc schema version ${JSON.stringify(version)} is not supported; this CLI reads version ${COMPILED_DOC_SCHEMA_VERSION}. Compile the docs again with this CLI.`;
335
+ }
336
+
337
+ /**
338
+ * Validate a compiled node of any kind.
339
+ * @param {unknown} value
340
+ * @returns {import('./compile.mjs').CompiledDocNode | import('./compile.mjs').CompiledReferenceNode}
341
+ */
342
+ export function parseCompiledDocNode(value) {
343
+ const skew = schemaVersionProblem(value);
344
+ if (skew) throw compiledDocError('unsupported_schema', skew);
345
+ const problems = nodeProblems(value, 'node');
346
+ if (problems.length > 0) {
347
+ throw compiledDocError(
348
+ 'invalid_bundle',
349
+ `Invalid compiled doc node: ${problems.slice(0, MAX_PROBLEMS).join('; ')}`,
350
+ );
351
+ }
352
+ return /** @type {any} */ (value);
353
+ }
354
+
355
+ /**
356
+ * Validate a compiled docs bundle: every node, every diagnostic, and one node
357
+ * per id.
358
+ * @param {unknown} value
359
+ * @returns {import('./bundle.mjs').CompiledDocsBundle}
360
+ */
361
+ export function parseCompiledDocsBundle(value) {
362
+ const skew = schemaVersionProblem(value);
363
+ if (skew) throw compiledDocError('unsupported_schema', skew);
364
+ const bundle = /** @type {any} */ (value);
365
+ /** @type {string[]} */
366
+ const problems = jsonProblems(bundle, 'bundle');
367
+ if (problems.length === 0) {
368
+ const unknown = Object.keys(bundle).filter(key => !BUNDLE_FIELDS.has(key));
369
+ if (unknown.length > 0)
370
+ problems.push(`unknown fields: ${unknown.join(', ')}`);
371
+ if (bundle.lang !== null && !isText(bundle.lang)) {
372
+ problems.push('lang: expected a language or null');
373
+ }
374
+ if (!Array.isArray(bundle.nodes)) {
375
+ problems.push('nodes: expected an array');
376
+ } else {
377
+ const ids = new Set();
378
+ bundle.nodes.forEach(
379
+ (/** @type {unknown} */ node, /** @type {number} */ index) => {
380
+ for (const problem of nodeProblems(node, `nodes[${index}]`)) {
381
+ problems.push(problem);
382
+ }
383
+ const id = /** @type {any} */ (node)?.id;
384
+ if (ids.has(id))
385
+ problems.push(`nodes[${index}].id: "${id}" appears twice`);
386
+ ids.add(id);
387
+ },
388
+ );
389
+ }
390
+ if (!Array.isArray(bundle.diagnostics)) {
391
+ problems.push('diagnostics: expected an array');
392
+ } else {
393
+ bundle.diagnostics.forEach(
394
+ (/** @type {unknown} */ d, /** @type {number} */ index) => {
395
+ const problem = diagnosticProblem(d);
396
+ if (problem) problems.push(`diagnostics[${index}]: ${problem}`);
397
+ },
398
+ );
399
+ }
400
+ }
401
+ if (problems.length > 0) {
402
+ throw compiledDocError(
403
+ 'invalid_bundle',
404
+ `Invalid compiled docs bundle: ${problems.slice(0, MAX_PROBLEMS).join('; ')}`,
405
+ );
406
+ }
407
+ return bundle;
408
+ }
409
+
410
+ /**
411
+ * Problems with one node of any kind, each prefixed with where it is.
412
+ * @param {unknown} value
413
+ * @param {string} at
414
+ * @returns {string[]}
415
+ */
416
+ function nodeProblems(value, at) {
417
+ const node = /** @type {any} */ (value);
418
+ if (node?.schemaVersion !== COMPILED_DOC_SCHEMA_VERSION) {
419
+ return [`${at}.schemaVersion: expected ${COMPILED_DOC_SCHEMA_VERSION}`];
420
+ }
421
+ const json = jsonProblems(node, at);
422
+ if (json.length > 0) return json;
423
+ if (node.kind === 'reference') {
424
+ return structureProblems(node).map(problem => `${at}: ${problem}`);
425
+ }
426
+ /** @type {string[]} */
427
+ const problems = [];
428
+ const unknown = Object.keys(node).filter(key => !DOC_NODE_FIELDS.has(key));
429
+ if (unknown.length > 0)
430
+ problems.push(`${at}: unknown fields: ${unknown.join(', ')}`);
431
+ if (!COMPILED_DOC_KINDS.includes(node.kind)) {
432
+ problems.push(
433
+ `${at}.kind: ${JSON.stringify(node.kind)} is not a compiled doc kind`,
434
+ );
435
+ }
436
+ if (node.stage !== 'lowered')
437
+ problems.push(`${at}.stage: expected "lowered"`);
438
+ if (!isText(node.id)) problems.push(`${at}.id: expected an id`);
439
+ if (node.lang !== null && !isText(node.lang)) {
440
+ problems.push(`${at}.lang: expected a language or null`);
441
+ }
442
+ const provenance = node.provenance;
443
+ if (
444
+ !isRecord(provenance) ||
445
+ Object.keys(provenance).length !== 2 ||
446
+ !isPackageName(provenance.provider) ||
447
+ !isPackageSource(provenance.source)
448
+ ) {
449
+ problems.push(
450
+ `${at}.provenance: expected {provider, source} naming a package and a path inside it, not a machine path`,
451
+ );
452
+ }
453
+ if (!isRecord(node.doc) || !isText(node.doc.name)) {
454
+ problems.push(`${at}.doc: expected a doc with a name`);
455
+ } else if (
456
+ (node.kind === 'page' || node.kind === 'block' || node.kind === 'theme') &&
457
+ node.doc.type !== node.kind
458
+ ) {
459
+ problems.push(
460
+ `${at}.doc.type: a ${node.kind} node holds a ${node.kind} doc`,
461
+ );
462
+ }
463
+ return problems;
464
+ }