@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
@@ -0,0 +1,155 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file The plain signals search's learned searchers score with. Every one
5
+ * is a deterministic count over the query's words and each entry's own text:
6
+ * BM25 over the entry's name, its docs text, and its source-built card; how
7
+ * many query words it covers; whether the query holds its name; the lexical
8
+ * ranker's score and rank; and the router's guess of the domain the query
9
+ * wants. A searcher's weights file holds one number per signal (and per
10
+ * signal and domain); its score is their weighted sum.
11
+ * @input a query, the lexical ranking, the gathered candidates, static weights
12
+ * @output ranked entry ids, and the search response built from them
13
+ * @position api/search; the searchers and the root router import it
14
+ */
15
+
16
+ import {DOMAINS, ROUTES, words, fold, queryWords} from './_query-words.mjs';
17
+ import {entryOf} from './_entries.mjs';
18
+
19
+ /** @typedef {import('./_entries.mjs').EntryTable} EntryTable */
20
+
21
+ /** The signal names, in weights-file order (before the per-domain copies). */
22
+ export const SIGNALS = [
23
+ 's_score',
24
+ 's_rr',
25
+ 's_in',
26
+ 's_top20',
27
+ 'd_component',
28
+ 'd_hook',
29
+ 'd_doc',
30
+ 'd_template',
31
+ 'd_theme',
32
+ 'bm_name',
33
+ 'bm_desc',
34
+ 'bm_card',
35
+ 'cover',
36
+ 'cover_all',
37
+ 'head',
38
+ 'r_dom',
39
+ 'r_none',
40
+ 'name_in_q',
41
+ 'name_exact',
42
+ ];
43
+
44
+ /** The signals that also get one copy per domain. */
45
+ export const PER_DOMAIN = [
46
+ 's_score',
47
+ 's_rr',
48
+ 'bm_name',
49
+ 'bm_desc',
50
+ 'bm_card',
51
+ 'cover',
52
+ 'r_dom',
53
+ 'name_in_q',
54
+ ];
55
+
56
+ /**
57
+ * Every entry's signals for one query, as rows of numbers.
58
+ * @param {EntryTable} table
59
+ * @param {string} query
60
+ * @param {{domain: string, name: string, score?: number}[]} lexical the lexical ranking, best first
61
+ * @param {number[] | null} route the router's class probabilities
62
+ * @returns {Float64Array[]}
63
+ */
64
+ export function signals(table, query, lexical, route) {
65
+ const n = table.ids.length;
66
+ const S = SIGNALS.length;
67
+ const rows = table.ids.map(() => new Float64Array(S));
68
+ const seen = new Set();
69
+ let r = 0;
70
+ for (const res of lexical) {
71
+ const k = entryOf(table, res);
72
+ if (k === undefined || seen.has(k)) continue;
73
+ seen.add(k);
74
+ r++;
75
+ const x = rows[k];
76
+ x[0] = (res.score ?? 0) / 100;
77
+ x[1] = 1 / r;
78
+ x[2] = 1;
79
+ x[3] = r <= 20 ? 1 : 0;
80
+ }
81
+ for (let k = 0; k < n; k++) rows[k][4 + table.domain[k]] = 1;
82
+ const qw = [...new Set(queryWords(query))];
83
+ if (qw.length) {
84
+ const hits = qw.map(() => new Uint8Array(n));
85
+ /** @type {const} */ (['name', 'desc', 'card']).forEach((f, j) => {
86
+ const {post, idf, len, avg} = table.fields[f];
87
+ qw.forEach((w, a) => {
88
+ for (const [k, c] of post.get(w) ?? []) {
89
+ rows[k][9 + j] +=
90
+ ((idf.get(w) ?? 0) * c * 2.2) /
91
+ (c + 1.2 * (0.25 + (0.75 * len[k]) / avg));
92
+ hits[a][k] = 1;
93
+ }
94
+ });
95
+ });
96
+ for (let k = 0; k < n; k++) {
97
+ let cov = 0;
98
+ for (let a = 0; a < qw.length; a++) cov += hits[a][k];
99
+ rows[k][12] = cov / qw.length;
100
+ rows[k][13] = cov === qw.length && qw.length > 1 ? 1 : 0;
101
+ rows[k][14] = hits[qw.length - 1][k];
102
+ }
103
+ }
104
+ if (route) {
105
+ for (let k = 0; k < n; k++) {
106
+ rows[k][15] = route[table.domain[k]];
107
+ rows[k][16] = route[ROUTES.length - 1];
108
+ }
109
+ }
110
+ const all = words(query).map(fold);
111
+ const spaced = ` ${all.join(' ')} `;
112
+ const runs = new Set();
113
+ for (let len = 1; len <= 3; len++) {
114
+ for (let a = 0; a + len <= all.length; a++)
115
+ runs.add(all.slice(a, a + len).join(''));
116
+ }
117
+ const whole = all.join('');
118
+ for (let k = 0; k < n; k++) {
119
+ const nw = table.name[k];
120
+ if (!nw.length) continue;
121
+ const j = nw.join('');
122
+ if (spaced.includes(` ${nw.join(' ')} `) || runs.has(j)) {
123
+ rows[k][17] = 1;
124
+ if (j === whole) rows[k][18] = 1;
125
+ }
126
+ }
127
+ return rows;
128
+ }
129
+
130
+ /**
131
+ * Ranks the entries by a searcher's weights: the weighted sum of each entry's
132
+ * standardized signals and per-domain copies; ties go to the earlier id.
133
+ * @param {EntryTable} table
134
+ * @param {Float64Array[]} rows
135
+ * @param {{w: number[], mu: number[], sd: number[]}} weights
136
+ * @returns {number[]} entry positions, best first
137
+ */
138
+ export function rank(table, rows, weights) {
139
+ const {w, mu, sd} = weights;
140
+ const S = SIGNALS.length;
141
+ const per = PER_DOMAIN.map(s => SIGNALS.indexOf(s));
142
+ const score = rows.map((x, k) => {
143
+ let s = 0;
144
+ for (let j = 0; j < S; j++) s += ((x[j] - mu[j]) / sd[j]) * w[j];
145
+ const d = table.domain[k];
146
+ per.forEach((j, p) => {
147
+ for (let dd = 0; dd < DOMAINS.length; dd++) {
148
+ const c = S + p * DOMAINS.length + dd;
149
+ s += (((dd === d ? x[j] : 0) - mu[c]) / sd[c]) * w[c];
150
+ }
151
+ });
152
+ return Math.round(s * 1e9) / 1e9;
153
+ });
154
+ return score.map((s, k) => k).sort((a, b) => score[b] - score[a] || a - b);
155
+ }
@@ -0,0 +1,26 @@
1
+ // @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
2
+ // DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
3
+
4
+ /**
5
+ * Build component candidates: core's own tree plus every configured
6
+ * integration's components.
7
+ * @param {string} coreDir
8
+ * @param {string} cwd
9
+ * @returns {Promise<Candidate[]>}
10
+ */
11
+ export function gatherComponents(coreDir: string, cwd: string): Promise<Candidate[]>;
12
+ /**
13
+ * Every component the project can use, Core's and its integrations', with the
14
+ * keywords its own doc declares: the discovery and doc reads search's own
15
+ * candidates use, without the import paths and prose a result carries. `build`
16
+ * reads it to tell a part of a page from a page when its search was narrowed
17
+ * away from components.
18
+ * @param {string} coreDir
19
+ * @param {string} cwd
20
+ * @returns {Promise<{name: string, keywords: string[]}[]>}
21
+ */
22
+ export function componentKeywords(coreDir: string, cwd: string): Promise<{
23
+ name: string;
24
+ keywords: string[];
25
+ }[]>;
26
+ export type Candidate = import("./_lexical.mjs").Candidate;
@@ -0,0 +1,284 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Search's component candidates: Core's components and the ones
5
+ * integrations contribute, read from their docs. Imported only by the adapter.
6
+ */
7
+
8
+ import {discoverExternalPackages} from '../../foundation/fs/paths.mjs';
9
+ import {
10
+ discoverComponents,
11
+ discoverValidIntegrationComponents,
12
+ docStem,
13
+ familyMember,
14
+ findComponentReadme,
15
+ forEachIntegrationDoc,
16
+ integrationMemberEntry,
17
+ integrationRecordHold,
18
+ isIntegrationMember,
19
+ resolveImportPath,
20
+ resolveIntegrationImportPath,
21
+ CORE_PACKAGE,
22
+ } from '../../foundation/discovery/component-discovery.mjs';
23
+ import {
24
+ loadComponentReplacements,
25
+ loadIntegrationsSafely,
26
+ resolveLegacyExternalDoc,
27
+ } from '../component/_adapter.mjs';
28
+ import {loadModuleDoc, guidanceFrom} from './_module-doc.mjs';
29
+
30
+ /** @typedef {import('./_lexical.mjs').Candidate} Candidate */
31
+
32
+ /**
33
+ * Build component candidates from core's own tree: name + keywords +
34
+ * usage/description from the component's .doc.mjs.
35
+ * @param {string} coreDir
36
+ * @returns {Promise<Candidate[]>}
37
+ */
38
+ async function gatherCoreComponents(coreDir) {
39
+ const grouped = discoverComponents(coreDir);
40
+ const names = Object.values(grouped).flat();
41
+ /** @type {Candidate[]} */
42
+ const candidates = [];
43
+ for (const comp of names) {
44
+ const readme = findComponentReadme(coreDir, comp);
45
+ /** @type {string[]} */
46
+ let keywords = [];
47
+ let description = '';
48
+ /** @type {string[]} */
49
+ let guidance = [];
50
+ if (readme && readme.endsWith('.doc.mjs')) {
51
+ const loaded = await loadModuleDoc(readme);
52
+ // A member documented in its family's doc (VStack in Stack's doc) is
53
+ // indexed from its own entry: the family's keywords and guidance
54
+ // describe the family, not the member.
55
+ const doc = familyMember(loaded, comp) ?? loaded;
56
+ if (doc) {
57
+ keywords = Array.isArray(doc.keywords) ? doc.keywords : [];
58
+ description = doc.usage?.description || doc.description || '';
59
+ guidance = guidanceFrom(doc);
60
+ }
61
+ }
62
+ candidates.push({
63
+ domain: 'component',
64
+ name: comp,
65
+ keywords,
66
+ description,
67
+ guidance,
68
+ _package: CORE_PACKAGE,
69
+ _import: resolveImportPath(coreDir, comp),
70
+ });
71
+ }
72
+ return candidates;
73
+ }
74
+
75
+ /**
76
+ * Build component candidates contributed by the project's configured
77
+ * integrations (astryx.config's `integrations`): name + keywords +
78
+ * usage/description from each component's .doc.mjs, same as core. Without
79
+ * this, an integration component is invisible to `search`/`build` even
80
+ * though `component --list`/`component <Name>` already resolve it — the two
81
+ * discovery paths silently disagreed. Also returns, per name, the docs that
82
+ * hold it by their file name and the loadable docs that only record it.
83
+ * @param {import('../../foundation/integrations/integrations.mjs').LoadedIntegration[]} loadedIntegrations
84
+ * @returns {Promise<{candidates: Candidate[], fileNamed: Map<string, Set<string>>, documented: Map<string, Set<string>>}>}
85
+ */
86
+ async function gatherIntegrationComponents(loadedIntegrations) {
87
+ /** @type {Candidate[]} */
88
+ const candidates = [];
89
+ // What the owner rule reads: the docs that hold a name by their file name,
90
+ // loadable or not and hidden ones too, and the loadable docs that only
91
+ // record it.
92
+ /** @type {Map<string, Set<string>>} */
93
+ const fileNamed = new Map();
94
+ /** @type {Map<string, Set<string>>} */
95
+ const documented = new Map();
96
+ /** @param {Map<string, Set<string>>} map @param {string} name @param {string} docPath */
97
+ const hold = (map, name, docPath) =>
98
+ map.set(name, (map.get(name) ?? new Set()).add(docPath));
99
+ for (const integration of loadedIntegrations) {
100
+ const {components, discovered} =
101
+ await discoverValidIntegrationComponents(integration);
102
+ const renamed = new Set(
103
+ discovered
104
+ .filter(rec => integrationRecordHold(rec) === 'doc')
105
+ .filter(rec => !isIntegrationMember(rec))
106
+ .map(rec => rec.docPath),
107
+ );
108
+ forEachIntegrationDoc(integration.components, (docPath, stem) => {
109
+ if (!renamed.has(docPath)) hold(fileNamed, stem, docPath);
110
+ });
111
+ for (const rec of components) {
112
+ const loaded = await loadModuleDoc(rec.docPath);
113
+ // A family member is indexed from its own entry: the family's keywords
114
+ // and guidance describe the family.
115
+ const member = isIntegrationMember(rec);
116
+ const doc = member ? integrationMemberEntry(loaded, rec) : loaded;
117
+ // A doc that names another export of its file still answers to the
118
+ // file's name, as `component <file name>` does.
119
+ const fileName = docStem(rec.docPath);
120
+ if (integrationRecordHold(rec) === 'doc') {
121
+ hold(documented, rec.name, rec.docPath);
122
+ }
123
+ if (renamed.has(rec.docPath) && !member) {
124
+ hold(documented, fileName, rec.docPath);
125
+ }
126
+ candidates.push({
127
+ domain: 'component',
128
+ name: rec.name,
129
+ aliases: !member && fileName !== rec.name ? [fileName] : [],
130
+ _hold: integrationRecordHold(rec),
131
+ _docPath: rec.docPath,
132
+ keywords: doc && Array.isArray(doc.keywords) ? doc.keywords : [],
133
+ description: doc ? doc.usage?.description || doc.description || '' : '',
134
+ guidance: guidanceFrom(doc),
135
+ _package: rec.package ?? integration.name,
136
+ // Exactly what `component` reports: a doc may state its own specifier
137
+ // (one entry point exporting several components), and only when it
138
+ // does not do we resolve the subpath against the owning package's
139
+ // exports — read off the integration, which the loader already parsed.
140
+ // Reporting the bare package name here handed out a path that does not
141
+ // resolve, and disagreed with what `component <Name>` said about the
142
+ // very same component.
143
+ _import: resolveIntegrationImportPath(
144
+ {
145
+ exportsMap: integration.__packageExports,
146
+ packageDir: integration.__packageDir,
147
+ docPath: rec.docPath,
148
+ packageName: rec.package,
149
+ },
150
+ rec.name,
151
+ loaded?.import,
152
+ ),
153
+ });
154
+ }
155
+ }
156
+ return {candidates, fileNamed, documented};
157
+ }
158
+
159
+ /**
160
+ * Build component candidates: core's own tree plus every configured
161
+ * integration's components.
162
+ * @param {string} coreDir
163
+ * @param {string} cwd
164
+ * @returns {Promise<Candidate[]>}
165
+ */
166
+ export async function gatherComponents(coreDir, cwd) {
167
+ const loadedIntegrations = await loadIntegrationsSafely(cwd);
168
+ const [core, gathered, replacements] = await Promise.all([
169
+ gatherCoreComponents(coreDir),
170
+ gatherIntegrationComponents(loadedIntegrations),
171
+ loadComponentReplacements(coreDir, loadedIntegrations),
172
+ ]);
173
+ const {candidates: integrations, fileNamed, documented} = gathered;
174
+ // An active replacement answers to the Core name it replaces, so a search
175
+ // for that name finds the replacement and not the Core original
176
+ // (spec:AST-035 FR11).
177
+ /** @type {Map<string, string>} */
178
+ const targetOf = new Map(
179
+ replacements.active.map(active => [
180
+ `${active.package}\0${active.name}`,
181
+ active.target,
182
+ ]),
183
+ );
184
+ // The owner rule, as component detail applies it: a candidate `component
185
+ // <name>` does not open names its package and follows the owner, and a
186
+ // file-name alias that opens another owner is dropped.
187
+ const coreNames = new Set(core.map(candidate => candidate.name));
188
+ /** @param {string} name @param {Candidate} candidate */
189
+ const opens = (name, candidate) =>
190
+ !coreNames.has(name) &&
191
+ findComponentReadme(coreDir, name) == null &&
192
+ [...(fileNamed.get(name) ?? [])].every(doc => doc === candidate._docPath) &&
193
+ [...(documented.get(name) ?? [])].every(doc => doc === candidate._docPath);
194
+ // A legacy docs package keeps an exact name over an export name a doc records.
195
+ /** @type {ReturnType<typeof discoverExternalPackages> | undefined} */
196
+ let externals;
197
+ /** @param {string} name */
198
+ const legacyDocuments = name =>
199
+ (externals ??= discoverExternalPackages(cwd)).some(
200
+ ext => resolveLegacyExternalDoc(ext, name) != null,
201
+ );
202
+ const ruled = integrations.map(candidate => {
203
+ const aliases = (candidate.aliases ?? []).filter(alias =>
204
+ opens(alias, candidate),
205
+ );
206
+ return candidate._hold === 'doc' &&
207
+ (!opens(candidate.name, candidate) || legacyDocuments(candidate.name))
208
+ ? {
209
+ ...candidate,
210
+ aliases,
211
+ _command: `astryx component ${candidate.name} --package ${candidate._package}`,
212
+ }
213
+ : {...candidate, aliases};
214
+ });
215
+ return [
216
+ ...core.filter(candidate => !replacements.forTarget(candidate.name)),
217
+ ...[
218
+ ...ruled.filter(candidate => !candidate._command),
219
+ ...ruled.filter(candidate => candidate._command),
220
+ ].map(candidate => {
221
+ const target = targetOf.get(`${candidate._package}\0${candidate.name}`);
222
+ if (target != null) {
223
+ return {...candidate, aliases: [...(candidate.aliases ?? []), target]};
224
+ }
225
+ // Another package's component named after a replaced Core component is
226
+ // shadowed for the bare name (FR14): its command names its package.
227
+ const shadowedBy = replacements.forTarget(candidate.name);
228
+ return shadowedBy && shadowedBy.package !== candidate._package
229
+ ? {
230
+ ...candidate,
231
+ _command: `astryx component ${candidate.name} --package ${candidate._package}`,
232
+ }
233
+ : candidate;
234
+ }),
235
+ ];
236
+ }
237
+
238
+ /**
239
+ * Every component the project can use, Core's and its integrations', with the
240
+ * keywords its own doc declares: the discovery and doc reads search's own
241
+ * candidates use, without the import paths and prose a result carries. `build`
242
+ * reads it to tell a part of a page from a page when its search was narrowed
243
+ * away from components.
244
+ * @param {string} coreDir
245
+ * @param {string} cwd
246
+ * @returns {Promise<{name: string, keywords: string[]}[]>}
247
+ */
248
+ export async function componentKeywords(coreDir, cwd) {
249
+ /** @param {any} doc */
250
+ const keywordsOf = doc => (Array.isArray(doc?.keywords) ? doc.keywords : []);
251
+ const core = Object.values(discoverComponents(coreDir))
252
+ .flat()
253
+ .map(async name => {
254
+ const readme = findComponentReadme(coreDir, name);
255
+ const loaded =
256
+ readme && readme.endsWith('.doc.mjs')
257
+ ? await loadModuleDoc(readme)
258
+ : null;
259
+ return {name, keywords: keywordsOf(familyMember(loaded, name) ?? loaded)};
260
+ });
261
+ const loadedIntegrations = await loadIntegrationsSafely(cwd);
262
+ const replacements = await loadComponentReplacements(
263
+ coreDir,
264
+ loadedIntegrations,
265
+ );
266
+ const integrations = loadedIntegrations.map(async integration => {
267
+ const {components} = await discoverValidIntegrationComponents(integration);
268
+ return Promise.all(
269
+ components.map(async rec => {
270
+ const loaded = await loadModuleDoc(rec.docPath);
271
+ return {
272
+ name: rec.name,
273
+ keywords: keywordsOf(integrationMemberEntry(loaded, rec) ?? loaded),
274
+ };
275
+ }),
276
+ );
277
+ });
278
+ return [
279
+ ...(await Promise.all(core)).filter(
280
+ component => !replacements.forTarget(component.name),
281
+ ),
282
+ ...(await Promise.all(integrations)).flat(),
283
+ ];
284
+ }
@@ -0,0 +1,20 @@
1
+ // @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
2
+ // DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
3
+
4
+ /** @typedef {import('./_lexical.mjs').Candidate} Candidate */
5
+ /**
6
+ * Build doc candidates at the grain a reader reads them: each section of a
7
+ * topic, whose command reads just that section; each topic as a whole, whose
8
+ * command lists its sections; and each docs-tree node by its route. The tree's
9
+ * guides split into sections like topics, and its typed docs also match by
10
+ * their own name, so `assertResponse` finds `cli/api/functions/assert-response`.
11
+ *
12
+ * Reads the project's catalog rather than the CLI's own docs directory, so a
13
+ * topic an integration contributed (or replaced) is searchable exactly like a
14
+ * built-in one — otherwise the replacement is served by `astryx docs` but
15
+ * invisible to the command whose job is finding it.
16
+ * @param {string} cwd
17
+ * @returns {Promise<Candidate[]>}
18
+ */
19
+ export function gatherDocs(cwd: string): Promise<Candidate[]>;
20
+ export type Candidate = import("./_lexical.mjs").Candidate;