@astryxdesign/cli 0.7.12-canary.ccb4a6d → 0.7.12-canary.da3dbdc

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 (85) hide show
  1. package/api/component/__fixtures__/resolutions/modules.txt +5 -5
  2. package/api/docs/_adapter.d.mts +22 -0
  3. package/api/docs/_adapter.mjs +81 -3
  4. package/api/docs/docs.doc.mjs +5 -5
  5. package/api/docs/docs.type.d.mts +21 -0
  6. package/api/docs/docs.type.mjs +11 -0
  7. package/api/docs/list/list.d.mts +5 -1
  8. package/api/docs/list/list.mjs +72 -6
  9. package/api/docs/node/node.mjs +2 -40
  10. package/api/index.d.mts +1 -0
  11. package/api/index.mjs +1 -0
  12. package/api/render/_adapter.mjs +9 -0
  13. package/api/render/render.doc.mjs +2 -2
  14. package/api/search/_adapter.d.mts +22 -1
  15. package/api/search/_adapter.mjs +37 -20
  16. package/api/search/exportSearchIndex.doc.d.mts +10 -0
  17. package/api/search/exportSearchIndex.doc.mjs +58 -0
  18. package/api/search/rankSearchIndex.doc.d.mts +10 -0
  19. package/api/search/rankSearchIndex.doc.mjs +79 -0
  20. package/api/search/search-index.d.mts +44 -0
  21. package/api/search/search-index.mjs +270 -0
  22. package/api/search/search.type.d.mts +28 -0
  23. package/api/search/search.type.mjs +17 -0
  24. package/api/swizzle/_moduleGraph.d.mts +7 -0
  25. package/api/swizzle/_moduleGraph.mjs +191 -83
  26. package/api/swizzle/copy/copy.mjs +100 -9
  27. package/api/swizzle/swizzle.doc.mjs +7 -3
  28. package/api/swizzle/swizzle.type.d.mts +5 -0
  29. package/api/swizzle/swizzle.type.mjs +1 -0
  30. package/api/template/template.mjs +1 -0
  31. package/assets/discover/official-packages.json +16 -0
  32. package/assets/docs/browser-support.doc.mjs +2 -2
  33. package/assets/docs/styling-overview.doc.mjs +6 -2
  34. package/assets/docs/tree/render-pages.doc.mjs +2 -2
  35. package/assets/docs/tree/tailwind.doc.mjs +41 -1
  36. package/assets/docs/tree/type-scale.doc.mjs +19 -2
  37. package/assets/templates/blocks/components/Calendar/useCalendarConstraintsHookUsage.doc.mjs +14 -0
  38. package/assets/templates/blocks/components/Calendar/useCalendarConstraintsHookUsage.tsx +146 -0
  39. package/assets/templates/blocks/components/Calendar/useCalendarNavigationHookUsage.doc.mjs +14 -0
  40. package/assets/templates/blocks/components/Calendar/useCalendarNavigationHookUsage.tsx +173 -0
  41. package/assets/templates/blocks/components/Hooks/useInputStatusIconHookUsage.tsx +1 -0
  42. package/assets/templates/blocks/components/Hooks/useInteractiveRoleHookUsage.doc.mjs +14 -0
  43. package/assets/templates/blocks/components/Hooks/useInteractiveRoleHookUsage.tsx +56 -0
  44. package/assets/templates/blocks/components/Hooks/useStreamingTextHookUsage.doc.mjs +2 -2
  45. package/assets/templates/blocks/components/Hooks/useStreamingTextHookUsage.tsx +66 -10
  46. package/assets/templates/blocks/components/Link/useLinkifyHookUsage.doc.mjs +14 -0
  47. package/assets/templates/blocks/components/Link/useLinkifyHookUsage.tsx +39 -0
  48. package/assets/templates/blocks/components/PowerSearch/usePowerSearchConfigHookUsage.doc.mjs +14 -0
  49. package/assets/templates/blocks/components/PowerSearch/usePowerSearchConfigHookUsage.tsx +83 -0
  50. package/assets/templates/blocks/components/Table/RowsVirtualizationHookUsage.doc.mjs +14 -0
  51. package/assets/templates/blocks/components/Table/RowsVirtualizationHookUsage.tsx +35 -0
  52. package/assets/templates/blocks/components/Table/useTableColumnSettingsStateHookUsage.doc.mjs +14 -0
  53. package/assets/templates/blocks/components/Table/useTableColumnSettingsStateHookUsage.tsx +97 -0
  54. package/assets/templates/pages/table-filter/page.tsx +15 -23
  55. package/assets/templates/pages/table-inbox/page.tsx +3 -6
  56. package/assets/templates/themes/butter/butterTheme.ts +2 -2
  57. package/assets/templates/themes/stone/stoneTheme.ts +1 -1
  58. package/assets/templates/themes/y2k/y2kTheme.ts +1 -1
  59. package/clients/cli/commands/docs.doc.mjs +5 -1
  60. package/clients/cli/commands/docs.mjs +30 -2
  61. package/clients/cli/commands/swizzle.doc.mjs +4 -0
  62. package/clients/cli/commands/swizzle.mjs +12 -0
  63. package/clients/cli/lib/resolve-theme.mjs +16 -7
  64. package/foundation/discovery/hook-discovery.mjs +31 -8
  65. package/foundation/doc-compiler/import.d.mts +5 -1
  66. package/foundation/doc-compiler/import.mjs +40 -6
  67. package/foundation/fs/import-url.d.mts +20 -0
  68. package/foundation/fs/import-url.mjs +22 -0
  69. package/foundation/fs/module-loader.d.mts +24 -3
  70. package/foundation/fs/module-loader.mjs +69 -47
  71. package/foundation/render/charts.d.mts +59 -0
  72. package/foundation/render/charts.mjs +216 -0
  73. package/foundation/render/embedded-imports.d.mts +7 -7
  74. package/foundation/render/embedded-imports.mjs +63 -54
  75. package/foundation/render/page-worker.mjs +339 -52
  76. package/foundation/render/render-page.mjs +26 -10
  77. package/foundation/render/ts-paths.d.mts +63 -0
  78. package/foundation/render/ts-paths.mjs +138 -0
  79. package/foundation/response/error-codes.doc.mjs +2 -2
  80. package/foundation/response/error-codes.mjs +3 -2
  81. package/foundation/text/levenshtein.d.mts +6 -1
  82. package/foundation/text/levenshtein.mjs +25 -9
  83. package/foundation/text/suggest-order.d.mts +8 -1
  84. package/foundation/text/suggest-order.mjs +72 -6
  85. package/package.json +7 -6
@@ -0,0 +1,58 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file FunctionDoc for `exportSearchIndex()`. Colocated with the API function it
5
+ * documents; the shape source of truth stays in `search.type.mjs`.
6
+ * @position packages/cli/api/search — function documentation
7
+ */
8
+
9
+ /** @type {import('@astryxdesign/cli/authoring').FunctionDoc} */
10
+ export const doc = {
11
+ type: 'function',
12
+ kind: 'api',
13
+ name: 'exportSearchIndex',
14
+ namespace: 'cli/api',
15
+ stability: 'experimental',
16
+ displayName: 'exportSearchIndex()',
17
+ summary:
18
+ 'The candidates search ranks in a folder, as plain JSON to store and rank later with rankSearchIndex().',
19
+ description:
20
+ "Gathers what `search()` would rank in `cwd` and returns it as `{version, candidates}`. Gathering reads each installed package's docs and loads its integration manifest, which runs that package's code, so a catalog that lists packages it must not load makes the index where loading is safe (a folder with those packages installed), stores it, and ranks it with {@link function:rankSearchIndex}. With `package`, the index keeps only that package's candidates, for a catalog that stores one index per package. A component description's inline doc tokens are resolved where the index is made, as `search` prints them; when Core could not be had there, the index says why in `coreSkipped`. Fields that point into the folder are left out, so an index reads the same wherever it was made. The candidates are the ranker's input, not a stable shape: an index is ranked only by the CLI version that made it. Experimental: it may change or be removed in a patch release.",
21
+ importPath: '@astryxdesign/cli/api',
22
+ signature: 'exportSearchIndex(options?: {cwd?: string, package?: string}): Promise<SearchIndex>',
23
+ keywords: ['search index', 'stored search index'],
24
+ params: [
25
+ {
26
+ name: 'options.cwd',
27
+ type: 'string',
28
+ description: 'The folder to gather from, read as `search()` reads it.',
29
+ default: 'process.cwd()',
30
+ },
31
+ {
32
+ name: 'options.package',
33
+ type: 'string',
34
+ description:
35
+ "Keep only the candidates this npm package owns: its components, templates, themes, and doc parts. Without it the index holds everything the folder's search ranks, Core's entries and the CLI's docs included.",
36
+ },
37
+ ],
38
+ returns: [
39
+ {
40
+ type: 'SearchIndex',
41
+ description:
42
+ '`version`, the CLI version that made it, and `candidates`, the gathered entries as plain JSON.',
43
+ },
44
+ ],
45
+ throws: [
46
+ {
47
+ code: 'ERR_INVALID_ARGUMENT',
48
+ when: '`package` is given and is not a non-empty string',
49
+ },
50
+ ],
51
+ examples: [
52
+ {
53
+ label: 'Index one installed integration',
54
+ code: "const index = await exportSearchIndex({cwd: '/tmp/harness', package: '@acme/ui'});",
55
+ },
56
+ ],
57
+ related: ['search', 'rankSearchIndex'],
58
+ };
@@ -0,0 +1,10 @@
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
+ * @file FunctionDoc for `rankSearchIndex()`. Colocated with the API function it
6
+ * documents; the shape source of truth stays in `search.type.mjs`.
7
+ * @position packages/cli/api/search — function documentation
8
+ */
9
+ /** @type {import('@astryxdesign/cli/authoring').FunctionDoc} */
10
+ export const doc: import("@astryxdesign/cli/authoring").FunctionDoc;
@@ -0,0 +1,79 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file FunctionDoc for `rankSearchIndex()`. Colocated with the API function it
5
+ * documents; the shape source of truth stays in `search.type.mjs`.
6
+ * @position packages/cli/api/search — function documentation
7
+ */
8
+
9
+ /** @type {import('@astryxdesign/cli/authoring').FunctionDoc} */
10
+ export const doc = {
11
+ type: 'function',
12
+ kind: 'api',
13
+ name: 'rankSearchIndex',
14
+ namespace: 'cli/api',
15
+ stability: 'experimental',
16
+ displayName: 'rankSearchIndex()',
17
+ summary:
18
+ "Ranks stored search indexes with search's own tree: an index made in one folder answers as search() does there.",
19
+ description:
20
+ "Ranks the candidates of every index together, with the same tree, weights, and order as {@link function:search}. An index made with `exportSearchIndex()` in one folder ranks exactly as `search(query)` does in that folder: the same results, order, and scores. Indexes made in separate folders are ranked together as one list; a doc that one package replaces or extends in another is not merged across them, so a project's own `search` can differ where that happens. A theme result's command is the in-project `theme add`. When every index was made where Core could not be had, an open rank (no `type`) carries `meta.partial`, as `search` does; a rank of `type` `component`, `hook`, or `template` throws the error `search` throws there. Every index must come from this CLI's version, because the candidates' shape is the ranker's input. Experimental: it may change or be removed in a patch release.",
21
+ importPath: '@astryxdesign/cli/api',
22
+ signature:
23
+ 'rankSearchIndex(query: string, indexes: SearchIndex | SearchIndex[], options?: {type?: SearchDomain, limit?: number}): Promise<SearchResponse>',
24
+ keywords: ['search index', 'stored search index'],
25
+ params: [
26
+ {
27
+ name: 'query',
28
+ type: 'string',
29
+ description: 'Free-text search term.',
30
+ required: true,
31
+ },
32
+ {
33
+ name: 'indexes',
34
+ type: 'SearchIndex | SearchIndex[]',
35
+ description:
36
+ 'Indexes made by `exportSearchIndex()` with this CLI version.',
37
+ required: true,
38
+ },
39
+ {
40
+ name: 'options.type',
41
+ type: "'component' | 'hook' | 'doc' | 'template' | 'theme'",
42
+ description: 'Restrict results to a single domain.',
43
+ },
44
+ {
45
+ name: 'options.limit',
46
+ type: 'number',
47
+ description: 'Maximum number of results.',
48
+ default: '20',
49
+ },
50
+ ],
51
+ returns: [
52
+ {
53
+ type: 'search',
54
+ description:
55
+ 'The same response as `search()`: the query, `matchCount`, and the ranked results bounded by `limit`.',
56
+ },
57
+ ],
58
+ throws: [
59
+ {
60
+ code: 'ERR_INVALID_ARGUMENT',
61
+ when: 'the query is empty, `type` is unknown, `limit` is not a positive integer, an index is not `{version, candidates}`, or an index was made by another CLI version',
62
+ },
63
+ {
64
+ code: 'ERR_CORE_NOT_FOUND',
65
+ when: 'every index was made where @astryxdesign/core could not be found, and `type` names a domain that reads it: `component`, `hook`, or `template`',
66
+ },
67
+ {
68
+ code: 'ERR_VERSIONS_UNAVAILABLE',
69
+ when: 'every index was made where the Core of the release that answers could not be installed into the per-user cache, and `type` names a domain that reads it: `component`, `hook`, or `template`; a rank with no `type` answers with `meta.partial` instead',
70
+ },
71
+ ],
72
+ examples: [
73
+ {
74
+ label: 'Rank stored indexes',
75
+ code: 'const r = await rankSearchIndex(text, [coreIndex, ...packageIndexes]);',
76
+ },
77
+ ],
78
+ related: ['search', 'exportSearchIndex'],
79
+ };
@@ -0,0 +1,44 @@
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
+ * The candidates `search` ranks in a folder, as plain JSON: store it, and rank
6
+ * it later with {@link rankSearchIndex}.
7
+ *
8
+ * Experimental (spec:AST-017 FR27): may change or be removed in a patch
9
+ * release.
10
+ * @experimental
11
+ * @param {object} [options]
12
+ * @param {string} [options.cwd] - The folder to gather from, as `search` reads it.
13
+ * @param {string} [options.package] - Keep only the candidates this npm package
14
+ * owns (its components, templates, themes, and doc parts).
15
+ * @returns {Promise<import('./search.type.mjs').SearchIndex>}
16
+ */
17
+ export function exportSearchIndex(options?: {
18
+ cwd?: string | undefined;
19
+ package?: string | undefined;
20
+ }): Promise<import("./search.type.mjs").SearchIndex>;
21
+ /**
22
+ * Ranks stored indexes with search's tree. An index made in one folder answers
23
+ * as `search(query)` does there (a theme result's command is the in-project
24
+ * `theme add`). Indexes made in separate folders, such as one per package
25
+ * (made with `package`) and one for Core, are ranked together as one list; a
26
+ * doc that one package replaces or extends in another is not merged across
27
+ * them. Every index must come from this CLI's version, since the tree reads
28
+ * the candidates' shape. When every index was made where Core could not be
29
+ * had, the answer carries `meta.partial`, as `search` does.
30
+ *
31
+ * Experimental (spec:AST-017 FR27): may change or be removed in a patch
32
+ * release.
33
+ * @experimental
34
+ * @param {string} query - Free-text search term.
35
+ * @param {import('./search.type.mjs').SearchIndex | import('./search.type.mjs').SearchIndex[]} indexes
36
+ * @param {object} [options]
37
+ * @param {import('./search.type.mjs').SearchDomain} [options.type] - Restrict to one domain.
38
+ * @param {number} [options.limit] - Max results (default 20).
39
+ * @returns {Promise<import('./search.type.mjs').SearchResponse>}
40
+ */
41
+ export function rankSearchIndex(query: string, indexes: import("./search.type.mjs").SearchIndex | import("./search.type.mjs").SearchIndex[], options?: {
42
+ type?: import("./search.type.mjs").SearchDomain | undefined;
43
+ limit?: number | undefined;
44
+ }): Promise<import("./search.type.mjs").SearchResponse>;
@@ -0,0 +1,270 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file A stored search index: the candidates `search` gathers in a folder, as
5
+ * plain JSON, and the ranking of stored candidates with search's own tree.
6
+ *
7
+ * Gathering reads every package's docs and loads its manifest, which runs the
8
+ * package's code. A catalog that lists packages it must not load (a web page
9
+ * that lists every published integration) stores each package's index once,
10
+ * where loading is safe, and ranks the stored indexes per query.
11
+ * @position api/search; search.mjs is the folder-reading search.
12
+ */
13
+
14
+ import {
15
+ docPackageManager,
16
+ runWithDocPackageManager,
17
+ } from '../../foundation/env/package-manager.mjs';
18
+ import {withListingCache} from '../../foundation/fs/listing-cache.mjs';
19
+ import {cliVersion} from '../../foundation/release/run-context.mjs';
20
+ import {runTree} from '../../foundation/route/route.mjs';
21
+ import {ERROR_CODES} from '../../foundation/response/error-codes.mjs';
22
+ import {linkSearchResponse} from '../component/_adapter.mjs';
23
+ import {AstryxError} from '../error.mjs';
24
+ import {
25
+ gatherCandidates,
26
+ loadSearchData,
27
+ resolveSearchCore,
28
+ } from './_adapter.mjs';
29
+ import {checkSearchArgs} from './_result.mjs';
30
+ import {SEARCH_TREE} from './tree.mjs';
31
+
32
+ /**
33
+ * Fields a gathered candidate holds that point into the folder it was read
34
+ * from. They decide ownership while gathering and never rank, so an index
35
+ * leaves them out and reads the same wherever it was made.
36
+ */
37
+ const LOCAL_FIELDS = ['_docPath'];
38
+
39
+ /**
40
+ * A component candidate's description as `search` prints it: its inline
41
+ * `{@link}`, `{@pm}` and `{@command}` tokens resolved in the folder the index
42
+ * is made in. The raw description stays the one that is scored.
43
+ */
44
+ const DISPLAY_FIELD = '_displayDescription';
45
+
46
+ /** Stands for the Core the indexes were made with, which ranking never reads. */
47
+ const STORED_CORE = 'stored-index';
48
+
49
+ /**
50
+ * @param {Record<string, unknown>} candidate
51
+ * @returns {Record<string, unknown>}
52
+ */
53
+ function portable(candidate) {
54
+ /** @type {Record<string, unknown>} */
55
+ const out = JSON.parse(JSON.stringify(candidate));
56
+ for (const field of LOCAL_FIELDS) delete out[field];
57
+ return out;
58
+ }
59
+
60
+ /**
61
+ * The candidates `search` ranks in a folder, as plain JSON: store it, and rank
62
+ * it later with {@link rankSearchIndex}.
63
+ *
64
+ * Experimental (spec:AST-017 FR27): may change or be removed in a patch
65
+ * release.
66
+ * @experimental
67
+ * @param {object} [options]
68
+ * @param {string} [options.cwd] - The folder to gather from, as `search` reads it.
69
+ * @param {string} [options.package] - Keep only the candidates this npm package
70
+ * owns (its components, templates, themes, and doc parts).
71
+ * @returns {Promise<import('./search.type.mjs').SearchIndex>}
72
+ */
73
+ export async function exportSearchIndex(options = {}) {
74
+ const {cwd = process.cwd(), package: owner} = options;
75
+ if (owner != null && (typeof owner !== 'string' || owner.trim() === '')) {
76
+ throw new AstryxError(
77
+ `Invalid package "${owner}". Pass an npm package name.`,
78
+ undefined,
79
+ ERROR_CODES.ERR_INVALID_ARGUMENT,
80
+ );
81
+ }
82
+ return runWithDocPackageManager(docPackageManager(cwd), () =>
83
+ withListingCache(async () => {
84
+ const gathered = await gatherCandidates({cwd});
85
+ const kept =
86
+ owner == null
87
+ ? gathered.all
88
+ : gathered.all.filter(candidate => candidate._package === owner);
89
+ const candidates = kept.map(candidate =>
90
+ portable(/** @type {Record<string, unknown>} */ (candidate)),
91
+ );
92
+ await addDisplayDescriptions(candidates, cwd);
93
+ return {
94
+ version: cliVersion(),
95
+ candidates,
96
+ ...(gathered.coreSkipped == null
97
+ ? {}
98
+ : {
99
+ coreSkipped: gathered.coreSkipped,
100
+ ...(gathered.coreSkippedCode == null
101
+ ? {}
102
+ : {coreSkippedCode: gathered.coreSkippedCode}),
103
+ }),
104
+ };
105
+ }),
106
+ );
107
+ }
108
+
109
+ /**
110
+ * Resolves each component description's inline tokens as `search` does for
111
+ * the results it prints, and keeps the printed text beside the scored one.
112
+ * @param {Record<string, unknown>[]} candidates
113
+ * @param {string} cwd
114
+ */
115
+ async function addDisplayDescriptions(candidates, cwd) {
116
+ const linkable = candidates.filter(
117
+ candidate =>
118
+ candidate.domain === 'component' &&
119
+ typeof candidate.description === 'string' &&
120
+ candidate.description.includes('{@'),
121
+ );
122
+ if (linkable.length === 0) return;
123
+ const response = await linkSearchResponse(
124
+ /** @type {any} */ ({
125
+ type: 'search',
126
+ data: {
127
+ results: linkable.map(candidate => ({
128
+ domain: 'component',
129
+ package: candidate._package,
130
+ description: candidate.description,
131
+ })),
132
+ },
133
+ }),
134
+ cwd,
135
+ );
136
+ response.data.results.forEach(
137
+ (/** @type {any} */ result, /** @type {number} */ i) => {
138
+ if (result.description !== linkable[i].description)
139
+ linkable[i][DISPLAY_FIELD] = result.description;
140
+ },
141
+ );
142
+ }
143
+
144
+ /**
145
+ * Ranks stored indexes with search's tree. An index made in one folder answers
146
+ * as `search(query)` does there (a theme result's command is the in-project
147
+ * `theme add`). Indexes made in separate folders, such as one per package
148
+ * (made with `package`) and one for Core, are ranked together as one list; a
149
+ * doc that one package replaces or extends in another is not merged across
150
+ * them. Every index must come from this CLI's version, since the tree reads
151
+ * the candidates' shape. When every index was made where Core could not be
152
+ * had, the answer carries `meta.partial`, as `search` does.
153
+ *
154
+ * Experimental (spec:AST-017 FR27): may change or be removed in a patch
155
+ * release.
156
+ * @experimental
157
+ * @param {string} query - Free-text search term.
158
+ * @param {import('./search.type.mjs').SearchIndex | import('./search.type.mjs').SearchIndex[]} indexes
159
+ * @param {object} [options]
160
+ * @param {import('./search.type.mjs').SearchDomain} [options.type] - Restrict to one domain.
161
+ * @param {number} [options.limit] - Max results (default 20).
162
+ * @returns {Promise<import('./search.type.mjs').SearchResponse>}
163
+ */
164
+ export async function rankSearchIndex(query, indexes, options = {}) {
165
+ checkSearchArgs(query, options);
166
+ const list = Array.isArray(indexes) ? indexes : [indexes];
167
+ const version = cliVersion();
168
+ /** @type {any[]} */
169
+ const all = [];
170
+ for (const index of list) {
171
+ if (index == null || !Array.isArray(index.candidates)) {
172
+ throw new AstryxError(
173
+ 'A search index is {version, candidates}, as exportSearchIndex() returns it.',
174
+ undefined,
175
+ ERROR_CODES.ERR_INVALID_ARGUMENT,
176
+ );
177
+ }
178
+ if (index.version !== version) {
179
+ throw new AstryxError(
180
+ `This search index was made by CLI ${index.version}; this is CLI ${version}. Make it again with this version's exportSearchIndex().`,
181
+ undefined,
182
+ ERROR_CODES.ERR_INVALID_ARGUMENT,
183
+ );
184
+ }
185
+ all.push(...index.candidates);
186
+ }
187
+ const {type} = options;
188
+ const wanted =
189
+ type == null ? all : all.filter(candidate => candidate.domain === type);
190
+ const {searchedComponents, coreSkipped} = coreFlags(list, type);
191
+ const response = /** @type {import('./search.type.mjs').SearchResponse} */ (
192
+ await runTree(
193
+ SEARCH_TREE,
194
+ {query, options: {type, limit: options.limit}},
195
+ {
196
+ all: wanted,
197
+ components: wanted.filter(
198
+ candidate => candidate.domain === 'component',
199
+ ),
200
+ searchedComponents,
201
+ outside: false,
202
+ coreSkipped,
203
+ coreSkippedCode: null,
204
+ data: loadSearchData(),
205
+ },
206
+ )
207
+ );
208
+ return withDisplayDescriptions(response, wanted);
209
+ }
210
+
211
+ /**
212
+ * The catalog flags `search` derives from Core for a request of `type`, by
213
+ * search's own rule (`resolveSearchCore`). Core is missing only when no index
214
+ * had it. An open search then names what it skipped. A docs or themes search
215
+ * never reads Core. A Core domain asked for by name throws the error that
216
+ * `search` throws.
217
+ * @param {import('./search.type.mjs').SearchIndex[]} list
218
+ * @param {import('./search.type.mjs').SearchDomain | undefined} type
219
+ * @returns {{searchedComponents: boolean, coreSkipped: string | null}}
220
+ */
221
+ function coreFlags(list, type) {
222
+ const missing =
223
+ list.length > 0 &&
224
+ list.every(index => typeof index.coreSkipped === 'string');
225
+ const lookup = () => {
226
+ if (!missing) return STORED_CORE;
227
+ throw Object.assign(
228
+ new Error(/** @type {string} */ (list[0].coreSkipped)),
229
+ {
230
+ code: list[0].coreSkippedCode ?? ERROR_CODES.ERR_CORE_NOT_FOUND,
231
+ },
232
+ );
233
+ };
234
+ const {coreSkipped, wants} = resolveSearchCore(type, lookup, STORED_CORE);
235
+ return {searchedComponents: wants('component'), coreSkipped};
236
+ }
237
+
238
+ /**
239
+ * The response with each component result's description as `search` prints
240
+ * it, from the text the index stored when it was made.
241
+ * @param {import('./search.type.mjs').SearchResponse} response
242
+ * @param {any[]} candidates
243
+ * @returns {import('./search.type.mjs').SearchResponse}
244
+ */
245
+ function withDisplayDescriptions(response, candidates) {
246
+ /** @type {Map<string, string>} */
247
+ const display = new Map();
248
+ for (const candidate of candidates) {
249
+ if (
250
+ candidate.domain === 'component' &&
251
+ typeof candidate[DISPLAY_FIELD] === 'string'
252
+ )
253
+ display.set(
254
+ `${candidate._package}\u0000${candidate._resultName ?? candidate.name}`,
255
+ candidate[DISPLAY_FIELD],
256
+ );
257
+ }
258
+ if (display.size === 0) return response;
259
+ return {
260
+ ...response,
261
+ data: {
262
+ ...response.data,
263
+ results: response.data.results.map(result => {
264
+ if (result.domain !== 'component') return result;
265
+ const text = display.get(`${result.package}\u0000${result.name}`);
266
+ return text == null ? result : {...result, description: text};
267
+ }),
268
+ },
269
+ };
270
+ }
@@ -95,6 +95,34 @@ export type SearchResponse = {
95
95
  };
96
96
  } | undefined;
97
97
  };
98
+ /**
99
+ * The candidates `search` ranks in a folder, as `exportSearchIndex()` returns them:
100
+ * plain JSON to store and rank later with `rankSearchIndex()`.
101
+ */
102
+ export type SearchIndex = {
103
+ /**
104
+ * - The CLI version that made it. `rankSearchIndex()`
105
+ * ranks only indexes made by its own version.
106
+ */
107
+ version: string;
108
+ /**
109
+ * - The gathered candidates,
110
+ * each the entry search scores. Their fields are the ranker's input, not a
111
+ * contract: they may change in any release, which is why `version` must match.
112
+ */
113
+ candidates: Record<string, unknown>[];
114
+ /**
115
+ * - Why Core could not be had where the index
116
+ * was made, when it could not: its components, hooks and templates are then
117
+ * missing, and `rankSearchIndex()` answers as `search` does without Core
118
+ * (`meta.partial` for an open search; refused for a domain that reads Core).
119
+ */
120
+ coreSkipped?: string | undefined;
121
+ /**
122
+ * - The error code that Core lookup gave.
123
+ */
124
+ coreSkippedCode?: string | undefined;
125
+ };
98
126
  /**
99
127
  * Options for `search()`.
100
128
  */
@@ -51,6 +51,23 @@
51
51
  * The results are the docs and themes.
52
52
  */
53
53
 
54
+ /**
55
+ * The candidates `search` ranks in a folder, as `exportSearchIndex()` returns them:
56
+ * plain JSON to store and rank later with `rankSearchIndex()`.
57
+ * @experimental May change or be removed in a patch release.
58
+ * @typedef {object} SearchIndex
59
+ * @property {string} version - The CLI version that made it. `rankSearchIndex()`
60
+ * ranks only indexes made by its own version.
61
+ * @property {Record<string, unknown>[]} candidates - The gathered candidates,
62
+ * each the entry search scores. Their fields are the ranker's input, not a
63
+ * contract: they may change in any release, which is why `version` must match.
64
+ * @property {string} [coreSkipped] - Why Core could not be had where the index
65
+ * was made, when it could not: its components, hooks and templates are then
66
+ * missing, and `rankSearchIndex()` answers as `search` does without Core
67
+ * (`meta.partial` for an open search; refused for a domain that reads Core).
68
+ * @property {string} [coreSkippedCode] - The error code that Core lookup gave.
69
+ */
70
+
54
71
  /**
55
72
  * Options for `search()`.
56
73
  * @typedef {object} SearchOptions
@@ -16,6 +16,8 @@
16
16
  * file the copy holds (absolute source path), with the edits its imports need
17
17
  * @property {Map<string, Set<string>>} dependsOn file -> carried files its
18
18
  * computed specifiers point at
19
+ * @property {Map<string, string>} otherPackages the source root of each other
20
+ * Astryx package the copy carries modules from, with its package name
19
21
  * @property {Record<string, string>} dependencies each npm package the copied
20
22
  * files import, other than the owner package and Node's built-in modules,
21
23
  * with the range the owner package declares
@@ -162,6 +164,11 @@ export type CopyPlan = {
162
164
  * computed specifiers point at
163
165
  */
164
166
  dependsOn: Map<string, Set<string>>;
167
+ /**
168
+ * the source root of each other
169
+ * Astryx package the copy carries modules from, with its package name
170
+ */
171
+ otherPackages: Map<string, string>;
165
172
  /**
166
173
  * each npm package the copied
167
174
  * files import, other than the owner package and Node's built-in modules,