@astryxdesign/cli 0.6.4 → 0.6.5-canary.031021b

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 (288) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +97 -90
  3. package/api/build/build.doc.mjs +6 -1
  4. package/api/build/build.test.mjs +22 -0
  5. package/api/build/kit/kit.mjs +44 -5
  6. package/api/component/_adapter.d.mts +25 -0
  7. package/api/component/_adapter.mjs +59 -5
  8. package/api/component/component.d.mts +6 -3
  9. package/api/component/component.doc.mjs +37 -17
  10. package/api/component/component.mjs +249 -9
  11. package/api/component/component.type.d.mts +25 -0
  12. package/api/component/component.type.mjs +44 -0
  13. package/api/discover/_adapter.d.mts +114 -6
  14. package/api/discover/_adapter.mjs +372 -17
  15. package/api/discover/_adapter.test.mjs +215 -0
  16. package/api/discover/_catalog-view.d.mts +115 -0
  17. package/api/discover/_catalog-view.mjs +203 -0
  18. package/api/discover/_catalog-view.test.mjs +128 -0
  19. package/api/discover/detail/detail.d.mts +18 -6
  20. package/api/discover/detail/detail.mjs +67 -13
  21. package/api/discover/detail/detail.test.mjs +85 -0
  22. package/api/discover/detail/item/item.d.mts +26 -0
  23. package/api/discover/detail/item/item.mjs +78 -0
  24. package/api/discover/detail/item/item.test.mjs +73 -0
  25. package/api/discover/discover.d.mts +3 -9
  26. package/api/discover/discover.doc.mjs +61 -18
  27. package/api/discover/discover.mjs +220 -36
  28. package/api/discover/discover.test.mjs +11 -2
  29. package/api/discover/discover.type.d.mts +147 -8
  30. package/api/discover/discover.type.mjs +102 -12
  31. package/api/discover/list/list.d.mts +20 -6
  32. package/api/discover/list/list.mjs +45 -12
  33. package/api/discover/list/list.test.mjs +46 -0
  34. package/api/discover/search/search.d.mts +18 -16
  35. package/api/discover/search/search.mjs +102 -56
  36. package/api/discover/search/search.test.mjs +144 -10
  37. package/api/docs/_adapter.d.mts +8 -3
  38. package/api/docs/_adapter.mjs +14 -6
  39. package/api/docs/docOverlays.test.mjs +27 -1
  40. package/api/docs/docs.doc.mjs +2 -2
  41. package/api/doctor/doctor.d.mts +8 -3
  42. package/api/doctor/doctor.doc.mjs +17 -8
  43. package/api/doctor/doctor.mjs +90 -9
  44. package/api/doctor/doctor.test.mjs +122 -10
  45. package/api/doctor/doctor.type.d.mts +1 -1
  46. package/api/doctor/doctor.type.mjs +1 -1
  47. package/api/gap-report/gap-report.doc.mjs +19 -10
  48. package/api/hook/hook.doc.mjs +6 -3
  49. package/api/index.d.mts +1 -0
  50. package/api/index.mjs +5 -3
  51. package/api/init/init.doc.mjs +17 -12
  52. package/api/integration/add-helpers.d.mts +5 -2
  53. package/api/integration/add-helpers.mjs +36 -9
  54. package/api/integration/add-theme.mjs +22 -1
  55. package/api/integration/add-theme.test.mjs +34 -0
  56. package/api/integration/authoring-checks.mjs +2 -2
  57. package/api/integration/integrationPackCheck.doc.mjs +3 -3
  58. package/api/integration/pack-check.lifecycle-output.test.mjs +107 -0
  59. package/api/integration/pack-check.mjs +82 -9
  60. package/api/integration/pack-check.test.mjs +90 -0
  61. package/api/integration/pack-check.type.mjs +1 -1
  62. package/api/json/assertResponse.doc.mjs +1 -1
  63. package/api/json/isError.doc.mjs +1 -1
  64. package/api/search/search.d.mts +27 -1
  65. package/api/search/search.doc.mjs +2 -2
  66. package/api/search/search.mjs +228 -16
  67. package/api/swizzle/swizzle.doc.mjs +7 -5
  68. package/api/template/copy/copy.mjs +1 -1
  69. package/api/template/copy/copy.test.mjs +9 -0
  70. package/api/template/template.doc.mjs +2 -1
  71. package/api/theme/add/add.mjs +17 -25
  72. package/api/theme/add/add.rollback.test.mjs +158 -0
  73. package/api/theme/add/add.staging.test.mjs +40 -23
  74. package/api/theme/build/build.family.test.mjs +7 -12
  75. package/api/theme/build/build.mjs +8 -18
  76. package/api/theme/build/build.rollback.test.mjs +148 -0
  77. package/api/theme/generateTonalPalette.doc.mjs +1 -2
  78. package/api/theme/listThemes.doc.mjs +1 -1
  79. package/api/theme/themeAdd.doc.mjs +9 -10
  80. package/api/theme/themeBuild.doc.mjs +13 -13
  81. package/api/theme/themeList.doc.mjs +1 -1
  82. package/api/theme/themeListAvailable.doc.mjs +2 -1
  83. package/api/theme/themePaletteGenerate.doc.mjs +15 -8
  84. package/api/theme/themeTargets.doc.mjs +3 -2
  85. package/api/theme/themeTemplate.doc.mjs +2 -1
  86. package/api/upgrade/run/files-changed.test.mjs +111 -0
  87. package/api/upgrade/run/run.mjs +5 -3
  88. package/api/upgrade/upgrade.doc.mjs +24 -22
  89. package/api/upgrade/upgrade.type.mjs +2 -2
  90. package/assets/codemods/__tests__/runner.test.mjs +3 -1
  91. package/assets/codemods/file-count.test.mjs +163 -0
  92. package/assets/codemods/integration-runner.mjs +3 -3
  93. package/assets/codemods/runner.mjs +5 -4
  94. package/assets/docs/README.md +4 -2
  95. package/assets/docs/browser-support.doc.mjs +11 -11
  96. package/assets/docs/color.doc.mjs +8 -2
  97. package/assets/docs/elevation.doc.mjs +6 -4
  98. package/assets/docs/getting-started.doc.mjs +5 -16
  99. package/assets/docs/icons.doc.mjs +2 -21
  100. package/assets/docs/illustrations.doc.mjs +7 -15
  101. package/assets/docs/internationalization.doc.mjs +7 -5
  102. package/assets/docs/layout.doc.dense.mjs +130 -82
  103. package/assets/docs/layout.doc.mjs +133 -77
  104. package/assets/docs/migration.doc.mjs +19 -21
  105. package/assets/docs/motion.doc.mjs +16 -3
  106. package/assets/docs/principles.doc.dense.mjs +5 -5
  107. package/assets/docs/principles.doc.mjs +8 -0
  108. package/assets/docs/principles.doc.zh.mjs +6 -6
  109. package/assets/docs/shape.doc.mjs +8 -3
  110. package/assets/docs/spacing.doc.mjs +7 -2
  111. package/assets/docs/styling-libraries.doc.mjs +6 -2
  112. package/assets/docs/styling.doc.mjs +19 -23
  113. package/assets/docs/theme.doc.dense.mjs +58 -18
  114. package/assets/docs/theme.doc.mjs +57 -47
  115. package/assets/docs/theme.doc.zh.mjs +9 -8
  116. package/assets/docs/tokens.doc.dense.mjs +2 -2
  117. package/assets/docs/tokens.doc.mjs +389 -8
  118. package/assets/docs/tokens.doc.zh.mjs +2 -2
  119. package/assets/docs/tree/add-a-component.doc.mjs +75 -0
  120. package/assets/docs/tree/add-a-theme.doc.mjs +85 -0
  121. package/assets/docs/tree/add-a-topic.doc.mjs +144 -0
  122. package/assets/docs/tree/agent-guidance.doc.mjs +138 -0
  123. package/assets/docs/tree/block-template.doc.mjs +130 -0
  124. package/assets/docs/tree/build-the-template.doc.mjs +28 -0
  125. package/assets/docs/tree/building-blocks.doc.mjs +46 -0
  126. package/assets/docs/tree/check-your-docs.doc.mjs +137 -0
  127. package/assets/docs/tree/checks.doc.mjs +119 -0
  128. package/assets/docs/tree/codemods.doc.mjs +147 -0
  129. package/assets/docs/tree/component-family.doc.mjs +113 -0
  130. package/assets/docs/tree/component-imports.doc.mjs +69 -0
  131. package/assets/docs/tree/component-lookups.doc.mjs +149 -0
  132. package/assets/docs/tree/components.doc.mjs +23 -0
  133. package/assets/docs/tree/configuration.doc.mjs +23 -0
  134. package/assets/docs/tree/debug-and-gap-reports.doc.mjs +182 -0
  135. package/assets/docs/tree/define-the-theme.doc.mjs +118 -0
  136. package/assets/docs/tree/describe-the-component.doc.mjs +57 -0
  137. package/assets/docs/tree/docs.doc.mjs +21 -0
  138. package/assets/docs/tree/document-the-template.doc.mjs +28 -0
  139. package/assets/docs/tree/document-the-theme.doc.mjs +68 -0
  140. package/assets/docs/tree/export-template-assets.doc.mjs +147 -0
  141. package/assets/docs/tree/extend-or-replace.doc.mjs +103 -0
  142. package/assets/docs/tree/fonts-and-assets.doc.mjs +106 -0
  143. package/assets/docs/tree/generate-a-palette.doc.mjs +66 -0
  144. package/assets/docs/tree/grade-template-with-agent.doc.mjs +105 -0
  145. package/assets/docs/tree/help.doc.mjs +16 -0
  146. package/assets/docs/tree/integrations.doc.mjs +25 -451
  147. package/assets/docs/tree/links.doc.mjs +98 -0
  148. package/assets/docs/tree/package-and-test.doc.mjs +32 -0
  149. package/assets/docs/tree/page-template.doc.mjs +71 -0
  150. package/assets/docs/tree/publishing.doc.mjs +111 -0
  151. package/assets/docs/tree/quick-start.doc.mjs +272 -0
  152. package/assets/docs/tree/replace-a-core-component.doc.mjs +104 -0
  153. package/assets/docs/tree/replace-a-core-template.doc.mjs +172 -0
  154. package/assets/docs/tree/sections-and-placement.doc.mjs +108 -0
  155. package/assets/docs/tree/see-it-in-an-app.doc.mjs +59 -0
  156. package/assets/docs/tree/ship.doc.mjs +16 -0
  157. package/assets/docs/tree/short-and-findable.doc.mjs +108 -0
  158. package/assets/docs/tree/single-component.doc.mjs +165 -0
  159. package/assets/docs/tree/start-a-template.doc.mjs +143 -0
  160. package/assets/docs/tree/subcomponent.doc.mjs +115 -0
  161. package/assets/docs/tree/template-assets.doc.mjs +64 -0
  162. package/assets/docs/tree/template-doc-overview.doc.mjs +109 -0
  163. package/assets/docs/tree/template-fonts.doc.mjs +102 -0
  164. package/assets/docs/tree/template-grading-rubric.doc.mjs +452 -0
  165. package/assets/docs/tree/template-icons.doc.mjs +97 -0
  166. package/assets/docs/tree/template-images-media.doc.mjs +127 -0
  167. package/assets/docs/tree/template-styles.doc.mjs +93 -0
  168. package/assets/docs/tree/templates.doc.mjs +34 -0
  169. package/assets/docs/tree/test-in-an-app.doc.mjs +115 -0
  170. package/assets/docs/tree/test-template-in-app.doc.mjs +128 -0
  171. package/assets/docs/tree/themes.doc.mjs +39 -0
  172. package/assets/docs/tree/troubleshooting.doc.mjs +149 -0
  173. package/assets/docs/tree/upgrading.doc.mjs +103 -0
  174. package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +51 -0
  175. package/assets/docs/tree/verify-packed-template.doc.mjs +77 -0
  176. package/assets/docs/tree/versioning.doc.mjs +161 -0
  177. package/assets/docs/tree/write-good-templates.doc.mjs +64 -0
  178. package/assets/docs/tree/write-the-template-file.doc.mjs +154 -0
  179. package/assets/docs/typography.doc.mjs +24 -4
  180. package/assets/docs/working-with-ai.doc.mjs +30 -22
  181. package/assets/templates/blocks/components/InternationalizationProvider/InternationalizationProvider01ShippedLocale.tsx +1 -1
  182. package/authoring/config/config.doc.mjs +9 -1
  183. package/authoring/config/parse.d.mts +2 -0
  184. package/authoring/config/parse.mjs +19 -0
  185. package/authoring/config/parse.test.mjs +8 -0
  186. package/authoring/config/type.ts +11 -0
  187. package/authoring/discover/discover.doc.d.mts +13 -0
  188. package/authoring/discover/discover.doc.mjs +138 -0
  189. package/authoring/discover/parse.d.mts +24 -0
  190. package/authoring/discover/parse.mjs +128 -0
  191. package/authoring/discover/parse.test.mjs +124 -0
  192. package/authoring/discover/type.ts +87 -0
  193. package/authoring/doctypes/_schema.d.mts +3 -2
  194. package/authoring/doctypes/_schema.mjs +6 -0
  195. package/authoring/doctypes/base/graph-fields.doc.mjs +3 -3
  196. package/authoring/doctypes/base/type.ts +4 -2
  197. package/authoring/doctypes/component/component.doc.mjs +6 -0
  198. package/authoring/doctypes/component/type.ts +8 -0
  199. package/authoring/doctypes/reference/reference.doc.mjs +7 -0
  200. package/authoring/doctypes/reference/type.ts +5 -0
  201. package/authoring/doctypes/schema/schema.doc.mjs +2 -2
  202. package/authoring/doctypes/template/template.doc.mjs +1 -1
  203. package/authoring/doctypes/template/type.ts +2 -2
  204. package/authoring/index.d.mts +1 -0
  205. package/authoring/index.d.ts +10 -0
  206. package/authoring/index.mjs +1 -0
  207. package/authoring/integration/integration.doc.mjs +12 -10
  208. package/clients/cli/commands/component/index.mjs +152 -55
  209. package/clients/cli/commands/component-batch.test.mjs +341 -0
  210. package/clients/cli/commands/component-ownership.test.mjs +89 -0
  211. package/clients/cli/commands/component.doc.mjs +27 -9
  212. package/clients/cli/commands/discover.doc.mjs +53 -9
  213. package/clients/cli/commands/discover.mjs +393 -118
  214. package/clients/cli/commands/discover.sources.test.mjs +267 -0
  215. package/clients/cli/commands/docs.doc.mjs +1 -1
  216. package/clients/cli/commands/docs.mjs +60 -17
  217. package/clients/cli/commands/doctor-integration-docs.doc.mjs +3 -2
  218. package/clients/cli/commands/doctor-integration.test.mjs +53 -0
  219. package/clients/cli/commands/doctor.doc.mjs +3 -1
  220. package/clients/cli/commands/doctor.mjs +49 -5
  221. package/clients/cli/commands/gap-report.doc.mjs +10 -9
  222. package/clients/cli/commands/init.doc.mjs +9 -6
  223. package/clients/cli/commands/integration-add.doc.mjs +9 -9
  224. package/clients/cli/commands/integration-authoring.test.mjs +61 -10
  225. package/clients/cli/commands/integration-pack.doc.mjs +5 -9
  226. package/clients/cli/commands/integration-real-world.test.mjs +1 -1
  227. package/clients/cli/commands/integration-verify.doc.mjs +22 -0
  228. package/clients/cli/commands/integration.doc.mjs +4 -4
  229. package/clients/cli/commands/integration.mjs +74 -43
  230. package/clients/cli/commands/manifest.doc.mjs +1 -1
  231. package/clients/cli/commands/search.doc.mjs +10 -3
  232. package/clients/cli/commands/search.mjs +21 -2
  233. package/clients/cli/commands/search.test.mjs +21 -4
  234. package/clients/cli/commands/swizzle.doc.mjs +1 -1
  235. package/clients/cli/commands/template.doc.mjs +1 -1
  236. package/clients/cli/commands/text-json-parity.test.mjs +7 -1
  237. package/clients/cli/commands/theme-add.doc.mjs +1 -1
  238. package/clients/cli/commands/theme-palette-generate.doc.mjs +3 -2
  239. package/clients/cli/commands/theme-palette.doc.mjs +1 -2
  240. package/clients/cli/commands/theme-targets.doc.mjs +2 -2
  241. package/clients/cli/commands/theme.doc.mjs +2 -1
  242. package/clients/cli/commands/upgrade.doc.mjs +62 -3
  243. package/clients/cli/index.mjs +28 -6
  244. package/clients/cli/lib/define-command.mjs +28 -4
  245. package/clients/cli/lib/define-command.test.mjs +54 -0
  246. package/clients/cli/lib/exit-codes.test.mjs +17 -1
  247. package/clients/cli/lib/json-shim.mjs +24 -14
  248. package/clients/cli/lib/manifest.mjs +18 -5
  249. package/clients/cli/lib/manifest.test.mjs +5 -2
  250. package/clients/cli/lib/parse-error-format.test.mjs +81 -0
  251. package/foundation/agent-docs/agent-docs.mjs +1 -1
  252. package/foundation/discovery/authoring-self-docs.mjs +1 -0
  253. package/foundation/discovery/authoring-self-docs.test.mjs +6 -2
  254. package/foundation/discovery/cli-self-docs.mjs +16 -2
  255. package/foundation/discovery/cli-self-docs.test.mjs +20 -0
  256. package/foundation/discovery/docs-discovery.mjs +5 -1
  257. package/foundation/discovery/docs-discovery.test.mjs +21 -0
  258. package/foundation/discovery/docs-section-key.d.mts +1 -1
  259. package/foundation/discovery/docs-section-key.mjs +1 -1
  260. package/foundation/doc-compiler/doc-loads.test.mjs +3 -2
  261. package/foundation/doc-compiler/inputs.test.mjs +0 -1
  262. package/foundation/doc-compiler/tree.d.mts +4 -0
  263. package/foundation/doc-compiler/tree.mjs +6 -1
  264. package/foundation/integrations/cli-requirement.d.mts +26 -6
  265. package/foundation/integrations/cli-requirement.mjs +46 -11
  266. package/foundation/integrations/cli-requirement.test.mjs +7 -2
  267. package/foundation/integrations/contribution-inventory.mjs +1 -1
  268. package/foundation/integrations/integrations.d.mts +14 -1
  269. package/foundation/integrations/integrations.mjs +41 -1
  270. package/foundation/integrations/integrations.test.mjs +31 -0
  271. package/foundation/response/batch.type.d.mts +33 -0
  272. package/foundation/response/batch.type.mjs +34 -0
  273. package/foundation/response/error-codes.doc.mjs +6 -8
  274. package/foundation/response/error-codes.test.mjs +30 -5
  275. package/foundation/response/response-types.doc.d.mts +4 -3
  276. package/foundation/response/response-types.doc.mjs +40 -10
  277. package/foundation/response/response-types.doc.test.mjs +23 -0
  278. package/foundation/response/response.doc.mjs +11 -10
  279. package/package.json +9 -9
  280. package/api/docs/docs.test.mjs +0 -243
  281. package/api/docs/integration-tree.test.mjs +0 -555
  282. package/api/docs/integrationDocs.test.mjs +0 -314
  283. package/api/search/search.test.mjs +0 -512
  284. package/assets/docs/tree/integrations.test.mjs +0 -62
  285. package/assets/docs/tree/writing-docs.doc.mjs +0 -286
  286. package/clients/cli/commands/docs.test.mjs +0 -294
  287. package/foundation/agent-docs/agent-docs.test.mjs +0 -1159
  288. package/foundation/doc-compiler/tree.test.mjs +0 -598
@@ -1,44 +1,76 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
3
  /**
4
- * @file discover.search leaf — free-text search across external packages.
4
+ * @file discover.search leaf — free-text search across installed packages and,
5
+ * when the project has discover sources, everything it could add.
5
6
  *
6
- * Resolution order (matching the flat command exactly):
7
- * 1. exact component name -> discover.detail.doc
8
- * 2. single substring hit -> discover.detail.doc
9
- * 3. multiple substring hits -> discover.search
10
- * 4. fuzzy hits (distance <= 3) -> throw ERR_NOT_FOUND with suggestions
11
- * 5. otherwise -> throw ERR_NOT_FOUND
7
+ * A free-text query always answers with a list, so its response type depends
8
+ * on the form of the query and never on what the project has:
9
+ * 1. any matches -> discover.search
10
+ * 2. fuzzy hits (distance <= 3) -> throw ERR_NOT_FOUND with suggestions
11
+ * 3. otherwise -> throw ERR_NOT_FOUND
12
+ * Matches are installed components, the project's other installed items,
13
+ * packages, and every item a source lists for a package the project could add.
14
+ * A caller opens one item by its package path, which ../detail answers.
12
15
  *
13
- * @position api/discover/search — projection over ../_adapter's resolver;
14
- * delegates the single-component cases to ../detail/doc.
16
+ * @position api/discover/search — pure ranking over the installed component
17
+ * names and ../_catalog-view's search items.
15
18
  */
16
19
 
17
- import {findComponent} from '../_adapter.mjs';
18
- import {docFromResult} from '../detail/doc/doc.mjs';
19
20
  import {levenshteinDistance} from '../../../foundation/text/string-utils.mjs';
20
21
  import {AstryxError} from '../../error.mjs';
21
22
  import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
23
+ import {DISCOVER_KINDS} from '../../../authoring/discover/parse.mjs';
22
24
 
23
25
  /**
24
26
  * @typedef {import('../_package-scanner.mjs').ScannedPackage} ScannedPackage
27
+ * @typedef {import('../_catalog-view.mjs').SearchItem} SearchItem
25
28
  */
26
29
 
30
+ const KIND_ORDER = ['package', ...DISCOVER_KINDS];
31
+
32
+ /**
33
+ * How well an item matches: 0 exact name, 1 name prefix, 2 name substring,
34
+ * 3 title, summary, keyword, or description; null for no match.
35
+ * @param {SearchItem} item
36
+ * @param {string} lower
37
+ * @returns {number | null}
38
+ */
39
+ function rank(item, lower) {
40
+ const name = item.name.toLowerCase();
41
+ if (name === lower) return 0;
42
+ if (name.startsWith(lower)) return 1;
43
+ if (name.includes(lower)) return 2;
44
+ const text = [
45
+ item.title,
46
+ item.summary,
47
+ item.description,
48
+ ...(item.keywords ?? []),
49
+ ]
50
+ .filter(Boolean)
51
+ .join('\n')
52
+ .toLowerCase();
53
+ return text.includes(lower) ? 3 : null;
54
+ }
55
+
27
56
  /**
28
57
  * Search all packages for `query` (a free-text term that never starts with
29
- * `@`). Resolves to a single component's docs when unambiguous, a search
30
- * response when several match, or throws AstryxError (ERR_NOT_FOUND) — with
31
- * fuzzy suggestions when any exist.
58
+ * `@`). Resolves to a search response when anything matches, even a single
59
+ * item or an exact component name, or throws AstryxError (ERR_NOT_FOUND) —
60
+ * with fuzzy suggestions when any exist.
32
61
  *
33
- * @param {ScannedPackage[]} packages
62
+ * @param {ScannedPackage[]} packages installed packages
34
63
  * @param {string} query
35
- * @param {{lang?: string | null, zh?: boolean}} opts
36
- * @returns {Promise<
37
- * import('../discover.type.mjs').DiscoverDetailDocResponse |
38
- * import('../discover.type.mjs').DiscoverSearchResponse
39
- * >}
64
+ * @param {{
65
+ * items?: SearchItem[],
66
+ * type?: import('../../../authoring/discover/type').DiscoverKind,
67
+ * only?: 'installed' | 'available',
68
+ * limit?: number,
69
+ * }} opts
70
+ * @returns {Promise<import('../discover.type.mjs').DiscoverSearchResponse>}
40
71
  */
41
- export async function search(packages, query, {lang, zh}) {
72
+ export async function search(packages, query, opts) {
73
+ const {items = [], type, only, limit} = opts;
42
74
  // An empty query must error, not match every component via `.includes('')`
43
75
  // (parity with the api/search leaf). The discover() dispatcher already routes
44
76
  // an empty query to list, but the leaf must be safe on its own.
@@ -51,50 +83,64 @@ export async function search(packages, query, {lang, zh}) {
51
83
  }
52
84
  const lower = query.toLowerCase();
53
85
 
54
- const exact = findComponent(packages, query);
55
- if (exact) return await docFromResult(exact, {lang, zh});
56
-
57
- const substringMatches =
58
- /** @type {Array<{pkg: ScannedPackage, comp: string}>} */ ([]);
59
- for (const pkg of packages) {
60
- for (const comp of pkg.components) {
61
- if (comp.toLowerCase().includes(lower)) {
62
- substringMatches.push({pkg, comp});
63
- }
64
- }
65
- }
86
+ /** @type {SearchItem[]} */
87
+ const installedComponents = packages.flatMap(pkg =>
88
+ pkg.components.map(name => ({
89
+ package: pkg.name,
90
+ kind: /** @type {const} */ ('component'),
91
+ name,
92
+ installed: true,
93
+ })),
94
+ );
66
95
 
67
- if (substringMatches.length === 1) {
68
- const match = substringMatches[0];
69
- const result = findComponent([match.pkg], match.comp);
70
- if (result) return await docFromResult(result, {lang, zh});
71
- }
96
+ const ranked = [...installedComponents, ...items]
97
+ .filter(item => (type == null ? true : item.kind === type))
98
+ .filter(item =>
99
+ only === 'installed'
100
+ ? item.installed
101
+ : only === 'available'
102
+ ? !item.installed
103
+ : true,
104
+ )
105
+ .map(item => ({item, score: rank(item, lower)}))
106
+ .filter(
107
+ /** @returns {m is {item: SearchItem, score: number}} */
108
+ m => m.score != null,
109
+ )
110
+ .sort(
111
+ (a, b) =>
112
+ a.score - b.score ||
113
+ Number(b.item.installed) - Number(a.item.installed) ||
114
+ KIND_ORDER.indexOf(a.item.kind) - KIND_ORDER.indexOf(b.item.kind) ||
115
+ a.item.package.localeCompare(b.item.package) ||
116
+ a.item.name.localeCompare(b.item.name),
117
+ );
72
118
 
73
- if (substringMatches.length > 1) {
119
+ if (ranked.length > 0) {
120
+ const matches = ranked.map(({item}) => ({
121
+ package: item.package,
122
+ component: item.name,
123
+ kind: item.kind,
124
+ installed: item.installed,
125
+ ...(item.title ? {title: item.title} : {}),
126
+ ...(item.summary ? {summary: item.summary} : {}),
127
+ }));
128
+ const shown = limit == null ? matches : matches.slice(0, limit);
74
129
  return {
75
130
  type: 'discover.search',
76
131
  data: {
77
132
  query,
78
- matches: substringMatches.map(m => ({
79
- package: m.pkg.name,
80
- component: m.comp,
81
- })),
133
+ matches: shown,
134
+ ...(shown.length < matches.length ? {total: matches.length} : {}),
82
135
  },
83
136
  };
84
137
  }
85
138
 
86
- // Fuzzy fallback
87
- const allComponents =
88
- /** @type {Array<{pkg: ScannedPackage, comp: string}>} */ ([]);
89
- for (const pkg of packages) {
90
- for (const comp of pkg.components) {
91
- allComponents.push({pkg, comp});
92
- }
93
- }
94
- const fuzzyMatches = allComponents
95
- .map(item => ({
96
- ...item,
97
- distance: levenshteinDistance(lower, item.comp.toLowerCase()),
139
+ // Fuzzy fallback over installed components.
140
+ const fuzzyMatches = installedComponents
141
+ .map(entry => ({
142
+ ...entry,
143
+ distance: levenshteinDistance(lower, entry.name.toLowerCase()),
98
144
  }))
99
145
  .filter(m => m.distance <= 3)
100
146
  .sort((a, b) => a.distance - b.distance)
@@ -104,7 +150,7 @@ export async function search(packages, query, {lang, zh}) {
104
150
  throw new AstryxError(
105
151
  `"${query}" not found`,
106
152
  fuzzyMatches.map(m => ({
107
- name: m.pkg.name + '/' + m.comp,
153
+ name: m.package + '/' + m.name,
108
154
  reason: 'similar name',
109
155
  })),
110
156
  ERROR_CODES.ERR_NOT_FOUND,
@@ -1,8 +1,8 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
3
  /**
4
- * @file Colocated tests for the discover.search leaf. Exact/single matches load
5
- * a real `.doc.mjs`, so these drive a small temp docs directory.
4
+ * @file Colocated tests for the discover.search leaf, over a small temp docs
5
+ * directory shaped like a scanned package.
6
6
  */
7
7
 
8
8
  import {describe, it, expect, beforeAll, afterAll} from 'vitest';
@@ -47,16 +47,31 @@ afterAll(() => {
47
47
  });
48
48
 
49
49
  describe('discover.search leaf', () => {
50
- it('an exact component name resolves to its docs', async () => {
50
+ it('lists an exact component name first, with every other match', async () => {
51
51
  const res = await search(packages, 'Alpha', {});
52
- expect(res.type).toBe('discover.detail.doc');
53
- expect(res.data.name).toBe('Alpha');
52
+ expect(res.type).toBe('discover.search');
53
+ expect(res.data.matches.map(m => m.component)).toEqual([
54
+ 'Alpha',
55
+ 'AlphaCard',
56
+ ]);
54
57
  });
55
58
 
56
- it('a single substring match resolves to its docs', async () => {
59
+ it('lists a single match too, so the response type never depends on the data', async () => {
57
60
  const res = await search(packages, 'card', {});
58
- expect(res.type).toBe('discover.detail.doc');
59
- expect(res.data.name).toBe('AlphaCard');
61
+ expect(res).toEqual({
62
+ type: 'discover.search',
63
+ data: {
64
+ query: 'card',
65
+ matches: [
66
+ {
67
+ package: '@acme/widgets',
68
+ component: 'AlphaCard',
69
+ kind: 'component',
70
+ installed: true,
71
+ },
72
+ ],
73
+ },
74
+ });
60
75
  });
61
76
 
62
77
  it('multiple substring matches return a search response', async () => {
@@ -66,8 +81,18 @@ describe('discover.search leaf', () => {
66
81
  data: {
67
82
  query: 'alph',
68
83
  matches: [
69
- {package: '@acme/widgets', component: 'Alpha'},
70
- {package: '@acme/widgets', component: 'AlphaCard'},
84
+ {
85
+ package: '@acme/widgets',
86
+ component: 'Alpha',
87
+ kind: 'component',
88
+ installed: true,
89
+ },
90
+ {
91
+ package: '@acme/widgets',
92
+ component: 'AlphaCard',
93
+ kind: 'component',
94
+ installed: true,
95
+ },
71
96
  ],
72
97
  },
73
98
  });
@@ -116,3 +141,112 @@ describe('discover.search leaf — empty query (parity with api/search)', () =>
116
141
  });
117
142
  });
118
143
  });
144
+
145
+ describe('discover.search leaf across every kind and source', () => {
146
+ /** @type {any[]} */
147
+ const items = [
148
+ {
149
+ package: '@acme/widgets',
150
+ kind: 'template',
151
+ name: 'pages/AlphaHome',
152
+ installed: true,
153
+ },
154
+ {
155
+ package: '@acme/charts',
156
+ kind: 'package',
157
+ name: '@acme/charts',
158
+ installed: false,
159
+ description: 'Charts for alpha dashboards',
160
+ },
161
+ {
162
+ package: '@acme/charts',
163
+ kind: 'component',
164
+ name: 'AlphaChart',
165
+ installed: false,
166
+ },
167
+ {
168
+ package: '@acme/charts',
169
+ kind: 'doc',
170
+ name: 'guide',
171
+ installed: false,
172
+ summary: 'How to chart',
173
+ },
174
+ ];
175
+
176
+ it('ranks every match by how closely its name matches, installed first among equals', async () => {
177
+ const res = await search(packages, 'alph', {items});
178
+ expect(res.type).toBe('discover.search');
179
+ expect(
180
+ res.data.matches.map(m => [m.kind, m.component, m.installed]),
181
+ ).toEqual([
182
+ ['component', 'Alpha', true],
183
+ ['component', 'AlphaCard', true],
184
+ ['component', 'AlphaChart', false],
185
+ ['template', 'pages/AlphaHome', true],
186
+ ['package', '@acme/charts', false],
187
+ ]);
188
+ });
189
+
190
+ it('lists an exact installed component name with the matches from every source', async () => {
191
+ const res = await search(packages, 'Alpha', {items});
192
+ expect(res.type).toBe('discover.search');
193
+ expect(res.data.matches[0]).toEqual({
194
+ package: '@acme/widgets',
195
+ component: 'Alpha',
196
+ kind: 'component',
197
+ installed: true,
198
+ });
199
+ expect(res.data.matches.map(m => m.component)).toContain('AlphaChart');
200
+ });
201
+
202
+ it('lists a single partial component match when other items match too', async () => {
203
+ const res = await search(packages, 'card', {
204
+ items: [
205
+ ...items,
206
+ {
207
+ package: '@acme/charts',
208
+ kind: 'template',
209
+ name: 'pages/CardGrid',
210
+ installed: false,
211
+ },
212
+ ],
213
+ });
214
+ expect(res.type).toBe('discover.search');
215
+ expect(res.data.matches.map(m => m.component)).toEqual([
216
+ 'AlphaCard',
217
+ 'pages/CardGrid',
218
+ ]);
219
+ });
220
+
221
+ it('lists one installed component when nothing else matches', async () => {
222
+ const res = await search(packages, 'card', {items});
223
+ expect(res.type).toBe('discover.search');
224
+ expect(res.data.matches.map(m => m.component)).toEqual(['AlphaCard']);
225
+ });
226
+
227
+ it('keeps one kind with type, and one side with only', async () => {
228
+ const templates = await search(packages, 'alph', {items, type: 'template'});
229
+ expect(templates.data.matches.map(m => m.component)).toEqual([
230
+ 'pages/AlphaHome',
231
+ ]);
232
+ const available = await search(packages, 'alph', {
233
+ items,
234
+ only: 'available',
235
+ });
236
+ expect(available.data.matches.map(m => m.component)).toEqual([
237
+ 'AlphaChart',
238
+ '@acme/charts',
239
+ ]);
240
+ });
241
+
242
+ it('caps the list at limit and reports the total', async () => {
243
+ const res = await search(packages, 'alph', {items, limit: 2});
244
+ expect(res.data.matches).toHaveLength(2);
245
+ expect(res.data.total).toBe(5);
246
+ });
247
+
248
+ it('matches titles, summaries, keywords, and descriptions too', async () => {
249
+ const res = await search(packages, 'how to chart', {items});
250
+ expect(res.data.matches.map(m => m.component)).toEqual(['guide']);
251
+ });
252
+ });
@@ -77,10 +77,12 @@ export function docsLinkProblems(catalog: DocsCatalog, tree: DocsTree, { owner,
77
77
  /**
78
78
  * What \`astryx doctor integration docs\` checks in one integration's docs: the
79
79
  * docs tree they build beside the CLI's (namespaces, placements, routes) and
80
- * every link in them (spec:AST-046, spec:AST-047).
80
+ * every link in them (spec:AST-046, spec:AST-047). A tree problem hides a doc,
81
+ * so it is an error; a link that names no doc prints as written, so it is a
82
+ * warning.
81
83
  * @param {{name: string}} integration
82
84
  * @param {{records: import('../../foundation/discovery/docs-discovery.mjs').DocsTopicRecord[], namespaces: import('../../foundation/doc-compiler/tree.mjs').TreeNamespaceInput[], guides: import('../../foundation/doc-compiler/tree.mjs').TreeDocInput[]}} discovered
83
- * @returns {Promise<string[]>}
85
+ * @returns {Promise<Array<{severity: 'error' | 'warning', message: string}>>}
84
86
  */
85
87
  export function packageDocsProblems(integration: {
86
88
  name: string;
@@ -88,7 +90,10 @@ export function packageDocsProblems(integration: {
88
90
  records: import("../../foundation/discovery/docs-discovery.mjs").DocsTopicRecord[];
89
91
  namespaces: import("../../foundation/doc-compiler/tree.mjs").TreeNamespaceInput[];
90
92
  guides: import("../../foundation/doc-compiler/tree.mjs").TreeDocInput[];
91
- }): Promise<string[]>;
93
+ }): Promise<Array<{
94
+ severity: "error" | "warning";
95
+ message: string;
96
+ }>>;
92
97
  /**
93
98
  * How a token reference finds its target: the topic it names in `catalog`,
94
99
  * lowered for the same language.
@@ -667,10 +667,12 @@ export async function docsLinkProblems(
667
667
  /**
668
668
  * What \`astryx doctor integration docs\` checks in one integration's docs: the
669
669
  * docs tree they build beside the CLI's (namespaces, placements, routes) and
670
- * every link in them (spec:AST-046, spec:AST-047).
670
+ * every link in them (spec:AST-046, spec:AST-047). A tree problem hides a doc,
671
+ * so it is an error; a link that names no doc prints as written, so it is a
672
+ * warning.
671
673
  * @param {{name: string}} integration
672
674
  * @param {{records: import('../../foundation/discovery/docs-discovery.mjs').DocsTopicRecord[], namespaces: import('../../foundation/doc-compiler/tree.mjs').TreeNamespaceInput[], guides: import('../../foundation/doc-compiler/tree.mjs').TreeDocInput[]}} discovered
673
- * @returns {Promise<string[]>}
675
+ * @returns {Promise<Array<{severity: 'error' | 'warning', message: string}>>}
674
676
  */
675
677
  export async function packageDocsProblems(integration, discovered) {
676
678
  const catalog = DocsCatalog.fromBuiltins();
@@ -680,12 +682,18 @@ export async function packageDocsProblems(integration, discovered) {
680
682
  guides: discovered.guides.map(input => ({...input, rank: 1})),
681
683
  });
682
684
  const tree = await projectTree(catalog);
685
+ /** @type {Array<{severity: 'error' | 'warning', message: string}>} */
683
686
  const problems = tree.diagnostics
684
687
  .filter(d => d.severity === 'error' && d.provider === integration.name)
685
- .map(d => `${d.source ?? integration.name}: ${d.message}`);
686
- problems.push(
687
- ...(await docsLinkProblems(catalog, tree, {owner: integration.name})),
688
- );
688
+ .map(d => ({
689
+ severity: /** @type {const} */ ('error'),
690
+ message: `${d.source ?? integration.name}: ${d.message}`,
691
+ }));
692
+ for (const message of await docsLinkProblems(catalog, tree, {
693
+ owner: integration.name,
694
+ })) {
695
+ problems.push({severity: 'warning', message});
696
+ }
689
697
  return problems;
690
698
  }
691
699
 
@@ -76,6 +76,32 @@ describe('reference doc overlays (#2182)', () => {
76
76
  ).toEqual([]);
77
77
  });
78
78
 
79
+ it(`${topic} --${variant}: every content override lands on a block of its own type`, async () => {
80
+ // Blocks are matched by index and type, and a mismatch is dropped with
81
+ // no warning: a prose override aimed at a code block leaves the base
82
+ // text in place, so the reader gets a translated title over an English
83
+ // body. Pad with null to reach the block you mean.
84
+ const base = await load(basePath);
85
+ const overlayMod = await load(overlayPath);
86
+ const overlay = overlayMod.docsDense || overlayMod.docsZh;
87
+ const byTitle = new Map(base.docs.sections.map(s => [s.title, s]));
88
+ const dropped = [];
89
+ for (const entry of overlay.sections || []) {
90
+ const section = byTitle.get(entry.section);
91
+ if (!section) continue;
92
+ (entry.content || []).forEach((block, i) => {
93
+ if (block == null) return;
94
+ const target = section.content[i];
95
+ if (target?.type !== block.type) {
96
+ dropped.push(
97
+ `${entry.section} block ${i}: ${block.type} over ${target?.type ?? 'nothing'}`,
98
+ );
99
+ }
100
+ });
101
+ }
102
+ expect(dropped, `${topic}.doc.${variant}.mjs overrides that never apply`).toEqual([]);
103
+ });
104
+
79
105
  it(`${topic} --${variant}: no base section is overridden twice`, async () => {
80
106
  const overlayMod = await load(overlayPath);
81
107
  const overlay = overlayMod.docsDense || overlayMod.docsZh;
@@ -127,7 +153,7 @@ describe('the reported defect: docs tokens --dense (#2182)', () => {
127
153
  const zh = await docs('theme', null, {zh: true});
128
154
  const titles = zh.data.sections.map(s => s.title);
129
155
  // Every section the overlay translates must appear once, in Chinese only.
130
- expect(titles).not.toContain('Light/Dark Mode');
156
+ expect(titles).not.toContain('Dark mode');
131
157
  expect(titles).toContain('亮/暗模式');
132
158
  });
133
159
  });
@@ -27,7 +27,7 @@ export const doc = {
27
27
  'A route opens a node of the docs tree instead: a namespace such as ' +
28
28
  "`cli/api` returns its children one level down, a typed doc such as " +
29
29
  "`cli/api/functions/search` returns its content, and a guide the tree " +
30
- 'places (`cli/integrations`) reads like any topic. ' +
30
+ 'places (`cli/integrations/quick-start`) reads like any topic. ' +
31
31
  'Every read but the list carries `links`, the commands that move from it: ' +
32
32
  '`up` to the level it sits in, `previous` and `next` to its neighbors, and, ' +
33
33
  'for a typed doc, `related` to the docs it names (its command or function, ' +
@@ -137,7 +137,7 @@ export const doc = {
137
137
  {label: 'One API function', code: "await docs('cli/api/functions/search');"},
138
138
  {
139
139
  label: 'A whole guide from the docs tree',
140
- code: "await docs('cli/integrations');",
140
+ code: "await docs('cli/integrations/quick-start');",
141
141
  },
142
142
  {label: 'One section by key', code: "await docs('tokens', 'spacing');"},
143
143
  ],
@@ -257,15 +257,20 @@ export type DoctorContext = {
257
257
  * could not be built, when it could not.
258
258
  */
259
259
  docsCatalogError?: string | null | undefined;
260
- /**
261
- * Combined project-level integration issues, including cross-package template replacement warnings.
262
- */
263
260
  integrationIssues?: {
264
261
  package: string;
265
262
  code: string;
266
263
  severity: "warning" | "error";
267
264
  message: string;
268
265
  }[] | null | undefined;
266
+ /**
267
+ * installed dependencies whose integration manifest could not be loaded
268
+ * Combined project-level integration issues, including cross-package template replacement warnings.
269
+ */
270
+ autolinkFailures?: {
271
+ spec: string;
272
+ error: string;
273
+ }[] | null | undefined;
269
274
  /**
270
275
  * - Error thrown while resolving the config
271
276
  * path (e.g. multiple config files present), surfaced by checkConfig as a FAIL.
@@ -13,14 +13,18 @@ export const doc = {
13
13
  name: 'doctor',
14
14
  namespace: 'cli/api',
15
15
  displayName: 'doctor()',
16
- summary: 'Read-only project + environment health check.',
16
+ summary:
17
+ "Check a project's Astryx setup and get a pass/warn/fail report per check. Use it as a CI gate or before debugging a broken install.",
17
18
  description:
18
- 'Runs a series of side-effect-free diagnostics: Node version, ' +
19
+ 'Runs a series of diagnostics: Node version, ' +
19
20
  '@astryxdesign/core install and version alignment with the CLI, installed ' +
20
21
  'themes and wiring, astryx.config validity, integrations linked from ' +
21
- 'package.json without a config entry, agent docs, core peer ' +
22
- 'dependencies, and the detected package manager, and returns a structured ' +
23
- 'report. It only reads (never installs, writes, or mutates), so it is safe ' +
22
+ 'package.json without a config entry, core peer dependencies, ' +
23
+ 'integration provider identity and contribution issues, agent docs, the ' +
24
+ 'detected package manager, and the health of the docs the CLI reads (authoring ' +
25
+ 'and CLI docs, the docs tree, doc size), and returns a structured ' +
26
+ 'report. It only reads (never installs, writes, or mutates), apart from ' +
27
+ "importing astryx.config, which runs that file's top-level code, so it is safe " +
24
28
  'as a CI gate and for agents to invoke.',
25
29
  importPath: '@astryxdesign/cli/api',
26
30
  signature: 'doctor(options?: DoctorOptions): Promise<DoctorResponse>',
@@ -29,18 +33,23 @@ export const doc = {
29
33
  {
30
34
  name: 'options.cwd',
31
35
  type: 'string',
32
- description: 'Directory to diagnose.',
36
+ description:
37
+ 'Directory to diagnose. A missing directory is not an error; it shows up in the checks (e.g. core-installed: fail).',
38
+ default: 'process.cwd()',
33
39
  },
34
40
  ],
35
41
  returns: [
36
42
  {
37
43
  type: 'doctor',
38
44
  description:
39
- 'The diagnostic report: `data.checks`, each with a stable id, label, `status` (`pass` | `warn` | `fail` | `info`), a one-line message, and a `fix` when the status is not `pass`; plus `data.summary` with counts per status.',
45
+ 'The diagnostic report: `data.checks`, each with a stable id, label, `status` (`pass` | `warn` | `fail` | `info`), a one-line message, and an optional `fix` (always present on `warn` and `fail`; some `info` checks carry one too); plus `data.summary` with counts per status.',
40
46
  },
41
47
  ],
42
48
  examples: [
43
- {label: 'Run diagnostics', code: 'const r = await doctor();'},
49
+ {
50
+ label: 'Fail a CI step on any failed check',
51
+ code: 'const r = await doctor();\nif (r.data.summary.fail > 0) process.exitCode = 1;',
52
+ },
44
53
  {
45
54
  label: 'Diagnose a directory',
46
55
  code: "await doctor({cwd: '/path/to/app'});",