@astryxdesign/cli 0.7.2 → 0.7.3-canary.06872b4

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 (212) hide show
  1. package/README.md +99 -66
  2. package/api/build/_adapter.d.mts +58 -0
  3. package/api/build/_adapter.mjs +115 -3
  4. package/api/build/blocks/blocks.d.mts +42 -0
  5. package/api/build/blocks/blocks.mjs +169 -0
  6. package/api/build/blocks/blocks.test.mjs +108 -0
  7. package/api/build/blocks/blocks.weights.json +1 -0
  8. package/api/build/build.mjs +25 -6
  9. package/api/build/kit/answer.d.mts +14 -0
  10. package/api/build/kit/answer.mjs +85 -0
  11. package/api/build/kit/answer.test.mjs +38 -0
  12. package/api/build/kit/kind.d.mts +25 -0
  13. package/api/build/kit/kind.mjs +170 -0
  14. package/api/build/kit/kit.d.mts +2 -4
  15. package/api/build/kit/kit.mjs +8 -398
  16. package/api/build/kit/rank.d.mts +3 -32
  17. package/api/build/kit/rank.mjs +25 -342
  18. package/api/build/kit/rank.test.mjs +2 -1
  19. package/api/build/kit/signals.d.mts +60 -0
  20. package/api/build/kit/signals.mjs +284 -0
  21. package/api/build/kit/start.d.mts +32 -0
  22. package/api/build/kit/start.mjs +163 -0
  23. package/api/build/kit/words.d.mts +72 -0
  24. package/api/build/kit/words.mjs +202 -0
  25. package/api/build/tree.d.mts +20 -0
  26. package/api/build/tree.mjs +52 -0
  27. package/api/build/tree.test.mjs +177 -0
  28. package/api/build/tree.weights.json +1 -0
  29. package/api/component/component.mjs +16 -2
  30. package/api/component/component.test.mjs +15 -0
  31. package/api/discover/_adapter.mjs +36 -9
  32. package/api/discover/_adapter.test.mjs +42 -0
  33. package/api/docs/_adapter.mjs +17 -5
  34. package/api/docs/docs.test.mjs +7 -1
  35. package/api/doctor/doctor.mjs +86 -12
  36. package/api/doctor/doctor.test.mjs +28 -3
  37. package/api/init/init.doc.mjs +20 -6
  38. package/api/init/init.test.mjs +18 -2
  39. package/api/init/init.type.d.mts +3 -3
  40. package/api/init/init.type.mjs +3 -3
  41. package/api/init/remove/remove.mjs +17 -4
  42. package/api/init/remove/remove.test.mjs +114 -2
  43. package/api/init/run/run.mjs +69 -6
  44. package/api/integration/authoring-checks.mjs +83 -9
  45. package/api/integration/pack-check.mjs +123 -4
  46. package/api/integration/pack-check.test.mjs +87 -0
  47. package/api/integration/validate-integration-case-disk.test.mjs +111 -0
  48. package/api/integration/validate-integration-gaps.test.mjs +1964 -0
  49. package/api/integration/validate-integration.d.mts +11 -2
  50. package/api/integration/validate-integration.mjs +226 -32
  51. package/api/integration/validate-integration.type.d.mts +58 -1
  52. package/api/integration/validate-integration.type.mjs +45 -2
  53. package/api/integration/validateIntegration.doc.mjs +1 -1
  54. package/api/search/ORDERING.md +28 -0
  55. package/api/search/_adapter.d.mts +66 -2
  56. package/api/search/_adapter.mjs +118 -10
  57. package/api/search/_candidate-score.d.mts +40 -0
  58. package/api/search/_candidate-score.mjs +238 -0
  59. package/api/search/_cards.json +6098 -0
  60. package/api/search/_compound.d.mts +50 -0
  61. package/api/search/_compound.mjs +170 -0
  62. package/api/search/_doc-searcher.d.mts +4 -0
  63. package/api/search/_doc-searcher.mjs +23 -0
  64. package/api/search/_doc-searcher.test.mjs +36 -0
  65. package/api/search/_doc-searcher.weights.json +1 -0
  66. package/api/search/_doc-text.d.mts +30 -0
  67. package/api/search/_doc-text.mjs +87 -0
  68. package/api/search/_entries.d.mts +52 -0
  69. package/api/search/_entries.mjs +158 -0
  70. package/api/search/_features.d.mts +35 -0
  71. package/api/search/_features.mjs +155 -0
  72. package/api/search/_gather-components.d.mts +26 -0
  73. package/api/search/_gather-components.mjs +284 -0
  74. package/api/search/_gather-docs.d.mts +20 -0
  75. package/api/search/_gather-docs.mjs +254 -0
  76. package/api/search/_gather-hooks.d.mts +12 -0
  77. package/api/search/_gather-hooks.mjs +52 -0
  78. package/api/search/_gather-templates.d.mts +25 -0
  79. package/api/search/_gather-templates.mjs +107 -0
  80. package/api/search/_lexical.d.mts +135 -0
  81. package/api/search/_lexical.mjs +351 -0
  82. package/api/search/_main-searcher.d.mts +19 -0
  83. package/api/search/_main-searcher.mjs +115 -0
  84. package/api/search/_main-searcher.test.mjs +106 -0
  85. package/api/search/_main-searcher.weights.json +1 -0
  86. package/api/search/_module-doc.d.mts +32 -0
  87. package/api/search/_module-doc.mjs +78 -0
  88. package/api/search/_order.d.mts +32 -0
  89. package/api/search/_order.mjs +162 -0
  90. package/api/search/_order.test.mjs +72 -0
  91. package/api/search/_priority.d.mts +50 -0
  92. package/api/search/_priority.mjs +139 -0
  93. package/api/search/_query-words.d.mts +25 -0
  94. package/api/search/_query-words.mjs +92 -0
  95. package/api/search/_query.d.mts +65 -0
  96. package/api/search/_query.mjs +176 -0
  97. package/api/search/_result.d.mts +97 -0
  98. package/api/search/_result.mjs +150 -0
  99. package/api/search/_score.d.mts +60 -0
  100. package/api/search/_score.mjs +283 -0
  101. package/api/search/_synonyms.d.mts +21 -0
  102. package/api/search/_synonyms.mjs +67 -0
  103. package/api/search/_template-searcher.d.mts +4 -0
  104. package/api/search/_template-searcher.mjs +23 -0
  105. package/api/search/_template-searcher.test.mjs +40 -0
  106. package/api/search/_template-searcher.weights.json +1 -0
  107. package/api/search/_titles.d.mts +33 -0
  108. package/api/search/_titles.mjs +65 -0
  109. package/api/search/_words.d.mts +64 -0
  110. package/api/search/_words.mjs +157 -0
  111. package/api/search/search.d.mts +22 -305
  112. package/api/search/search.doc.mjs +7 -5
  113. package/api/search/search.mjs +53 -2483
  114. package/api/search/search.type.d.mts +6 -1
  115. package/api/search/search.type.mjs +6 -1
  116. package/api/search/tree.d.mts +15 -0
  117. package/api/search/tree.mjs +40 -0
  118. package/api/search/tree.test.mjs +66 -0
  119. package/api/search/tree.weights.json +1 -0
  120. package/api/theme/build/build.packed-old-core.test.mjs +1 -0
  121. package/api/upgrade/_adapter.d.mts +57 -9
  122. package/api/upgrade/_adapter.mjs +198 -28
  123. package/api/upgrade/registry/registry.d.mts +10 -4
  124. package/api/upgrade/registry/registry.mjs +33 -16
  125. package/api/upgrade/registry/registry.test.mjs +85 -0
  126. package/api/upgrade/run/run.d.mts +4 -2
  127. package/api/upgrade/run/run.mjs +13 -2
  128. package/api/upgrade/run/run.test.mjs +417 -3
  129. package/api/upgrade/upgrade.doc.mjs +10 -5
  130. package/api/upgrade/upgrade.mjs +13 -2
  131. package/api/upgrade/upgrade.test.mjs +194 -0
  132. package/api/upgrade/upgrade.type.d.mts +5 -3
  133. package/api/upgrade/upgrade.type.mjs +8 -6
  134. package/assets/codemods/transforms/v0.7.0/__tests__/validate-data-token-ownership.test.mjs +11 -0
  135. package/assets/codemods/transforms/v0.7.0/validate-data-token-ownership.mjs +3 -3
  136. package/assets/docs/is-my-code-good.doc.mjs +287 -0
  137. package/assets/docs/tree/api.doc.mjs +6 -0
  138. package/assets/docs/tree/host-entry.doc.mjs +128 -0
  139. package/assets/docs/tree/template-grading-rubric.doc.mjs +16 -37
  140. package/assets/docs/tree/tokens-data.doc.mjs +2 -2
  141. package/assets/docs/working-with-ai.doc.mjs +6 -2
  142. package/assets/templates/themes/butter/butterTheme.ts +4 -2
  143. package/assets/templates/themes/stone/stoneTheme.ts +4 -2
  144. package/assets/templates/themes/y2k/y2kTheme.ts +4 -2
  145. package/authoring/debug/parse.d.mts +1 -1
  146. package/clients/cli/bin/astryx.mjs +7 -78
  147. package/clients/cli/commands/doctor.mjs +30 -0
  148. package/clients/cli/commands/doctor.test.mjs +65 -0
  149. package/clients/cli/commands/init.behavior.test.mjs +230 -1
  150. package/clients/cli/commands/init.doc.mjs +28 -9
  151. package/clients/cli/commands/init.flag-help.test.mjs +1 -0
  152. package/clients/cli/commands/init.mjs +2 -2
  153. package/clients/cli/commands/outside-project.test.mjs +2 -1
  154. package/clients/cli/commands/setup-nudge.test.mjs +61 -0
  155. package/clients/cli/commands/upgrade.doc.mjs +4 -2
  156. package/clients/cli/commands/upgrade.mjs +9 -2
  157. package/clients/cli/commands/upgrade.subfolder-path.test.mjs +97 -0
  158. package/clients/cli/index.mjs +19 -1
  159. package/clients/cli/run.mjs +108 -0
  160. package/foundation/agent-docs/agent-doc-state.d.mts +20 -7
  161. package/foundation/agent-docs/agent-doc-state.mjs +35 -8
  162. package/foundation/agent-docs/agent-docs.d.mts +100 -12
  163. package/foundation/agent-docs/agent-docs.mjs +1010 -99
  164. package/foundation/agent-docs/agent-docs.path-safety.test.mjs +1239 -17
  165. package/foundation/agent-docs/agent-docs.test.mjs +678 -35
  166. package/foundation/config/project.mjs +20 -22
  167. package/foundation/config/project.test.mjs +56 -0
  168. package/foundation/discovery/component-discovery.d.mts +37 -0
  169. package/foundation/discovery/component-discovery.mjs +80 -5
  170. package/foundation/discovery/component-loader.d.mts +1 -1
  171. package/foundation/discovery/docs-discovery.mjs +14 -1
  172. package/foundation/discovery/module-exports.d.mts +41 -0
  173. package/foundation/discovery/module-exports.mjs +253 -0
  174. package/foundation/discovery/template-adapter.mjs +18 -1
  175. package/foundation/discovery/template-needs.mjs +1 -44
  176. package/foundation/discovery/theme-discovery.mjs +147 -3
  177. package/foundation/doc-compiler/doc-loads.test.mjs +8 -0
  178. package/foundation/doc-compiler/read.mjs +59 -1
  179. package/foundation/doc-compiler/read.test.mjs +11 -2
  180. package/foundation/fs/paths.d.mts +70 -8
  181. package/foundation/fs/paths.mjs +556 -38
  182. package/foundation/fs/paths.test.mjs +430 -54
  183. package/foundation/integrations/autolink.d.mts +2 -1
  184. package/foundation/integrations/autolink.mjs +4 -13
  185. package/foundation/integrations/computer-integrations.d.mts +6 -3
  186. package/foundation/integrations/computer-integrations.mjs +38 -9
  187. package/foundation/integrations/contribution-inventory.d.mts +15 -20
  188. package/foundation/integrations/contribution-inventory.mjs +41 -5
  189. package/foundation/integrations/integrations.d.mts +27 -1
  190. package/foundation/integrations/integrations.mjs +61 -2
  191. package/foundation/integrations/validate-contributions.d.mts +115 -0
  192. package/foundation/integrations/validate-contributions.mjs +1168 -13
  193. package/foundation/release/handoff.d.mts +23 -4
  194. package/foundation/release/handoff.mjs +175 -8
  195. package/foundation/release/run-context.mjs +14 -0
  196. package/foundation/response/response-types.doc.mjs +1 -1
  197. package/foundation/route/route.d.mts +285 -0
  198. package/foundation/route/route.mjs +324 -0
  199. package/foundation/route/route.test.mjs +153 -0
  200. package/foundation/runtime/hosted-runtime.test.mjs +403 -0
  201. package/foundation/runtime/locked-versions.d.mts +32 -0
  202. package/foundation/runtime/locked-versions.mjs +65 -0
  203. package/foundation/runtime/package-root.d.mts +91 -0
  204. package/foundation/runtime/package-root.mjs +174 -0
  205. package/foundation/runtime/package-set.d.mts +62 -0
  206. package/foundation/runtime/package-set.mjs +130 -0
  207. package/foundation/runtime/package-set.test.mjs +432 -0
  208. package/host/door-conformance.test.mjs +274 -0
  209. package/host/index.d.ts +116 -0
  210. package/host/index.mjs +295 -0
  211. package/host/index.test.mjs +302 -0
  212. package/package.json +14 -9
@@ -225,12 +225,19 @@ export function peerProblem(pkg, release) {
225
225
 
226
226
  /**
227
227
  * The computer-level integrations installed beside the CLI at `cliRoot`, in
228
- * name order (declaration order when the root declares them). Reads
228
+ * name order (declaration order when the root declares them), then each
229
+ * integration a host approves (`approved`, architecture:cli-surface INV30),
230
+ * resolved by name from the package set that holds that CLI alone. Reads
229
231
  * package.json files only.
230
- * @param {{cliRoot: string, release: string, cacheDir?: string}} options
232
+ * @param {{cliRoot: string, release: string, cacheDir?: string, approved?: readonly string[]}} options
231
233
  * @returns {ComputerIntegrationCandidate[]}
232
234
  */
233
- export function scanComputerIntegrations({cliRoot, release, cacheDir}) {
235
+ export function scanComputerIntegrations({
236
+ cliRoot,
237
+ release,
238
+ cacheDir,
239
+ approved = [],
240
+ }) {
234
241
  const self = real(cliRoot);
235
242
  /** @type {ComputerIntegrationCandidate[]} */
236
243
  const found = [];
@@ -238,21 +245,43 @@ export function scanComputerIntegrations({cliRoot, release, cacheDir}) {
238
245
  for (const root of computerInstallRoots(cliRoot, {cacheDir})) {
239
246
  scanRoot(root, {self, release, found, seen});
240
247
  }
248
+ const owner = approved.length > 0 ? installPlaceOf(self).owner : null;
249
+ if (owner) scanRoot(owner, {self, release, found, seen}, approved);
241
250
  return found;
242
251
  }
243
252
 
244
253
  /**
245
- * Add the integrations installed in one install root.
254
+ * `name` as installed in `root`'s own `node_modules`, never in a folder above.
255
+ * @param {string} name
256
+ * @param {string} root
257
+ * @returns {{packageDir: string, hostDir: string} | null}
258
+ */
259
+ function ownPackageDir(name, root) {
260
+ const packageDir = path.join(root, 'node_modules', ...name.split('/'));
261
+ return fs.existsSync(path.join(packageDir, 'package.json'))
262
+ ? {packageDir, hostDir: root}
263
+ : null;
264
+ }
265
+
266
+ /**
267
+ * Add the integrations installed in one install root: those it declares or
268
+ * installs, or, given `approved`, those names installed in its own
269
+ * `node_modules`.
246
270
  * @param {string} root
247
271
  * @param {{self: string, release: string, found: ComputerIntegrationCandidate[], seen: Set<string>}} into
272
+ * @param {readonly string[] | null} [approved]
248
273
  */
249
- function scanRoot(root, {self, release, found, seen}) {
250
- const declared = readPackage(root)
251
- ? readDeclaredDependencies(root).map(d => d.name)
252
- : installedNames(root);
274
+ function scanRoot(root, {self, release, found, seen}, approved = null) {
275
+ const declared =
276
+ approved ??
277
+ (readPackage(root)
278
+ ? readDeclaredDependencies(root).map(d => d.name)
279
+ : installedNames(root));
253
280
  for (const name of declared) {
254
281
  if (NOT_INTEGRATIONS.has(name)) continue;
255
- const resolved = resolveInstalledPackageDir(name, root);
282
+ const resolved = approved
283
+ ? ownPackageDir(name, root)
284
+ : resolveInstalledPackageDir(name, root);
256
285
  if (!resolved) continue;
257
286
  const identity = real(resolved.packageDir);
258
287
  if (identity === self || seen.has(identity)) continue;
@@ -1,6 +1,21 @@
1
1
  // @generated by scripts/sync-api-types.mjs from the JSDoc in foundation/**/*.mjs.
2
2
  // DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
3
3
 
4
+ /**
5
+ * The contribution files readers read under each declared root that is a
6
+ * readable folder, package-relative, without importing anything. A folder
7
+ * inside a root that cannot be listed is skipped.
8
+ * @param {{components?: string, templates?: string, codemods?: string, docs?: string, themes?: string}} loaded
9
+ * @param {string} pkgDir
10
+ * @returns {string[]}
11
+ */
12
+ export function contributionFiles(loaded: {
13
+ components?: string;
14
+ templates?: string;
15
+ codemods?: string;
16
+ docs?: string;
17
+ themes?: string;
18
+ }, pkgDir: string): string[];
4
19
  /**
5
20
  * @typedef {object} FileInventoryRoot
6
21
  * @property {'themes'|'components'|'templates'|'codemods'|'docs'} kind
@@ -22,26 +37,6 @@
22
37
  * @returns {FileInventory}
23
38
  */
24
39
  export function computeRequiredFiles(loaded: import("./integrations.mjs").LoadedIntegration): FileInventory;
25
- /**
26
- * @typedef {object} ContributionIdentities
27
- * @property {{slug: string, exportName: string}[]} themes
28
- * @property {string[]} components
29
- * @property {{id: string, type: string, name: string, replaces?: string}[]} templates
30
- * @property {{version: string, id: string}[]} codemods
31
- * @property {string[]} docs
32
- * @property {string[]} agentDocsAppend
33
- */
34
- /**
35
- * The resolved fields needed to discover contribution identities.
36
- * @typedef {object} IdentitySource
37
- * @property {string} name
38
- * @property {string} [themes]
39
- * @property {string} [components]
40
- * @property {string} [templates]
41
- * @property {string} [codemods]
42
- * @property {string} [docs]
43
- * @property {{append?: readonly string[]}} [agentDocs]
44
- */
45
40
  /**
46
41
  * Run the same discovery functions a consumer uses to collect every
47
42
  * contribution identity from a loaded integration.
@@ -137,6 +137,29 @@ const ROOT_KINDS = /** @type {const} */ ([
137
137
  'docs',
138
138
  ]);
139
139
 
140
+ /**
141
+ * The contribution files readers read under each declared root that is a
142
+ * readable folder, package-relative, without importing anything. A folder
143
+ * inside a root that cannot be listed is skipped.
144
+ * @param {{components?: string, templates?: string, codemods?: string, docs?: string, themes?: string}} loaded
145
+ * @param {string} pkgDir
146
+ * @returns {string[]}
147
+ */
148
+ export function contributionFiles(loaded, pkgDir) {
149
+ /** @type {Set<string>} */
150
+ const files = new Set();
151
+ for (const kind of ROOT_KINDS) {
152
+ const root = loaded[kind];
153
+ if (!root || !isFolder(root)) continue;
154
+ try {
155
+ for (const file of ENUMERATORS[kind](root, pkgDir)) files.add(file);
156
+ } catch {
157
+ // An unreadable folder inside the root.
158
+ }
159
+ }
160
+ return [...files].sort();
161
+ }
162
+
140
163
  // ── Public: static file inventory ────────────────────────────────
141
164
 
142
165
  /**
@@ -207,6 +230,19 @@ export function computeRequiredFiles(loaded) {
207
230
  * @property {{append?: readonly string[]}} [agentDocs]
208
231
  */
209
232
 
233
+ /**
234
+ * Whether `dir` is a folder to list. A declared root that is a file is
235
+ * reported by the root check; discovery finds nothing in it.
236
+ * @param {string} dir
237
+ */
238
+ function isFolder(dir) {
239
+ try {
240
+ return fs.statSync(dir).isDirectory();
241
+ } catch {
242
+ return false;
243
+ }
244
+ }
245
+
210
246
  /**
211
247
  * Run the same discovery functions a consumer uses to collect every
212
248
  * contribution identity from a loaded integration.
@@ -229,7 +265,7 @@ export async function collectIdentities(loaded) {
229
265
  /** @type {Array<{kind: string, message: string}>} */
230
266
  const errors = [];
231
267
 
232
- if (loaded.themes && fs.existsSync(loaded.themes)) {
268
+ if (loaded.themes && isFolder(loaded.themes)) {
233
269
  try {
234
270
  const themes = await discoverIntegrationThemes(
235
271
  /** @type {import('./integrations.mjs').LoadedIntegration} */ (loaded),
@@ -245,7 +281,7 @@ export async function collectIdentities(loaded) {
245
281
  }
246
282
  }
247
283
 
248
- if (loaded.components && fs.existsSync(loaded.components)) {
284
+ if (loaded.components && isFolder(loaded.components)) {
249
285
  try {
250
286
  const records = discoverIntegrationComponents(loaded) ?? [];
251
287
  // A family doc backs several records: load it, and report it, once.
@@ -275,7 +311,7 @@ export async function collectIdentities(loaded) {
275
311
  }
276
312
  }
277
313
 
278
- if (loaded.templates && fs.existsSync(loaded.templates)) {
314
+ if (loaded.templates && isFolder(loaded.templates)) {
279
315
  try {
280
316
  const {templates, errors: templateErrors} =
281
317
  await discoverIntegrationTemplatesForOne(loaded);
@@ -298,7 +334,7 @@ export async function collectIdentities(loaded) {
298
334
  }
299
335
  }
300
336
 
301
- if (loaded.codemods && fs.existsSync(loaded.codemods)) {
337
+ if (loaded.codemods && isFolder(loaded.codemods)) {
302
338
  try {
303
339
  const byVersion = await discoverIntegrationCodemods([loaded]);
304
340
  /** @type {{version: string, id: string}[]} */
@@ -317,7 +353,7 @@ export async function collectIdentities(loaded) {
317
353
  }
318
354
  }
319
355
 
320
- if (loaded.docs && fs.existsSync(loaded.docs)) {
356
+ if (loaded.docs && isFolder(loaded.docs)) {
321
357
  try {
322
358
  const {
323
359
  records,
@@ -45,13 +45,14 @@ export function findManifestPaths(dir: string): string[];
45
45
  * @param {string} file absolute manifest path
46
46
  * @param {string} [label] used in error messages
47
47
  * @param {{fresh?: boolean}} [options]
48
- * @returns {Promise<{manifest: import('../../authoring/integration/type').AstryxIntegration, unknownKeys: string[], debug?: import('../../authoring/debug/type').DebugEventHandler, gapReport?: import('../../authoring/gap-report/type').GapReportHandler, gapReportError?: string, discover?: import('../../authoring/discover/type').DiscoverSource, discoverError?: string, agentDocsError?: string, cliCommandError?: string}>}
48
+ * @returns {Promise<{manifest: import('../../authoring/integration/type').AstryxIntegration, unknownKeys: string[], declaredKeys: string[], debug?: import('../../authoring/debug/type').DebugEventHandler, gapReport?: import('../../authoring/gap-report/type').GapReportHandler, gapReportError?: string, discover?: import('../../authoring/discover/type').DiscoverSource, discoverError?: string, agentDocsError?: string, cliCommandError?: string}>}
49
49
  */
50
50
  export function loadManifest(file: string, label?: string, { fresh }?: {
51
51
  fresh?: boolean;
52
52
  }): Promise<{
53
53
  manifest: import("../../authoring/integration/type").AstryxIntegration;
54
54
  unknownKeys: string[];
55
+ declaredKeys: string[];
55
56
  debug?: import("../../authoring/debug/type").DebugEventHandler;
56
57
  gapReport?: import("../../authoring/gap-report/type").GapReportHandler;
57
58
  gapReportError?: string;
@@ -79,6 +80,17 @@ export function loadManifestObject(file: string, label?: string, options?: {
79
80
  * @param {string} [cwd]
80
81
  */
81
82
  export function resolvePackageDir(packageName: string, cwd?: string): string;
83
+ /**
84
+ * Where an integration named in astryx.config is installed: the nearest
85
+ * `node_modules/<name>` at or above `cwd`, found the way an autolinked
86
+ * dependency is, so a package a workspace hoists to its root loads in every
87
+ * member. When it is installed nowhere, `cwd`'s own `node_modules/<name>`,
88
+ * which the caller reports as not installed.
89
+ * @param {string} packageName
90
+ * @param {string} [cwd]
91
+ * @returns {string}
92
+ */
93
+ export function resolveInstalledIntegrationDir(packageName: string, cwd?: string): string;
82
94
  /**
83
95
  * Load the integration package rooted at `packageDir` directly, without first
84
96
  * installing or publishing it. This is the self-resolution seam: an author in
@@ -134,6 +146,8 @@ export function loadIntegrations(specs?: string[], { cwd, fresh, resolveProvider
134
146
  * @property {string} [__agentDocsError] contribution-specific validation
135
147
  * failure; other manifest contributions remain available
136
148
  * @property {string} __spec
149
+ * @property {string} [__declaredProviderId] the manifest's `providerId` as
150
+ * written, kept by validation so it can tell whether the package has one
137
151
  * @property {string} __packageDir
138
152
  * @property {Record<string, unknown>|null} [__packageExports] the owning
139
153
  * package's `exports` map, kept from the package.json this loader already
@@ -145,6 +159,8 @@ export function loadIntegrations(specs?: string[], { cwd, fresh, resolveProvider
145
159
  * set on a package whose provider ID an earlier-loaded package already
146
160
  * claims; it contributes nothing and is reported as a `duplicate_provider`
147
161
  * warning
162
+ * @property {string[]} [__declaredKeys] the keys the manifest object itself
163
+ * declares, before any is filled in (providerId from the package name)
148
164
  * @property {string[]} [__unknownKeys] manifest keys this CLI does not know —
149
165
  * surfaced as a warning; the rest of the manifest still contributes
150
166
  * @property {boolean} [__autolinked] loaded because the project declares the
@@ -210,6 +226,11 @@ export type LoadedIntegration = {
210
226
  */
211
227
  __agentDocsError?: string | undefined;
212
228
  __spec: string;
229
+ /**
230
+ * the manifest's `providerId` as
231
+ * written, kept by validation so it can tell whether the package has one
232
+ */
233
+ __declaredProviderId?: string | undefined;
213
234
  __packageDir: string;
214
235
  /**
215
236
  * the owning
@@ -233,6 +254,11 @@ export type LoadedIntegration = {
233
254
  claimedBy: string;
234
255
  message: string;
235
256
  } | undefined;
257
+ /**
258
+ * the keys the manifest object itself
259
+ * declares, before any is filled in (providerId from the package name)
260
+ */
261
+ __declaredKeys?: string[] | undefined;
236
262
  /**
237
263
  * manifest keys this CLI does not know —
238
264
  * surfaced as a warning; the rest of the manifest still contributes
@@ -19,6 +19,7 @@
19
19
  import * as fs from 'node:fs';
20
20
  import * as path from 'node:path';
21
21
  import {assertWithin} from '../fs/path-safety.mjs';
22
+ import {findNodeModulesPackage} from '../fs/paths.mjs';
22
23
  import {normalizeProviderId} from '../identity/provider-identity.mjs';
23
24
  // The key census and contribution parsers are internal to the schema module on
24
25
  // purpose: the public parser still validates the complete authored type, while
@@ -60,6 +61,8 @@ import {resolveProviders} from './provider-resolution.mjs';
60
61
  * @property {string} [__agentDocsError] contribution-specific validation
61
62
  * failure; other manifest contributions remain available
62
63
  * @property {string} __spec
64
+ * @property {string} [__declaredProviderId] the manifest's `providerId` as
65
+ * written, kept by validation so it can tell whether the package has one
63
66
  * @property {string} __packageDir
64
67
  * @property {Record<string, unknown>|null} [__packageExports] the owning
65
68
  * package's `exports` map, kept from the package.json this loader already
@@ -71,6 +74,8 @@ import {resolveProviders} from './provider-resolution.mjs';
71
74
  * set on a package whose provider ID an earlier-loaded package already
72
75
  * claims; it contributes nothing and is reported as a `duplicate_provider`
73
76
  * warning
77
+ * @property {string[]} [__declaredKeys] the keys the manifest object itself
78
+ * declares, before any is filled in (providerId from the package name)
74
79
  * @property {string[]} [__unknownKeys] manifest keys this CLI does not know —
75
80
  * surfaced as a warning; the rest of the manifest still contributes
76
81
  * @property {boolean} [__autolinked] loaded because the project declares the
@@ -213,7 +218,7 @@ function parseDiscoverSourceExport(value, label) {
213
218
  * @param {string} file absolute manifest path
214
219
  * @param {string} [label] used in error messages
215
220
  * @param {{fresh?: boolean}} [options]
216
- * @returns {Promise<{manifest: import('../../authoring/integration/type').AstryxIntegration, unknownKeys: string[], debug?: import('../../authoring/debug/type').DebugEventHandler, gapReport?: import('../../authoring/gap-report/type').GapReportHandler, gapReportError?: string, discover?: import('../../authoring/discover/type').DiscoverSource, discoverError?: string, agentDocsError?: string, cliCommandError?: string}>}
221
+ * @returns {Promise<{manifest: import('../../authoring/integration/type').AstryxIntegration, unknownKeys: string[], declaredKeys: string[], debug?: import('../../authoring/debug/type').DebugEventHandler, gapReport?: import('../../authoring/gap-report/type').GapReportHandler, gapReportError?: string, discover?: import('../../authoring/discover/type').DiscoverSource, discoverError?: string, agentDocsError?: string, cliCommandError?: string}>}
217
222
  */
218
223
  export async function loadManifest(
219
224
  file,
@@ -222,6 +227,7 @@ export async function loadManifest(
222
227
  ) {
223
228
  const mod = await importUserModule(file, {fresh});
224
229
  const raw = mod?.default;
230
+ if (raw === undefined) throw new Error(noDefaultExportMessage(file, label, mod));
225
231
  const baseManifest = parseIntegrationBase(raw, label);
226
232
  const gapReport = parseGapReportHandlerExport(mod?.gapReport, label);
227
233
  const discover = parseDiscoverSourceExport(mod?.discover, label);
@@ -274,6 +280,8 @@ export async function loadManifest(
274
280
  ...(cliCommand == null ? {} : {cliCommand}),
275
281
  },
276
282
  unknownKeys: unknownIntegrationKeys(raw),
283
+ declaredKeys:
284
+ raw != null && typeof raw === 'object' ? Object.keys(raw) : [],
277
285
  debug:
278
286
  typeof mod?.debug === 'function'
279
287
  ? /** @type {import('../../authoring/debug/type').DebugEventHandler} */ (
@@ -289,6 +297,30 @@ export async function loadManifest(
289
297
  };
290
298
  }
291
299
 
300
+ /**
301
+ * Why a manifest has no default export, said plainly instead of as a schema
302
+ * failure on `undefined`: the file is empty, or it exports other names.
303
+ * @param {string} file
304
+ * @param {string} label
305
+ * @param {Record<string, unknown> | undefined} mod
306
+ */
307
+ function noDefaultExportMessage(file, label, mod) {
308
+ const fix = `Fix: export the manifest object as the default export: \`export default {components: './components'}\`, naming the roots the package has.`;
309
+ let text = '';
310
+ try {
311
+ text = fs.readFileSync(file, 'utf8');
312
+ } catch {
313
+ // Unreadable now: say only what is missing.
314
+ }
315
+ const names = Object.keys(mod ?? {}).filter(name => name !== 'default');
316
+ if (text.trim() === '') return `${label} is empty. ${fix}`;
317
+ const found =
318
+ names.length > 0
319
+ ? ` (it exports ${names.map(name => `\`${name}\``).join(', ')})`
320
+ : '';
321
+ return `${label} has no default export${found}. ${fix}`;
322
+ }
323
+
292
324
  /**
293
325
  * Load and validate a manifest module's default export against the integration
294
326
  * schema. Throws if the default export is missing or invalid. Exposed for
@@ -337,6 +369,25 @@ export function resolvePackageDir(packageName, cwd = process.cwd()) {
337
369
  return dir;
338
370
  }
339
371
 
372
+ /**
373
+ * Where an integration named in astryx.config is installed: the nearest
374
+ * `node_modules/<name>` at or above `cwd`, found the way an autolinked
375
+ * dependency is, so a package a workspace hoists to its root loads in every
376
+ * member. When it is installed nowhere, `cwd`'s own `node_modules/<name>`,
377
+ * which the caller reports as not installed.
378
+ * @param {string} packageName
379
+ * @param {string} [cwd]
380
+ * @returns {string}
381
+ */
382
+ export function resolveInstalledIntegrationDir(
383
+ packageName,
384
+ cwd = process.cwd(),
385
+ ) {
386
+ // Validates the name first: it throws on a spec with path segments.
387
+ const ownDir = resolvePackageDir(packageName, cwd);
388
+ return findNodeModulesPackage(cwd, packageName)?.packageDir ?? ownDir;
389
+ }
390
+
340
391
  /**
341
392
  * Find the single conventional root manifest sibling of the package's
342
393
  * package.json. Multiple present is a hard error; missing is a hard error.
@@ -396,6 +447,8 @@ export async function loadLocalIntegration(packageDir, {fresh = false} = {}) {
396
447
  let manifest;
397
448
  /** @type {string[]} */
398
449
  let unknownKeys;
450
+ /** @type {string[]} */
451
+ let declaredKeys;
399
452
  /** @type {import('../../authoring/debug/type').DebugEventHandler | undefined} */
400
453
  let debugHandler;
401
454
  /** @type {import('../../authoring/gap-report/type').GapReportHandler | undefined} */
@@ -414,6 +467,7 @@ export async function loadLocalIntegration(packageDir, {fresh = false} = {}) {
414
467
  ({
415
468
  manifest,
416
469
  unknownKeys,
470
+ declaredKeys,
417
471
  debug: debugHandler,
418
472
  gapReport: gapReportHandler,
419
473
  gapReportError,
@@ -462,6 +516,7 @@ export async function loadLocalIntegration(packageDir, {fresh = false} = {}) {
462
516
  __cliCommandError: cliCommandError,
463
517
  __agentDocsError: agentDocsError,
464
518
  __unknownKeys: unknownKeys,
519
+ __declaredKeys: declaredKeys,
465
520
  __debug: debugHandler,
466
521
  __gapReport: gapReportHandler,
467
522
  __gapReportError: gapReportError,
@@ -496,7 +551,7 @@ export async function loadIntegrations(
496
551
  if (!spec || seenSpecs.has(spec)) continue;
497
552
  seenSpecs.add(spec);
498
553
 
499
- const packageDir = resolvePackageDir(spec, cwd);
554
+ const packageDir = resolveInstalledIntegrationDir(spec, cwd);
500
555
  const pkgPath = path.join(packageDir, 'package.json');
501
556
  let pkg;
502
557
  try {
@@ -512,6 +567,8 @@ export async function loadIntegrations(
512
567
  let manifest;
513
568
  /** @type {string[]} */
514
569
  let unknownKeys;
570
+ /** @type {string[]} */
571
+ let declaredKeys;
515
572
  /** @type {import('../../authoring/debug/type').DebugEventHandler | undefined} */
516
573
  let debugHandler;
517
574
  /** @type {import('../../authoring/gap-report/type').GapReportHandler | undefined} */
@@ -530,6 +587,7 @@ export async function loadIntegrations(
530
587
  ({
531
588
  manifest,
532
589
  unknownKeys,
590
+ declaredKeys,
533
591
  debug: debugHandler,
534
592
  gapReport: gapReportHandler,
535
593
  gapReportError,
@@ -585,6 +643,7 @@ export async function loadIntegrations(
585
643
  __cliCommandError: cliCommandError,
586
644
  __agentDocsError: agentDocsError,
587
645
  __unknownKeys: unknownKeys,
646
+ __declaredKeys: declaredKeys,
588
647
  __debug: debugHandler,
589
648
  __gapReport: gapReportHandler,
590
649
  __gapReportError: gapReportError,
@@ -5,6 +5,29 @@
5
5
  export function issueError(code: string, message: string): Issue;
6
6
  /** @param {string} code @param {string} message @returns {Issue} */
7
7
  export function issueWarning(code: string, message: string): Issue;
8
+ /**
9
+ * A layout finding added after the last minor release: validate lists it, in
10
+ * its `layout` report, but it is never an issue, so nothing counts it and
11
+ * `integration check --strict` does not fail on it. A new failing finding
12
+ * joins only in a minor release (architecture:cli-surface INV35).
13
+ * @param {string} code
14
+ * @param {string} message
15
+ * @returns {Issue}
16
+ */
17
+ export function issueListed(code: string, message: string): Issue;
18
+ /**
19
+ * Whether `issueListed` made this issue.
20
+ * @param {Issue} issue
21
+ */
22
+ export function isListedIssue(issue: Issue): boolean;
23
+ /**
24
+ * The known manifest key `key` is a near miss of: a different letter case, or
25
+ * at most two letters added, dropped or changed (`component`, `doc`,
26
+ * `tempaltes`). Null when no known key is that close, or two are.
27
+ * @param {string} key
28
+ * @returns {string | null}
29
+ */
30
+ export function nearestKnownKey(key: string): string | null;
8
31
  /**
9
32
  * Why a declared root cannot be read as a folder, or null when it can.
10
33
  * A missing root keeps its own issue; a root that is a file or that the
@@ -21,6 +44,79 @@ export function rootProblem(root: string): {
21
44
  kind: "unreadable";
22
45
  code: string;
23
46
  } | null;
47
+ /**
48
+ * The first folder on the way from the package to `root` (the root itself
49
+ * included) that is a symlink, with where it points, or null.
50
+ * @param {string} root absolute
51
+ * @param {string} packageDir absolute
52
+ * @returns {{path: string, target: string} | null}
53
+ */
54
+ export function symlinkedFolder(root: string, packageDir: string): {
55
+ path: string;
56
+ target: string;
57
+ } | null;
58
+ /**
59
+ * The block component names this check could not judge, shaped like the
60
+ * doc type report so `integration check` counts them as not compared: each
61
+ * name a template gives in `exampleFor` or `componentsUsed` that neither the
62
+ * package nor Core has, when the template reaches components through
63
+ * anything but named imports and re-exports from the package, Core, or
64
+ * helpers read in full (see importedNames), or when Core is not installed to
65
+ * look it up in; its findings are always empty. A name judged unknown is an
66
+ * `unknown_block_component` note, with its fix in the message: validate
67
+ * lists notes beside its issues, and nothing counts them, so `--strict` does
68
+ * not fail on it until the next minor release (architecture:cli-surface
69
+ * INV35).
70
+ * @param {LoadedIntegration} integration
71
+ * @returns {Promise<{report: {status: 'checked', findings: [], unchecked: Array<{file: string, name: string, reason: string}>}, notes: Note[]}>}
72
+ */
73
+ export function blockComponentsCheck(integration: LoadedIntegration): Promise<{
74
+ report: {
75
+ status: "checked";
76
+ findings: [];
77
+ unchecked: Array<{
78
+ file: string;
79
+ name: string;
80
+ reason: string;
81
+ }>;
82
+ };
83
+ notes: Note[];
84
+ }>;
85
+ /**
86
+ * The component doc names the check could not judge, shaped like the doc type
87
+ * report so `integration check` counts them as not compared: each doc whose
88
+ * `name` differs from the name it is listed under and whose source's exports
89
+ * cannot all be read without running it; its findings are always empty. A
90
+ * name the module its printed import names provably lacks is a
91
+ * `doc_name_mismatch` note, with its fix in the message: validate lists
92
+ * notes beside its issues, and nothing counts them, so `--strict` does not
93
+ * fail on it until the next minor release (architecture:cli-surface INV35).
94
+ * @param {LoadedIntegration} integration
95
+ * @returns {Promise<{report: {status: 'checked', findings: [], unchecked: Array<{file: string, name: string, reason: string}>}, notes: Note[]}>}
96
+ */
97
+ export function componentDocNamesCheck(integration: LoadedIntegration): Promise<{
98
+ report: {
99
+ status: "checked";
100
+ findings: [];
101
+ unchecked: Array<{
102
+ file: string;
103
+ name: string;
104
+ reason: string;
105
+ }>;
106
+ };
107
+ notes: Note[];
108
+ }>;
109
+ /**
110
+ * The `name` a theme source passes to defineTheme for its exported theme,
111
+ * when the file says it as a string literal: `export const oceanTheme =
112
+ * defineTheme({name: 'ocean', ...})`. Null whenever only running the module
113
+ * could tell (a variable, a factory, a spread after the name, `extends`
114
+ * alone), so nothing is judged that a build would decide.
115
+ * @param {string} file
116
+ * @param {string} exportName
117
+ * @returns {string | null}
118
+ */
119
+ export function literalThemeName(file: string, exportName: string): string | null;
24
120
  /**
25
121
  * Validate an already-LOADED integration (as produced by `loadIntegrations` —
26
122
  * absolute contribution roots plus identity) and return its issues. This is the
@@ -32,6 +128,17 @@ export function rootProblem(root: string): {
32
128
  * @returns {Promise<Issue[]>}
33
129
  */
34
130
  export function validateLoadedIntegration(loaded: LoadedIntegration): Promise<Issue[]>;
131
+ /**
132
+ * Every contribution check, split: `issues` as validateLoadedIntegration
133
+ * returns them, and `listed`, the layout findings validate lists in its
134
+ * `layout` report without counting them.
135
+ * @param {LoadedIntegration} loaded
136
+ * @returns {Promise<{issues: Issue[], listed: Issue[]}>}
137
+ */
138
+ export function validateLoadedIntegrationListed(loaded: LoadedIntegration): Promise<{
139
+ issues: Issue[];
140
+ listed: Issue[];
141
+ }>;
35
142
  /**
36
143
  * @typedef {import('./issue').AstryxIntegrationIssue} Issue
37
144
  * @typedef {import('./integrations.mjs').LoadedIntegration} LoadedIntegration
@@ -42,5 +149,13 @@ export const INVALID_AGENT_DOCS: "invalid_agent_docs";
42
149
  export const INVALID_CLI_COMMAND: "invalid_cli_command";
43
150
  /** Stable issue code for a package whose provider ID is already claimed. */
44
151
  export const DUPLICATE_PROVIDER: "duplicate_provider";
152
+ /**
153
+ * A note: information beside validate's issues that fails nothing, with the
154
+ * fix in its message.
155
+ */
156
+ export type Note = {
157
+ code: string;
158
+ message: string;
159
+ };
45
160
  export type Issue = import("./issue").AstryxIntegrationIssue;
46
161
  export type LoadedIntegration = import("./integrations.mjs").LoadedIntegration;