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

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 (236) hide show
  1. package/README.md +20 -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/docs.doc.mjs +1 -0
  9. package/api/docs/list/list.mjs +3 -7
  10. package/api/doctor/doctor.d.mts +25 -3
  11. package/api/doctor/doctor.doc.mjs +1 -0
  12. package/api/doctor/doctor.mjs +185 -13
  13. package/api/doctor/doctor.test.mjs +326 -1
  14. package/api/gap-report/gap-report.doc.mjs +1 -0
  15. package/api/hook/_adapter.mjs +19 -5
  16. package/api/hook/hook.doc.mjs +1 -0
  17. package/api/hook/list/list.d.mts +1 -1
  18. package/api/hook/list/list.mjs +69 -17
  19. package/api/init/init.doc.mjs +1 -0
  20. package/api/integration/add-contribution.mjs +7 -7
  21. package/api/integration/add-contribution.test.mjs +37 -3
  22. package/api/integration/add-theme.mjs +34 -64
  23. package/api/integration/add-theme.test.mjs +105 -21
  24. package/api/integration/authoring-checks.test.mjs +8 -4
  25. package/api/integration/integration-block-exports.test.mjs +10 -6
  26. package/api/integration/integrationAdd.doc.mjs +1 -0
  27. package/api/integration/integrationAddAgentDoc.doc.mjs +1 -0
  28. package/api/integration/integrationAddCodemod.doc.mjs +1 -0
  29. package/api/integration/integrationAddComponent.doc.mjs +1 -0
  30. package/api/integration/integrationAddDoc.doc.mjs +1 -0
  31. package/api/integration/integrationAddTemplate.doc.mjs +1 -0
  32. package/api/integration/integrationAddTheme.doc.mjs +6 -5
  33. package/api/integration/integrationComponentConflicts.doc.mjs +1 -0
  34. package/api/integration/integrationDocConflicts.doc.mjs +1 -0
  35. package/api/integration/integrationPackCheck.doc.mjs +1 -0
  36. package/api/integration/integrationTemplateConflicts.doc.mjs +1 -0
  37. package/api/integration/pack-check.test.mjs +17 -47
  38. package/api/integration/summarizeIssues.doc.mjs +1 -0
  39. package/api/integration/validate-integration-fixes.test.mjs +1389 -0
  40. package/api/integration/validate-integration.mjs +50 -102
  41. package/api/integration/validate-integration.test.mjs +85 -23
  42. package/api/integration/validate-unread-theme-folders.test.mjs +110 -0
  43. package/api/integration/validateIntegration.doc.mjs +1 -0
  44. package/api/json/assertResponse.doc.mjs +1 -0
  45. package/api/json/isError.doc.mjs +1 -0
  46. package/api/json/parseResponse.doc.mjs +1 -0
  47. package/api/layout/layoutCheck.doc.mjs +1 -0
  48. package/api/layout/layoutExpand.doc.mjs +1 -0
  49. package/api/layout/layoutGrammar.doc.mjs +1 -0
  50. package/api/search/search.doc.mjs +1 -0
  51. package/api/search/search.mjs +16 -6
  52. package/api/swizzle/swizzle.doc.mjs +1 -0
  53. package/api/template/template-suffix.test.mjs +41 -21
  54. package/api/template/template.doc.mjs +1 -0
  55. package/api/theme/_adapter.d.mts +2 -3
  56. package/api/theme/_adapter.mjs +4 -5
  57. package/api/theme/add/add.binary.test.mjs +10 -17
  58. package/api/theme/add/add.test.mjs +14 -1
  59. package/api/theme/generateTonalPalette.doc.mjs +1 -0
  60. package/api/theme/integration-themes.test.mjs +39 -28
  61. package/api/theme/list/list.test.mjs +19 -20
  62. package/api/theme/listThemes.doc.mjs +6 -5
  63. package/api/theme/themeAdd.doc.mjs +4 -3
  64. package/api/theme/themeBuild.doc.mjs +1 -0
  65. package/api/theme/themeList.doc.mjs +6 -3
  66. package/api/theme/themeListAvailable.doc.mjs +5 -3
  67. package/api/theme/themePaletteGenerate.doc.mjs +1 -0
  68. package/api/theme/themeTargets.doc.mjs +1 -0
  69. package/api/theme/themeTemplate.doc.mjs +1 -0
  70. package/api/upgrade/_adapter.d.mts +12 -3
  71. package/api/upgrade/_adapter.mjs +81 -66
  72. package/api/upgrade/provider-agreement.test.mjs +152 -0
  73. package/api/upgrade/run/run.mjs +1 -0
  74. package/api/upgrade/upgrade.doc.mjs +1 -0
  75. package/assets/codemods/integration-discovery.mjs +8 -2
  76. package/assets/codemods/integration-discovery.test.mjs +15 -0
  77. package/assets/codemods/runner.mjs +115 -5
  78. package/assets/codemods/transforms/next/__tests__/migrate-theme-catalog-to-descriptors.test.mjs +216 -0
  79. package/assets/codemods/transforms/next/index.mjs +11 -1
  80. package/assets/codemods/transforms/next/migrate-theme-catalog-to-descriptors.mjs +141 -0
  81. package/assets/docs/cli-integrations.doc.mjs +17 -30
  82. package/assets/docs/cli.doc.mjs +15 -0
  83. package/assets/docs/theme.doc.mjs +1 -1
  84. package/assets/templates/themes/butter/butterTheme.doc.mjs +11 -0
  85. package/assets/templates/themes/chocolate/chocolateTheme.doc.mjs +11 -0
  86. package/assets/templates/themes/gothic/gothicTheme.doc.mjs +11 -0
  87. package/assets/templates/themes/matcha/matchaTheme.doc.mjs +11 -0
  88. package/assets/templates/themes/neutral/neutralTheme.doc.mjs +11 -0
  89. package/assets/templates/themes/stone/stoneTheme.doc.mjs +11 -0
  90. package/assets/templates/themes/y2k/y2kTheme.doc.mjs +11 -0
  91. package/authoring/codemod/codemod.doc.mjs +1 -1
  92. package/authoring/config/config.doc.mjs +1 -1
  93. package/authoring/debug/debug.doc.d.mts +11 -0
  94. package/authoring/debug/debug.doc.mjs +182 -0
  95. package/authoring/doctypes/base/graph-fields.doc.mjs +1 -1
  96. package/authoring/doctypes/command/command.doc.mjs +1 -1
  97. package/authoring/doctypes/command/type.ts +1 -1
  98. package/authoring/doctypes/component/type.ts +2 -2
  99. package/authoring/doctypes/doctypes-new.test.mjs +48 -6
  100. package/authoring/doctypes/enum/enum.doc.mjs +1 -1
  101. package/authoring/doctypes/enum/type.ts +1 -1
  102. package/authoring/doctypes/function/function.doc.mjs +1 -1
  103. package/authoring/doctypes/function/type.ts +1 -1
  104. package/authoring/doctypes/hook/type.ts +2 -2
  105. package/authoring/doctypes/load-contract.test.mjs +2 -1
  106. package/authoring/doctypes/parse.d.mts +4 -2
  107. package/authoring/doctypes/parse.mjs +7 -3
  108. package/authoring/doctypes/reference/reference.doc.mjs +1 -1
  109. package/authoring/doctypes/reference/type.ts +2 -2
  110. package/authoring/doctypes/schema/schema.doc.mjs +1 -1
  111. package/authoring/doctypes/schema/type.ts +1 -2
  112. package/authoring/doctypes/template/template.doc.mjs +3 -3
  113. package/authoring/doctypes/theme/parse.d.mts +35 -0
  114. package/authoring/doctypes/theme/parse.mjs +76 -0
  115. package/authoring/doctypes/theme/theme.doc.d.mts +9 -0
  116. package/authoring/doctypes/theme/theme.doc.mjs +79 -0
  117. package/authoring/doctypes/theme/type.ts +42 -0
  118. package/authoring/doctypes/types.ts +2 -1
  119. package/authoring/gap-report/gap-report.doc.d.mts +12 -0
  120. package/authoring/gap-report/gap-report.doc.mjs +183 -0
  121. package/authoring/index.d.mts +1 -0
  122. package/authoring/index.d.ts +6 -4
  123. package/authoring/index.mjs +2 -1
  124. package/authoring/integration/integration.doc.mjs +2 -2
  125. package/authoring/integration/type.ts +3 -9
  126. package/clients/cli/__tests__/cliManifest.test.ts +27 -29
  127. package/clients/cli/commands/blog.doc.mjs +1 -1
  128. package/clients/cli/commands/build.doc.mjs +1 -1
  129. package/clients/cli/commands/component.doc.mjs +1 -1
  130. package/clients/cli/commands/discover.doc.mjs +1 -1
  131. package/clients/cli/commands/docs.doc.mjs +1 -1
  132. package/clients/cli/commands/doctor-integration-components.doc.mjs +1 -1
  133. package/clients/cli/commands/doctor-integration-docs.doc.mjs +1 -1
  134. package/clients/cli/commands/doctor-integration-templates.doc.mjs +1 -1
  135. package/clients/cli/commands/doctor-integration-validate.doc.mjs +1 -1
  136. package/clients/cli/commands/doctor-integration.doc.mjs +1 -1
  137. package/clients/cli/commands/doctor-integration.test.mjs +15 -7
  138. package/clients/cli/commands/doctor.doc.mjs +1 -1
  139. package/clients/cli/commands/gap-report.doc.mjs +1 -1
  140. package/clients/cli/commands/hook.doc.mjs +1 -1
  141. package/clients/cli/commands/init.doc.mjs +1 -1
  142. package/clients/cli/commands/integration-add.controls.test.mjs +1 -1
  143. package/clients/cli/commands/integration-add.doc.mjs +1 -1
  144. package/clients/cli/commands/integration-pack.doc.mjs +1 -1
  145. package/clients/cli/commands/integration-real-world.test.mjs +3 -9
  146. package/clients/cli/commands/integration.doc.mjs +1 -1
  147. package/clients/cli/commands/layout-check.doc.mjs +1 -1
  148. package/clients/cli/commands/layout-expand.doc.mjs +1 -1
  149. package/clients/cli/commands/layout-grammar.doc.mjs +1 -1
  150. package/clients/cli/commands/layout.doc.mjs +1 -1
  151. package/clients/cli/commands/manifest.doc.mjs +1 -1
  152. package/clients/cli/commands/search.doc.mjs +1 -1
  153. package/clients/cli/commands/swizzle.doc.mjs +1 -1
  154. package/clients/cli/commands/template.doc.mjs +1 -1
  155. package/clients/cli/commands/theme-add.doc.mjs +2 -2
  156. package/clients/cli/commands/theme-build.doc.mjs +1 -1
  157. package/clients/cli/commands/theme-list.doc.mjs +2 -2
  158. package/clients/cli/commands/theme-palette-generate.doc.mjs +3 -3
  159. package/clients/cli/commands/theme-palette.doc.mjs +1 -1
  160. package/clients/cli/commands/theme-targets.doc.mjs +1 -1
  161. package/clients/cli/commands/theme-template.doc.mjs +1 -1
  162. package/clients/cli/commands/theme.doc.mjs +1 -1
  163. package/clients/cli/commands/upgrade.doc.mjs +1 -1
  164. package/clients/cli/lib/hook-format.mjs +14 -5
  165. package/foundation/config/project-themes.test.mjs +11 -19
  166. package/foundation/config/project.d.mts +8 -0
  167. package/foundation/config/project.mjs +50 -21
  168. package/foundation/config/project.test.mjs +3 -16
  169. package/foundation/discovery/authoring-self-docs.d.mts +6 -0
  170. package/foundation/discovery/authoring-self-docs.mjs +17 -7
  171. package/foundation/discovery/authoring-surface.d.mts +74 -0
  172. package/foundation/discovery/authoring-surface.mjs +525 -0
  173. package/foundation/discovery/authoring-surface.test.mjs +392 -0
  174. package/foundation/discovery/cli-self-docs.d.mts +113 -0
  175. package/foundation/discovery/cli-self-docs.mjs +514 -0
  176. package/foundation/discovery/cli-self-docs.test.mjs +437 -0
  177. package/foundation/discovery/component-loader.d.mts +35 -38
  178. package/foundation/discovery/component-loader.mjs +53 -222
  179. package/foundation/discovery/docs-discovery.d.mts +3 -2
  180. package/foundation/discovery/docs-discovery.mjs +8 -12
  181. package/foundation/discovery/docs-discovery.test.mjs +8 -5
  182. package/foundation/discovery/template-adapter.d.mts +7 -0
  183. package/foundation/discovery/template-adapter.mjs +27 -40
  184. package/foundation/discovery/template-adapter.test.mjs +15 -0
  185. package/foundation/discovery/theme-discovery.d.mts +67 -7
  186. package/foundation/discovery/theme-discovery.mjs +916 -186
  187. package/foundation/discovery/theme-discovery.test.mjs +613 -219
  188. package/foundation/doc-compiler/bundle.d.mts +47 -0
  189. package/foundation/doc-compiler/bundle.mjs +218 -0
  190. package/foundation/doc-compiler/bundle.test.mjs +255 -0
  191. package/foundation/doc-compiler/compile.d.mts +200 -0
  192. package/foundation/doc-compiler/compile.mjs +254 -5
  193. package/foundation/doc-compiler/diagnostics.d.mts +126 -0
  194. package/foundation/doc-compiler/diagnostics.mjs +277 -0
  195. package/foundation/doc-compiler/doc-compiler.test.mjs +28 -1
  196. package/foundation/doc-compiler/doc-loads.test.mjs +1642 -0
  197. package/foundation/doc-compiler/import.d.mts +24 -0
  198. package/foundation/doc-compiler/import.mjs +59 -0
  199. package/foundation/doc-compiler/inputs.d.mts +102 -0
  200. package/foundation/doc-compiler/inputs.mjs +286 -0
  201. package/foundation/doc-compiler/inputs.test.mjs +299 -0
  202. package/foundation/doc-compiler/ir.d.mts +13 -0
  203. package/foundation/doc-compiler/ir.mjs +189 -12
  204. package/foundation/doc-compiler/lower-doc.test.mjs +492 -0
  205. package/foundation/doc-compiler/overlays.d.mts +37 -0
  206. package/foundation/doc-compiler/overlays.mjs +206 -0
  207. package/foundation/doc-compiler/parse-readable.d.mts +9 -0
  208. package/foundation/doc-compiler/parse-readable.mjs +29 -0
  209. package/foundation/doc-compiler/read.d.mts +126 -0
  210. package/foundation/doc-compiler/read.mjs +320 -0
  211. package/foundation/doc-compiler/read.test.mjs +313 -0
  212. package/foundation/doc-compiler/source.d.mts +33 -0
  213. package/foundation/doc-compiler/source.mjs +128 -0
  214. package/foundation/integrations/autolink.d.mts +58 -1
  215. package/foundation/integrations/autolink.mjs +143 -57
  216. package/foundation/integrations/contribution-fixes.d.mts +145 -0
  217. package/foundation/integrations/contribution-fixes.mjs +1284 -0
  218. package/foundation/integrations/contribution-inventory.d.mts +1 -1
  219. package/foundation/integrations/contribution-inventory.mjs +17 -20
  220. package/foundation/integrations/contribution-inventory.test.mjs +67 -27
  221. package/foundation/integrations/integrations.d.mts +3 -0
  222. package/foundation/integrations/integrations.mjs +11 -97
  223. package/foundation/integrations/provider-ledger.test.mjs +275 -0
  224. package/foundation/integrations/provider-resolution.d.mts +152 -0
  225. package/foundation/integrations/provider-resolution.mjs +576 -0
  226. package/foundation/integrations/provider-resolution.test.mjs +369 -0
  227. package/foundation/integrations/theme-descriptor.d.mts +8 -0
  228. package/foundation/integrations/theme-descriptor.mjs +44 -0
  229. package/foundation/integrations/validate-contributions.mjs +104 -10
  230. package/foundation/response/error-codes.doc.mjs +2 -2
  231. package/foundation/response/error-codes.mjs +1 -1
  232. package/foundation/response/response-types.doc.mjs +1 -1
  233. package/foundation/response/response.doc.mjs +1 -1
  234. package/foundation/text/string-utils.mjs +22 -10
  235. package/package.json +9 -9
  236. package/assets/templates/themes/manifest.json +0 -95
@@ -0,0 +1,47 @@
1
+ // @generated by scripts/sync-api-types.mjs from the JSDoc in foundation/**/*.mjs.
2
+ // DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
3
+
4
+ /**
5
+ * @typedef {import('./compile.mjs').CompiledDocNode} CompiledDocNode
6
+ * @typedef {import('./compile.mjs').CompiledReferenceNode} CompiledReferenceNode
7
+ * @typedef {import('./diagnostics.mjs').CompilerDiagnostic} CompilerDiagnostic
8
+ */
9
+ /**
10
+ * @typedef {object} CompiledDocsBundle
11
+ * @property {number} schemaVersion
12
+ * @property {string | null} lang
13
+ * @property {Array<CompiledDocNode | CompiledReferenceNode>} nodes in the order
14
+ * the project reads its roots
15
+ * @property {CompilerDiagnostic[]} diagnostics sorted
16
+ */
17
+ /**
18
+ * Compile every descriptor the project reads.
19
+ *
20
+ * A problem with one descriptor withdraws that node and is reported; it never
21
+ * stops the rest, so every node in the bundle reads back through the sealed
22
+ * parser. A token reference that finds nothing is a warning: readers
23
+ * print a placeholder for it, as they always have.
24
+ *
25
+ * @param {import('../config/project.mjs').Project} project
26
+ * @param {{lang?: string | null}} [options]
27
+ * @returns {Promise<CompiledDocsBundle>}
28
+ */
29
+ export function compileDocs(project: import("../config/project.mjs").Project, { lang }?: {
30
+ lang?: string | null;
31
+ }): Promise<CompiledDocsBundle>;
32
+ export type CompiledDocNode = import("./compile.mjs").CompiledDocNode;
33
+ export type CompiledReferenceNode = import("./compile.mjs").CompiledReferenceNode;
34
+ export type CompilerDiagnostic = import("./diagnostics.mjs").CompilerDiagnostic;
35
+ export type CompiledDocsBundle = {
36
+ schemaVersion: number;
37
+ lang: string | null;
38
+ /**
39
+ * in the order
40
+ * the project reads its roots
41
+ */
42
+ nodes: Array<CompiledDocNode | CompiledReferenceNode>;
43
+ /**
44
+ * sorted
45
+ */
46
+ diagnostics: CompilerDiagnostic[];
47
+ };
@@ -0,0 +1,218 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Compiled docs bundle — every descriptor a project reads, compiled at
5
+ * once.
6
+ *
7
+ * @input A loaded Project and a reading language.
8
+ * @output A plain-JSON bundle: the schema version, the language, one compiled
9
+ * node per descriptor (a doc topic with its extensions is one linked node),
10
+ * and every compiler diagnostic, in one deterministic order.
11
+ * @position The whole-project view of the compiler, for checks that need every
12
+ * node at once; today only tests call it. Single reads stay lazy:
13
+ * ./read.mjs and api/docs/_adapter.mjs compile only what a read touches.
14
+ * Internal to the CLI.
15
+ */
16
+
17
+ import {
18
+ readThemeDescriptorValue,
19
+ themeDescriptorLabel,
20
+ } from '../discovery/theme-discovery.mjs';
21
+ import {
22
+ COMPILED_DOC_SCHEMA_VERSION,
23
+ linkReferenceTopic,
24
+ lowerReferenceTopic,
25
+ } from './compile.mjs';
26
+ import {diagnostic as rawDiagnostic, sortDiagnostics} from './diagnostics.mjs';
27
+ import {collectDocInputs} from './inputs.mjs';
28
+ import {compileDocFile, loadTopicInput} from './read.mjs';
29
+ import {packageSource, scrubPaths} from './source.mjs';
30
+
31
+ /**
32
+ * A compiler diagnostic whose message names files by package, never by their
33
+ * location on this machine.
34
+ * @param {string} code
35
+ * @param {Parameters<typeof rawDiagnostic>[1]} at
36
+ */
37
+ function diagnostic(code, at) {
38
+ return rawDiagnostic(code, {
39
+ ...at,
40
+ message: scrubPaths(at.message) || '(the error had no message)',
41
+ });
42
+ }
43
+
44
+ /**
45
+ * @typedef {import('./compile.mjs').CompiledDocNode} CompiledDocNode
46
+ * @typedef {import('./compile.mjs').CompiledReferenceNode} CompiledReferenceNode
47
+ * @typedef {import('./diagnostics.mjs').CompilerDiagnostic} CompilerDiagnostic
48
+ */
49
+
50
+ /**
51
+ * @typedef {object} CompiledDocsBundle
52
+ * @property {number} schemaVersion
53
+ * @property {string | null} lang
54
+ * @property {Array<CompiledDocNode | CompiledReferenceNode>} nodes in the order
55
+ * the project reads its roots
56
+ * @property {CompilerDiagnostic[]} diagnostics sorted
57
+ */
58
+
59
+ /**
60
+ * Compile every descriptor the project reads.
61
+ *
62
+ * A problem with one descriptor withdraws that node and is reported; it never
63
+ * stops the rest, so every node in the bundle reads back through the sealed
64
+ * parser. A token reference that finds nothing is a warning: readers
65
+ * print a placeholder for it, as they always have.
66
+ *
67
+ * @param {import('../config/project.mjs').Project} project
68
+ * @param {{lang?: string | null}} [options]
69
+ * @returns {Promise<CompiledDocsBundle>}
70
+ */
71
+ export async function compileDocs(project, {lang = null} = {}) {
72
+ const {inputs, problems} = await collectDocInputs(project);
73
+ /** @type {CompilerDiagnostic[]} */
74
+ const diagnostics = problems.map(problem =>
75
+ diagnostic(problem.code, {
76
+ provider: problem.provider,
77
+ source: problem.source,
78
+ message: problem.message,
79
+ }),
80
+ );
81
+ const catalog = await project.docs();
82
+
83
+ /** @type {Map<string, CompiledReferenceNode>} lowered topics by name */
84
+ const topics = new Map();
85
+ for (const entry of catalog.entries()) {
86
+ const input = await loadTopicInput(entry, lang);
87
+ const failed = [input.base, ...input.extensions].filter(
88
+ file => 'error' in file || 'overlayError' in file,
89
+ );
90
+ if (failed.length > 0) {
91
+ for (const file of failed) {
92
+ const fromExtension = input.extensions.find(ext => ext === file);
93
+ const path = fromExtension
94
+ ? entry.extensions[input.extensions.indexOf(fromExtension)].path
95
+ : entry.path;
96
+ diagnostics.push(
97
+ diagnostic('error' in file ? 'load_failed' : 'overlay_failed', {
98
+ provider: fromExtension ? fromExtension.provider : entry.package,
99
+ source: packageSource(path),
100
+ message: `${file.file}: ${messageOf('error' in file ? file.error : file.overlayError)}`,
101
+ }),
102
+ );
103
+ }
104
+ continue;
105
+ }
106
+ try {
107
+ topics.set(entry.name.toLowerCase(), lowerReferenceTopic(input));
108
+ } catch (error) {
109
+ diagnostics.push(
110
+ diagnostic(
111
+ errorCode(error) === 'not_json' ? 'not_json' : 'invalid_topic',
112
+ {
113
+ provider: entry.package,
114
+ source: packageSource(entry.path),
115
+ message: messageOf(error),
116
+ },
117
+ ),
118
+ );
119
+ }
120
+ }
121
+ /** @param {string} name */
122
+ const lowerTarget = async name => {
123
+ const target = catalog.resolve(name);
124
+ return target ? (topics.get(target.name.toLowerCase()) ?? null) : null;
125
+ };
126
+
127
+ /** @type {Array<CompiledDocNode | CompiledReferenceNode>} */
128
+ const nodes = [];
129
+ for (const input of inputs) {
130
+ if (input.root === 'docs') {
131
+ if (input.role !== 'base') continue;
132
+ const lowered = topics.get(input.name.toLowerCase());
133
+ if (!lowered) continue;
134
+ const linked = await linkReferenceTopic(lowered, lowerTarget);
135
+ diagnostics.push(...unresolvedReferences(linked, input));
136
+ nodes.push(linked);
137
+ continue;
138
+ }
139
+ const result = await compileDocFile(
140
+ input.file,
141
+ {
142
+ root: input.root,
143
+ provider: input.owner,
144
+ id: input.id,
145
+ lang:
146
+ input.root === 'components' || input.root === 'hooks' ? lang : null,
147
+ check: true,
148
+ ...(input.root === 'templates' ? {loader: 'template'} : {}),
149
+ ...(input.root === 'themes'
150
+ ? {
151
+ label: themeDescriptorLabel(input.file, input.owner),
152
+ readStatic: () =>
153
+ readThemeDescriptorValue(
154
+ input.file,
155
+ themeDescriptorLabel(input.file, input.owner),
156
+ ),
157
+ }
158
+ : {}),
159
+ },
160
+ {node: true},
161
+ );
162
+ diagnostics.push(...result.diagnostics);
163
+ // A descriptor with a problem of its own withdraws its node.
164
+ if (result.node && !result.diagnostics.some(d => d.severity === 'error')) {
165
+ nodes.push(result.node);
166
+ }
167
+ }
168
+
169
+ return {
170
+ schemaVersion: COMPILED_DOC_SCHEMA_VERSION,
171
+ lang,
172
+ nodes,
173
+ diagnostics: sortDiagnostics(diagnostics),
174
+ };
175
+ }
176
+
177
+ /**
178
+ * A warning for each token reference in a linked topic that finds no topic or
179
+ * no section.
180
+ * @param {CompiledReferenceNode} node
181
+ * @param {import('./inputs.mjs').DocInput} input
182
+ * @returns {CompilerDiagnostic[]}
183
+ */
184
+ function unresolvedReferences(node, input) {
185
+ /** @type {CompilerDiagnostic[]} */
186
+ const found = [];
187
+ for (const section of node.doc.sections) {
188
+ for (const block of section.content) {
189
+ if (block?.type !== 'token-ref') continue;
190
+ const status = block.resolved?.status;
191
+ if (status === 'resolved') continue;
192
+ found.push(
193
+ diagnostic('unresolved_reference', {
194
+ provider: input.owner,
195
+ source: input.source,
196
+ field: `sections.${section.id}`,
197
+ message:
198
+ status === 'unknown-topic'
199
+ ? `"${node.id}" section "${section.id}" refers to topic "${block.topic}", which this project does not have.`
200
+ : `"${node.id}" section "${section.id}" refers to section "${block.section}" of "${block.topic}", which that topic does not have.`,
201
+ }),
202
+ );
203
+ }
204
+ }
205
+ return found;
206
+ }
207
+
208
+ /** @param {unknown} error */
209
+ function messageOf(error) {
210
+ return error instanceof Error ? error.message : String(error);
211
+ }
212
+
213
+ /** @param {unknown} error */
214
+ function errorCode(error) {
215
+ return error && typeof error === 'object' && 'compilerCode' in error
216
+ ? error.compilerCode
217
+ : undefined;
218
+ }
@@ -0,0 +1,255 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file compileDocs: every descriptor a project reads compiles to one node or
5
+ * a diagnostic, and the bundle reads back through its sealed parser.
6
+ */
7
+
8
+ import {afterEach, beforeEach, describe, expect, it} from 'vitest';
9
+ import * as fs from 'node:fs';
10
+ import * as path from 'node:path';
11
+ import {Project} from '../config/project.mjs';
12
+ import {CLI_ROOT} from '../fs/paths.mjs';
13
+ import {compileDocs} from './bundle.mjs';
14
+ import {COMPILED_DOC_KINDS} from './compile.mjs';
15
+ import {collectDocInputs} from './inputs.mjs';
16
+ import {parseCompiledDocsBundle} from './ir.mjs';
17
+
18
+ const REPO_ROOT = path.resolve(CLI_ROOT, '..', '..');
19
+
20
+ let tmpDir;
21
+
22
+ beforeEach(() => {
23
+ // Inside the repo, so the project finds this checkout's Core.
24
+ tmpDir = fs.mkdtempSync(path.join(REPO_ROOT, '.astryx-doc-bundle-'));
25
+ fs.writeFileSync(
26
+ path.join(tmpDir, 'package.json'),
27
+ JSON.stringify({name: 'consumer', version: '1.0.0'}),
28
+ );
29
+ });
30
+
31
+ afterEach(() => {
32
+ fs.rmSync(tmpDir, {recursive: true, force: true});
33
+ });
34
+
35
+ describe('compileDocs over what this repo ships', () => {
36
+ it('compiles every descriptor, in every language, with nothing to report', async () => {
37
+ const project = await Project.load(tmpDir);
38
+ const {inputs} = await collectDocInputs(project);
39
+ // One node per input, except that a topic and its extensions are one node.
40
+ const expected = inputs.filter(
41
+ input => input.root !== 'docs' || input.role === 'base',
42
+ ).length;
43
+ for (const lang of [null, 'zh', 'dense']) {
44
+ const bundle = await compileDocs(project, {lang});
45
+ expect(bundle.diagnostics).toEqual([]);
46
+ expect(bundle.nodes).toHaveLength(expected);
47
+ expect(new Set(bundle.nodes.map(node => node.kind))).toEqual(
48
+ new Set(COMPILED_DOC_KINDS),
49
+ );
50
+ // Plain JSON that reads back through the sealed parser, as written.
51
+ const copy = JSON.parse(JSON.stringify(bundle));
52
+ expect(parseCompiledDocsBundle(copy)).toEqual(bundle);
53
+ }
54
+ }, 180_000);
55
+
56
+ it('is deterministic', async () => {
57
+ const one = await compileDocs(await Project.load(tmpDir));
58
+ const two = await compileDocs(await Project.load(tmpDir));
59
+ expect(JSON.stringify(two)).toBe(JSON.stringify(one));
60
+ }, 120_000);
61
+
62
+ it('never carries a machine path', async () => {
63
+ const bundle = await compileDocs(await Project.load(tmpDir));
64
+ const text = JSON.stringify(bundle);
65
+ expect(text.includes(REPO_ROOT)).toBe(false);
66
+ expect(text.includes(tmpDir)).toBe(false);
67
+ }, 120_000);
68
+ });
69
+
70
+ describe('compileDocs over a broken integration', () => {
71
+ /**
72
+ * @param {string} rel
73
+ * @param {string} text
74
+ */
75
+ function write(rel, text) {
76
+ const file = path.join(tmpDir, 'node_modules', '@acme', 'kit', rel);
77
+ fs.mkdirSync(path.dirname(file), {recursive: true});
78
+ fs.writeFileSync(file, text);
79
+ }
80
+
81
+ /** @param {object} fields */
82
+ const topic = fields =>
83
+ `export default ${JSON.stringify({
84
+ type: 'generic',
85
+ title: 'Acme',
86
+ description: 'Acme notes.',
87
+ sections: [{title: 'Acme', content: [{type: 'prose', text: 'Hi.'}]}],
88
+ ...fields,
89
+ })};\n`;
90
+
91
+ beforeEach(() => {
92
+ fs.writeFileSync(
93
+ path.join(tmpDir, 'package.json'),
94
+ JSON.stringify({
95
+ name: 'consumer',
96
+ version: '1.0.0',
97
+ dependencies: {'@acme/kit': '^1.0.0'},
98
+ }),
99
+ );
100
+ write(
101
+ 'package.json',
102
+ JSON.stringify({name: '@acme/kit', version: '1.0.0'}),
103
+ );
104
+ write(
105
+ 'astryx.integration.mjs',
106
+ "export default {components: './components', docs: './docs'};\n",
107
+ );
108
+ write(
109
+ 'components/AcmeCard.tsx',
110
+ 'export function AcmeCard() { return null; }\n',
111
+ );
112
+ write(
113
+ 'components/AcmeCard.doc.mjs',
114
+ "export default {type: 'component', name: 'AcmeCard', displayName: 'Acme Card', usage: {description: 'A card.'}, props: []};\n",
115
+ );
116
+ write('docs/acme-guide.doc.mjs', topic({name: 'acme-guide'}));
117
+ });
118
+
119
+ it('warns on a token reference that finds nothing, and still compiles the topic', async () => {
120
+ write(
121
+ 'docs/acme-guide.doc.mjs',
122
+ topic({
123
+ name: 'acme-guide',
124
+ sections: [
125
+ {
126
+ title: 'Colors',
127
+ content: [
128
+ {type: 'token-ref', topic: 'no-such-topic', section: 'x'},
129
+ {type: 'token-ref', topic: 'tokens', section: 'no-such-section'},
130
+ ],
131
+ },
132
+ ],
133
+ }),
134
+ );
135
+ const bundle = await compileDocs(await Project.load(tmpDir));
136
+ const warnings = bundle.diagnostics.filter(d => d.provider === '@acme/kit');
137
+ expect(warnings.map(d => [d.code, d.severity, d.field])).toEqual([
138
+ ['unresolved_reference', 'warning', 'sections.colors'],
139
+ ['unresolved_reference', 'warning', 'sections.colors'],
140
+ ]);
141
+ expect(
142
+ warnings.every(d => d.source === '@acme/kit/docs/acme-guide.doc.mjs'),
143
+ ).toBe(true);
144
+ expect(bundle.nodes.some(node => node.id === 'acme-guide')).toBe(true);
145
+ });
146
+
147
+ it('reports a component doc that fails to load, and compiles the rest', async () => {
148
+ write(
149
+ 'components/AcmeCard.doc.mjs',
150
+ "throw new Error('boom');\nexport default {};\n",
151
+ );
152
+ const project = await Project.load(tmpDir);
153
+ const bundle = await compileDocs(project);
154
+ const found = bundle.diagnostics.filter(d => d.provider === '@acme/kit');
155
+ // Discovery lists the doc; compiling it fails at load, and only it.
156
+ expect(found.map(d => [d.code, d.source])).toEqual([
157
+ ['load_failed', '@acme/kit/components/AcmeCard.doc.mjs'],
158
+ ]);
159
+ expect(found[0].message).toMatch(/boom/);
160
+ expect(bundle.nodes.some(node => node.kind === 'reference')).toBe(true);
161
+ });
162
+
163
+ it('withdraws a descriptor that fails its parser, and the bundle still reads back', async () => {
164
+ write(
165
+ 'components/AcmeCard.doc.mjs',
166
+ "export default {type: 'component', displayName: 'No name'};\n",
167
+ );
168
+ const bundle = await compileDocs(await Project.load(tmpDir));
169
+ const found = bundle.diagnostics.filter(d => d.provider === '@acme/kit');
170
+ expect(found.map(d => d.code)).toEqual(['invalid_doc']);
171
+ expect(
172
+ bundle.nodes.some(node => node.id === '@acme/kit:components:AcmeCard'),
173
+ ).toBe(false);
174
+ expect(() =>
175
+ parseCompiledDocsBundle(JSON.parse(JSON.stringify(bundle))),
176
+ ).not.toThrow();
177
+ });
178
+
179
+ it('reports a topic value JSON cannot hold with the same code as any other doc', async () => {
180
+ write(
181
+ 'docs/acme-guide.doc.mjs',
182
+ topic({name: 'acme-guide'}).replace('};', ', big: 1n};'),
183
+ );
184
+ const bundle = await compileDocs(await Project.load(tmpDir));
185
+ const found = bundle.diagnostics.filter(d => d.provider === '@acme/kit');
186
+ expect(found.map(d => d.code)).toContain('not_json');
187
+ expect(found.map(d => d.code)).not.toContain('invalid_topic');
188
+ // The compiler's code never reaches the error a reader throws.
189
+ const {lowerTopic} = await import('../../api/docs/_adapter.mjs');
190
+ const catalog = await (await Project.load(tmpDir)).docs();
191
+ const entry = /** @type {any} */ (catalog.resolve('acme-guide'));
192
+ const thrown = await lowerTopic(catalog, entry).catch(error => error);
193
+ expect(thrown).toBeInstanceOf(Error);
194
+ expect(/** @type {any} */ (thrown).code).toBeUndefined();
195
+ });
196
+
197
+ it('gives every extension file its own input, even two from one package', async () => {
198
+ write('docs/theme-a.doc.mjs', topic({name: 'theme-a', extends: 'theme'}));
199
+ write('docs/theme-b.doc.mjs', topic({name: 'theme-b', extends: 'theme'}));
200
+ const project = await Project.load(tmpDir);
201
+ const {problems} = await collectDocInputs(project);
202
+ expect(problems.filter(p => p.code === 'duplicate_id')).toEqual([]);
203
+ });
204
+
205
+ it('names a package that has no name by its folder, never by its path', async () => {
206
+ fs.writeFileSync(
207
+ path.join(tmpDir, 'package.json'),
208
+ JSON.stringify({version: '1.0.0'}),
209
+ );
210
+ fs.writeFileSync(
211
+ path.join(tmpDir, 'astryx.integration.mjs'),
212
+ "export default {components: './components'};\n",
213
+ );
214
+ fs.mkdirSync(path.join(tmpDir, 'components', 'Local'), {recursive: true});
215
+ fs.writeFileSync(
216
+ path.join(tmpDir, 'components', 'Local', 'Local.tsx'),
217
+ 'export {};\n',
218
+ );
219
+ fs.writeFileSync(
220
+ path.join(tmpDir, 'components', 'Local', 'Local.doc.mjs'),
221
+ "export default {type: 'component', name: 'Local', displayName: 'Local', usage: {description: 'd'}, props: []};\n",
222
+ );
223
+ const bundle = await compileDocs(await Project.load(tmpDir));
224
+ const text = JSON.stringify(bundle);
225
+ expect(text.includes(tmpDir)).toBe(false);
226
+ expect(() => parseCompiledDocsBundle(JSON.parse(text))).not.toThrow();
227
+ });
228
+
229
+ it('reports an extension that fails to load against the extension, not its base', async () => {
230
+ write('docs/theme-notes.doc.mjs', "throw new Error('bad extension');\n");
231
+ // Discovery refuses an unloadable topic file, so the catalog never merges
232
+ // it; compile the entry as the catalog would have if it had loaded.
233
+ const project = await Project.load(tmpDir);
234
+ const catalog = await project.docs();
235
+ const theme = /** @type {any} */ (catalog.resolve('theme'));
236
+ theme.extensions.push({
237
+ package: '@acme/kit',
238
+ path: path.join(
239
+ tmpDir,
240
+ 'node_modules',
241
+ '@acme',
242
+ 'kit',
243
+ 'docs',
244
+ 'theme-notes.doc.mjs',
245
+ ),
246
+ });
247
+ const bundle = await compileDocs(project);
248
+ const found = bundle.diagnostics.filter(d => d.code === 'load_failed');
249
+ expect(found.map(d => [d.provider, d.source])).toEqual([
250
+ ['@acme/kit', '@acme/kit/docs/theme-notes.doc.mjs'],
251
+ ]);
252
+ // The broken extension withdraws the topic it extends, as a docs read does.
253
+ expect(bundle.nodes.some(node => node.id === 'theme')).toBe(false);
254
+ });
255
+ });