@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.
- package/api/component/__fixtures__/resolutions/modules.txt +5 -5
- package/api/docs/_adapter.d.mts +22 -0
- package/api/docs/_adapter.mjs +81 -3
- package/api/docs/docs.doc.mjs +5 -5
- package/api/docs/docs.type.d.mts +21 -0
- package/api/docs/docs.type.mjs +11 -0
- package/api/docs/list/list.d.mts +5 -1
- package/api/docs/list/list.mjs +72 -6
- package/api/docs/node/node.mjs +2 -40
- package/api/index.d.mts +1 -0
- package/api/index.mjs +1 -0
- package/api/render/_adapter.mjs +9 -0
- package/api/render/render.doc.mjs +2 -2
- package/api/search/_adapter.d.mts +22 -1
- package/api/search/_adapter.mjs +37 -20
- package/api/search/exportSearchIndex.doc.d.mts +10 -0
- package/api/search/exportSearchIndex.doc.mjs +58 -0
- package/api/search/rankSearchIndex.doc.d.mts +10 -0
- package/api/search/rankSearchIndex.doc.mjs +79 -0
- package/api/search/search-index.d.mts +44 -0
- package/api/search/search-index.mjs +270 -0
- package/api/search/search.type.d.mts +28 -0
- package/api/search/search.type.mjs +17 -0
- package/api/swizzle/_moduleGraph.d.mts +7 -0
- package/api/swizzle/_moduleGraph.mjs +191 -83
- package/api/swizzle/copy/copy.mjs +100 -9
- package/api/swizzle/swizzle.doc.mjs +7 -3
- package/api/swizzle/swizzle.type.d.mts +5 -0
- package/api/swizzle/swizzle.type.mjs +1 -0
- package/api/template/template.mjs +1 -0
- package/assets/discover/official-packages.json +16 -0
- package/assets/docs/browser-support.doc.mjs +2 -2
- package/assets/docs/styling-overview.doc.mjs +6 -2
- package/assets/docs/tree/render-pages.doc.mjs +2 -2
- package/assets/docs/tree/tailwind.doc.mjs +41 -1
- package/assets/docs/tree/type-scale.doc.mjs +19 -2
- package/assets/templates/blocks/components/Calendar/useCalendarConstraintsHookUsage.doc.mjs +14 -0
- package/assets/templates/blocks/components/Calendar/useCalendarConstraintsHookUsage.tsx +146 -0
- package/assets/templates/blocks/components/Calendar/useCalendarNavigationHookUsage.doc.mjs +14 -0
- package/assets/templates/blocks/components/Calendar/useCalendarNavigationHookUsage.tsx +173 -0
- package/assets/templates/blocks/components/Hooks/useInputStatusIconHookUsage.tsx +1 -0
- package/assets/templates/blocks/components/Hooks/useInteractiveRoleHookUsage.doc.mjs +14 -0
- package/assets/templates/blocks/components/Hooks/useInteractiveRoleHookUsage.tsx +56 -0
- package/assets/templates/blocks/components/Hooks/useStreamingTextHookUsage.doc.mjs +2 -2
- package/assets/templates/blocks/components/Hooks/useStreamingTextHookUsage.tsx +66 -10
- package/assets/templates/blocks/components/Link/useLinkifyHookUsage.doc.mjs +14 -0
- package/assets/templates/blocks/components/Link/useLinkifyHookUsage.tsx +39 -0
- package/assets/templates/blocks/components/PowerSearch/usePowerSearchConfigHookUsage.doc.mjs +14 -0
- package/assets/templates/blocks/components/PowerSearch/usePowerSearchConfigHookUsage.tsx +83 -0
- package/assets/templates/blocks/components/Table/RowsVirtualizationHookUsage.doc.mjs +14 -0
- package/assets/templates/blocks/components/Table/RowsVirtualizationHookUsage.tsx +35 -0
- package/assets/templates/blocks/components/Table/useTableColumnSettingsStateHookUsage.doc.mjs +14 -0
- package/assets/templates/blocks/components/Table/useTableColumnSettingsStateHookUsage.tsx +97 -0
- package/assets/templates/pages/table-filter/page.tsx +15 -23
- package/assets/templates/pages/table-inbox/page.tsx +3 -6
- package/assets/templates/themes/butter/butterTheme.ts +2 -2
- package/assets/templates/themes/stone/stoneTheme.ts +1 -1
- package/assets/templates/themes/y2k/y2kTheme.ts +1 -1
- package/clients/cli/commands/docs.doc.mjs +5 -1
- package/clients/cli/commands/docs.mjs +30 -2
- package/clients/cli/commands/swizzle.doc.mjs +4 -0
- package/clients/cli/commands/swizzle.mjs +12 -0
- package/clients/cli/lib/resolve-theme.mjs +16 -7
- package/foundation/discovery/hook-discovery.mjs +31 -8
- package/foundation/doc-compiler/import.d.mts +5 -1
- package/foundation/doc-compiler/import.mjs +40 -6
- package/foundation/fs/import-url.d.mts +20 -0
- package/foundation/fs/import-url.mjs +22 -0
- package/foundation/fs/module-loader.d.mts +24 -3
- package/foundation/fs/module-loader.mjs +69 -47
- package/foundation/render/charts.d.mts +59 -0
- package/foundation/render/charts.mjs +216 -0
- package/foundation/render/embedded-imports.d.mts +7 -7
- package/foundation/render/embedded-imports.mjs +63 -54
- package/foundation/render/page-worker.mjs +339 -52
- package/foundation/render/render-page.mjs +26 -10
- package/foundation/render/ts-paths.d.mts +63 -0
- package/foundation/render/ts-paths.mjs +138 -0
- package/foundation/response/error-codes.doc.mjs +2 -2
- package/foundation/response/error-codes.mjs +3 -2
- package/foundation/text/levenshtein.d.mts +6 -1
- package/foundation/text/levenshtein.mjs +25 -9
- package/foundation/text/suggest-order.d.mts +8 -1
- package/foundation/text/suggest-order.mjs +72 -6
- 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,
|