@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.
- package/README.md +99 -66
- package/api/build/_adapter.d.mts +58 -0
- package/api/build/_adapter.mjs +115 -3
- package/api/build/blocks/blocks.d.mts +42 -0
- package/api/build/blocks/blocks.mjs +169 -0
- package/api/build/blocks/blocks.test.mjs +108 -0
- package/api/build/blocks/blocks.weights.json +1 -0
- package/api/build/build.mjs +25 -6
- package/api/build/kit/answer.d.mts +14 -0
- package/api/build/kit/answer.mjs +85 -0
- package/api/build/kit/answer.test.mjs +38 -0
- package/api/build/kit/kind.d.mts +25 -0
- package/api/build/kit/kind.mjs +170 -0
- package/api/build/kit/kit.d.mts +2 -4
- package/api/build/kit/kit.mjs +8 -398
- package/api/build/kit/rank.d.mts +3 -32
- package/api/build/kit/rank.mjs +25 -342
- package/api/build/kit/rank.test.mjs +2 -1
- package/api/build/kit/signals.d.mts +60 -0
- package/api/build/kit/signals.mjs +284 -0
- package/api/build/kit/start.d.mts +32 -0
- package/api/build/kit/start.mjs +163 -0
- package/api/build/kit/words.d.mts +72 -0
- package/api/build/kit/words.mjs +202 -0
- package/api/build/tree.d.mts +20 -0
- package/api/build/tree.mjs +52 -0
- package/api/build/tree.test.mjs +177 -0
- package/api/build/tree.weights.json +1 -0
- package/api/component/component.mjs +16 -2
- package/api/component/component.test.mjs +15 -0
- package/api/discover/_adapter.mjs +36 -9
- package/api/discover/_adapter.test.mjs +42 -0
- package/api/docs/_adapter.mjs +17 -5
- package/api/docs/docs.test.mjs +7 -1
- package/api/doctor/doctor.mjs +86 -12
- package/api/doctor/doctor.test.mjs +28 -3
- package/api/init/init.doc.mjs +20 -6
- package/api/init/init.test.mjs +18 -2
- package/api/init/init.type.d.mts +3 -3
- package/api/init/init.type.mjs +3 -3
- package/api/init/remove/remove.mjs +17 -4
- package/api/init/remove/remove.test.mjs +114 -2
- package/api/init/run/run.mjs +69 -6
- package/api/integration/authoring-checks.mjs +83 -9
- package/api/integration/pack-check.mjs +123 -4
- package/api/integration/pack-check.test.mjs +87 -0
- package/api/integration/validate-integration-case-disk.test.mjs +111 -0
- package/api/integration/validate-integration-gaps.test.mjs +1964 -0
- package/api/integration/validate-integration.d.mts +11 -2
- package/api/integration/validate-integration.mjs +226 -32
- package/api/integration/validate-integration.type.d.mts +58 -1
- package/api/integration/validate-integration.type.mjs +45 -2
- package/api/integration/validateIntegration.doc.mjs +1 -1
- package/api/search/ORDERING.md +28 -0
- package/api/search/_adapter.d.mts +66 -2
- package/api/search/_adapter.mjs +118 -10
- package/api/search/_candidate-score.d.mts +40 -0
- package/api/search/_candidate-score.mjs +238 -0
- package/api/search/_cards.json +6098 -0
- package/api/search/_compound.d.mts +50 -0
- package/api/search/_compound.mjs +170 -0
- package/api/search/_doc-searcher.d.mts +4 -0
- package/api/search/_doc-searcher.mjs +23 -0
- package/api/search/_doc-searcher.test.mjs +36 -0
- package/api/search/_doc-searcher.weights.json +1 -0
- package/api/search/_doc-text.d.mts +30 -0
- package/api/search/_doc-text.mjs +87 -0
- package/api/search/_entries.d.mts +52 -0
- package/api/search/_entries.mjs +158 -0
- package/api/search/_features.d.mts +35 -0
- package/api/search/_features.mjs +155 -0
- package/api/search/_gather-components.d.mts +26 -0
- package/api/search/_gather-components.mjs +284 -0
- package/api/search/_gather-docs.d.mts +20 -0
- package/api/search/_gather-docs.mjs +254 -0
- package/api/search/_gather-hooks.d.mts +12 -0
- package/api/search/_gather-hooks.mjs +52 -0
- package/api/search/_gather-templates.d.mts +25 -0
- package/api/search/_gather-templates.mjs +107 -0
- package/api/search/_lexical.d.mts +135 -0
- package/api/search/_lexical.mjs +351 -0
- package/api/search/_main-searcher.d.mts +19 -0
- package/api/search/_main-searcher.mjs +115 -0
- package/api/search/_main-searcher.test.mjs +106 -0
- package/api/search/_main-searcher.weights.json +1 -0
- package/api/search/_module-doc.d.mts +32 -0
- package/api/search/_module-doc.mjs +78 -0
- package/api/search/_order.d.mts +32 -0
- package/api/search/_order.mjs +162 -0
- package/api/search/_order.test.mjs +72 -0
- package/api/search/_priority.d.mts +50 -0
- package/api/search/_priority.mjs +139 -0
- package/api/search/_query-words.d.mts +25 -0
- package/api/search/_query-words.mjs +92 -0
- package/api/search/_query.d.mts +65 -0
- package/api/search/_query.mjs +176 -0
- package/api/search/_result.d.mts +97 -0
- package/api/search/_result.mjs +150 -0
- package/api/search/_score.d.mts +60 -0
- package/api/search/_score.mjs +283 -0
- package/api/search/_synonyms.d.mts +21 -0
- package/api/search/_synonyms.mjs +67 -0
- package/api/search/_template-searcher.d.mts +4 -0
- package/api/search/_template-searcher.mjs +23 -0
- package/api/search/_template-searcher.test.mjs +40 -0
- package/api/search/_template-searcher.weights.json +1 -0
- package/api/search/_titles.d.mts +33 -0
- package/api/search/_titles.mjs +65 -0
- package/api/search/_words.d.mts +64 -0
- package/api/search/_words.mjs +157 -0
- package/api/search/search.d.mts +22 -305
- package/api/search/search.doc.mjs +7 -5
- package/api/search/search.mjs +53 -2483
- package/api/search/search.type.d.mts +6 -1
- package/api/search/search.type.mjs +6 -1
- package/api/search/tree.d.mts +15 -0
- package/api/search/tree.mjs +40 -0
- package/api/search/tree.test.mjs +66 -0
- package/api/search/tree.weights.json +1 -0
- package/api/theme/build/build.packed-old-core.test.mjs +1 -0
- package/api/upgrade/_adapter.d.mts +57 -9
- package/api/upgrade/_adapter.mjs +198 -28
- package/api/upgrade/registry/registry.d.mts +10 -4
- package/api/upgrade/registry/registry.mjs +33 -16
- package/api/upgrade/registry/registry.test.mjs +85 -0
- package/api/upgrade/run/run.d.mts +4 -2
- package/api/upgrade/run/run.mjs +13 -2
- package/api/upgrade/run/run.test.mjs +417 -3
- package/api/upgrade/upgrade.doc.mjs +10 -5
- package/api/upgrade/upgrade.mjs +13 -2
- package/api/upgrade/upgrade.test.mjs +194 -0
- package/api/upgrade/upgrade.type.d.mts +5 -3
- package/api/upgrade/upgrade.type.mjs +8 -6
- package/assets/codemods/transforms/v0.7.0/__tests__/validate-data-token-ownership.test.mjs +11 -0
- package/assets/codemods/transforms/v0.7.0/validate-data-token-ownership.mjs +3 -3
- package/assets/docs/is-my-code-good.doc.mjs +287 -0
- package/assets/docs/tree/api.doc.mjs +6 -0
- package/assets/docs/tree/host-entry.doc.mjs +128 -0
- package/assets/docs/tree/template-grading-rubric.doc.mjs +16 -37
- package/assets/docs/tree/tokens-data.doc.mjs +2 -2
- package/assets/docs/working-with-ai.doc.mjs +6 -2
- package/assets/templates/themes/butter/butterTheme.ts +4 -2
- package/assets/templates/themes/stone/stoneTheme.ts +4 -2
- package/assets/templates/themes/y2k/y2kTheme.ts +4 -2
- package/authoring/debug/parse.d.mts +1 -1
- package/clients/cli/bin/astryx.mjs +7 -78
- package/clients/cli/commands/doctor.mjs +30 -0
- package/clients/cli/commands/doctor.test.mjs +65 -0
- package/clients/cli/commands/init.behavior.test.mjs +230 -1
- package/clients/cli/commands/init.doc.mjs +28 -9
- package/clients/cli/commands/init.flag-help.test.mjs +1 -0
- package/clients/cli/commands/init.mjs +2 -2
- package/clients/cli/commands/outside-project.test.mjs +2 -1
- package/clients/cli/commands/setup-nudge.test.mjs +61 -0
- package/clients/cli/commands/upgrade.doc.mjs +4 -2
- package/clients/cli/commands/upgrade.mjs +9 -2
- package/clients/cli/commands/upgrade.subfolder-path.test.mjs +97 -0
- package/clients/cli/index.mjs +19 -1
- package/clients/cli/run.mjs +108 -0
- package/foundation/agent-docs/agent-doc-state.d.mts +20 -7
- package/foundation/agent-docs/agent-doc-state.mjs +35 -8
- package/foundation/agent-docs/agent-docs.d.mts +100 -12
- package/foundation/agent-docs/agent-docs.mjs +1010 -99
- package/foundation/agent-docs/agent-docs.path-safety.test.mjs +1239 -17
- package/foundation/agent-docs/agent-docs.test.mjs +678 -35
- package/foundation/config/project.mjs +20 -22
- package/foundation/config/project.test.mjs +56 -0
- package/foundation/discovery/component-discovery.d.mts +37 -0
- package/foundation/discovery/component-discovery.mjs +80 -5
- package/foundation/discovery/component-loader.d.mts +1 -1
- package/foundation/discovery/docs-discovery.mjs +14 -1
- package/foundation/discovery/module-exports.d.mts +41 -0
- package/foundation/discovery/module-exports.mjs +253 -0
- package/foundation/discovery/template-adapter.mjs +18 -1
- package/foundation/discovery/template-needs.mjs +1 -44
- package/foundation/discovery/theme-discovery.mjs +147 -3
- package/foundation/doc-compiler/doc-loads.test.mjs +8 -0
- package/foundation/doc-compiler/read.mjs +59 -1
- package/foundation/doc-compiler/read.test.mjs +11 -2
- package/foundation/fs/paths.d.mts +70 -8
- package/foundation/fs/paths.mjs +556 -38
- package/foundation/fs/paths.test.mjs +430 -54
- package/foundation/integrations/autolink.d.mts +2 -1
- package/foundation/integrations/autolink.mjs +4 -13
- package/foundation/integrations/computer-integrations.d.mts +6 -3
- package/foundation/integrations/computer-integrations.mjs +38 -9
- package/foundation/integrations/contribution-inventory.d.mts +15 -20
- package/foundation/integrations/contribution-inventory.mjs +41 -5
- package/foundation/integrations/integrations.d.mts +27 -1
- package/foundation/integrations/integrations.mjs +61 -2
- package/foundation/integrations/validate-contributions.d.mts +115 -0
- package/foundation/integrations/validate-contributions.mjs +1168 -13
- package/foundation/release/handoff.d.mts +23 -4
- package/foundation/release/handoff.mjs +175 -8
- package/foundation/release/run-context.mjs +14 -0
- package/foundation/response/response-types.doc.mjs +1 -1
- package/foundation/route/route.d.mts +285 -0
- package/foundation/route/route.mjs +324 -0
- package/foundation/route/route.test.mjs +153 -0
- package/foundation/runtime/hosted-runtime.test.mjs +403 -0
- package/foundation/runtime/locked-versions.d.mts +32 -0
- package/foundation/runtime/locked-versions.mjs +65 -0
- package/foundation/runtime/package-root.d.mts +91 -0
- package/foundation/runtime/package-root.mjs +174 -0
- package/foundation/runtime/package-set.d.mts +62 -0
- package/foundation/runtime/package-set.mjs +130 -0
- package/foundation/runtime/package-set.test.mjs +432 -0
- package/host/door-conformance.test.mjs +274 -0
- package/host/index.d.ts +116 -0
- package/host/index.mjs +295 -0
- package/host/index.test.mjs +302 -0
- package/package.json +14 -9
|
@@ -1,11 +1,75 @@
|
|
|
1
1
|
// @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
|
|
2
2
|
// DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
|
|
3
3
|
|
|
4
|
+
/** @typedef {import('./_lexical.mjs').Candidate} Candidate */
|
|
4
5
|
/**
|
|
5
6
|
* The Core a typed search reads: the project's, else the release's own,
|
|
6
|
-
* installed into the per-user cache first when needed
|
|
7
|
-
* requireCoreDir does.
|
|
7
|
+
* installed into the per-user cache first when needed (a network install and
|
|
8
|
+
* an npm subprocess; spec AST-081 FR3). Throws as requireCoreDir does.
|
|
8
9
|
* @param {string} cwd
|
|
9
10
|
* @returns {string}
|
|
10
11
|
*/
|
|
11
12
|
export function requireSearchCoreDir(cwd: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Gathers every candidate the query's domains hold.
|
|
15
|
+
*
|
|
16
|
+
* @param {{cwd?: string, type?: string}} options
|
|
17
|
+
* @returns {Promise<{all: Candidate[], components: Candidate[], searchedComponents: boolean, outside: boolean, coreSkipped: string | null}>}
|
|
18
|
+
*/
|
|
19
|
+
export function gatherCandidates(options: {
|
|
20
|
+
cwd?: string;
|
|
21
|
+
type?: string;
|
|
22
|
+
}): Promise<{
|
|
23
|
+
all: Candidate[];
|
|
24
|
+
components: Candidate[];
|
|
25
|
+
searchedComponents: boolean;
|
|
26
|
+
outside: boolean;
|
|
27
|
+
coreSkipped: string | null;
|
|
28
|
+
}>;
|
|
29
|
+
/**
|
|
30
|
+
* The search tree's static data, read once: the entry cards and each node's
|
|
31
|
+
* weights file.
|
|
32
|
+
*
|
|
33
|
+
* These files are static, reviewed data that only order matches inside a score
|
|
34
|
+
* band; ORDERING.md beside them says what each holds and how
|
|
35
|
+
* they are refreshed.
|
|
36
|
+
* @returns {SearchData}
|
|
37
|
+
*/
|
|
38
|
+
export function loadSearchData(): SearchData;
|
|
39
|
+
export { componentKeywords } from "./_gather-components.mjs";
|
|
40
|
+
export type Candidate = import("./_lexical.mjs").Candidate;
|
|
41
|
+
export type SearcherWeights = {
|
|
42
|
+
w: number[];
|
|
43
|
+
mu: number[];
|
|
44
|
+
sd: number[];
|
|
45
|
+
};
|
|
46
|
+
export type SearchData = {
|
|
47
|
+
/**
|
|
48
|
+
* Each Core component's and hook's source-built card: what it is, the words
|
|
49
|
+
* people type for it, its main props.
|
|
50
|
+
*/
|
|
51
|
+
cards: Record<string, {
|
|
52
|
+
what?: string;
|
|
53
|
+
synonyms?: string[];
|
|
54
|
+
props?: string[];
|
|
55
|
+
}>;
|
|
56
|
+
/**
|
|
57
|
+
* The root classifier's weights.
|
|
58
|
+
*/
|
|
59
|
+
router: import("../../foundation/route/route.mjs").ClassifierWeights;
|
|
60
|
+
/**
|
|
61
|
+
* The main searcher's weights.
|
|
62
|
+
*/
|
|
63
|
+
main: SearcherWeights;
|
|
64
|
+
/**
|
|
65
|
+
* The doc searcher's weights.
|
|
66
|
+
*/
|
|
67
|
+
doc: SearcherWeights;
|
|
68
|
+
/**
|
|
69
|
+
* The template searcher's weights.
|
|
70
|
+
*/
|
|
71
|
+
template: SearcherWeights;
|
|
72
|
+
};
|
|
73
|
+
export type SearchCatalog = Awaited<ReturnType<typeof gatherCandidates>> & {
|
|
74
|
+
data: SearchData;
|
|
75
|
+
};
|
package/api/search/_adapter.mjs
CHANGED
|
@@ -1,25 +1,133 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @file
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* INV21).
|
|
8
|
-
*
|
|
9
|
-
* @input the folder a search runs in
|
|
10
|
-
* @output the Core folder a search reads
|
|
11
|
-
* @position packages/cli/api/search — imported by search.mjs only
|
|
4
|
+
* @file Search's adapter: the only place search reads the environment. It
|
|
5
|
+
* gathers every candidate from the domains a query asks for (one file per
|
|
6
|
+
* domain, _gather-*.mjs) and loads the tree's static data before it runs.
|
|
12
7
|
*/
|
|
13
8
|
|
|
9
|
+
import {readFileSync} from 'node:fs';
|
|
14
10
|
import {requireCoreDir} from '../../foundation/fs/paths.mjs';
|
|
11
|
+
import {isOutsideProject} from '../../foundation/env/project-scope.mjs';
|
|
12
|
+
import {coreDirOrThrow} from '../error.mjs';
|
|
13
|
+
import {CORELESS_DOMAINS} from './_query.mjs';
|
|
14
|
+
import {gatherComponents} from './_gather-components.mjs';
|
|
15
|
+
import {gatherHooks} from './_gather-hooks.mjs';
|
|
16
|
+
import {gatherDocs} from './_gather-docs.mjs';
|
|
17
|
+
import {gatherTemplates, gatherThemes} from './_gather-templates.mjs';
|
|
18
|
+
|
|
19
|
+
export {componentKeywords} from './_gather-components.mjs';
|
|
20
|
+
|
|
21
|
+
/** @typedef {import('./_lexical.mjs').Candidate} Candidate */
|
|
15
22
|
|
|
16
23
|
/**
|
|
17
24
|
* The Core a typed search reads: the project's, else the release's own,
|
|
18
|
-
* installed into the per-user cache first when needed
|
|
19
|
-
* requireCoreDir does.
|
|
25
|
+
* installed into the per-user cache first when needed (a network install and
|
|
26
|
+
* an npm subprocess; spec AST-081 FR3). Throws as requireCoreDir does.
|
|
20
27
|
* @param {string} cwd
|
|
21
28
|
* @returns {string}
|
|
22
29
|
*/
|
|
23
30
|
export function requireSearchCoreDir(cwd) {
|
|
24
31
|
return requireCoreDir(cwd);
|
|
25
32
|
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Gathers every candidate the query's domains hold.
|
|
36
|
+
*
|
|
37
|
+
* @param {{cwd?: string, type?: string}} options
|
|
38
|
+
* @returns {Promise<{all: Candidate[], components: Candidate[], searchedComponents: boolean, outside: boolean, coreSkipped: string | null}>}
|
|
39
|
+
*/
|
|
40
|
+
export async function gatherCandidates(options) {
|
|
41
|
+
const {cwd = process.cwd(), type} = options;
|
|
42
|
+
// `astryx docs` reads docs without @astryxdesign/core, and `astryx theme
|
|
43
|
+
// list` reads themes without it (bundled themes need no project), so a
|
|
44
|
+
// search of either must too. Every other domain reads core: asked for by
|
|
45
|
+
// name, it is an error without core; an open search then covers the docs
|
|
46
|
+
// and themes alone.
|
|
47
|
+
const needsCore = !type || !CORELESS_DOMAINS.includes(type);
|
|
48
|
+
// A domain that reads Core gets it as `component` does: the project's, else
|
|
49
|
+
// the answering release's own, installed into the per-user cache on first
|
|
50
|
+
// need (spec AST-081 FR3). Asked for by name, a Core that cannot be had is
|
|
51
|
+
// the error; an open search answers from the rest and says what it skipped
|
|
52
|
+
// and why, never silently.
|
|
53
|
+
/** @type {string | null} */
|
|
54
|
+
let coreDir = null;
|
|
55
|
+
/** @type {string | null} */
|
|
56
|
+
let coreSkipped = null;
|
|
57
|
+
if (needsCore && type) {
|
|
58
|
+
coreDir = coreDirOrThrow(requireSearchCoreDir, cwd);
|
|
59
|
+
} else if (needsCore) {
|
|
60
|
+
try {
|
|
61
|
+
coreDir = requireSearchCoreDir(cwd);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
coreSkipped = /** @type {Error} */ (error).message;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Gather candidates from each requested domain in parallel.
|
|
68
|
+
/** @param {string} d */
|
|
69
|
+
const wants = d =>
|
|
70
|
+
(!type && (coreDir != null || CORELESS_DOMAINS.includes(d))) || type === d;
|
|
71
|
+
const [components, hooks, docTopics, templates, themes] = await Promise.all([
|
|
72
|
+
wants('component')
|
|
73
|
+
? gatherComponents(/** @type {string} */ (coreDir), cwd)
|
|
74
|
+
: [],
|
|
75
|
+
wants('hook') ? gatherHooks(/** @type {string} */ (coreDir)) : [],
|
|
76
|
+
wants('doc') ? gatherDocs(cwd) : [],
|
|
77
|
+
wants('template') ? gatherTemplates(cwd) : [],
|
|
78
|
+
wants('theme') ? gatherThemes(cwd) : [],
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
const all = [...components, ...hooks, ...docTopics, ...templates, ...themes];
|
|
82
|
+
// Outside a project a theme cannot be added to an app, so its command forks
|
|
83
|
+
// the source instead (spec AST-081 FR3).
|
|
84
|
+
const outside = themes.length > 0 && isOutsideProject(cwd);
|
|
85
|
+
return {
|
|
86
|
+
all,
|
|
87
|
+
components,
|
|
88
|
+
searchedComponents: wants('component'),
|
|
89
|
+
outside,
|
|
90
|
+
coreSkipped,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @typedef {{w: number[], mu: number[], sd: number[]}} SearcherWeights
|
|
96
|
+
* @typedef {object} SearchData
|
|
97
|
+
* @property {Record<string, {what?: string, synonyms?: string[], props?: string[]}>} cards
|
|
98
|
+
* Each Core component's and hook's source-built card: what it is, the words
|
|
99
|
+
* people type for it, its main props.
|
|
100
|
+
* @property {import('../../foundation/route/route.mjs').ClassifierWeights} router The root classifier's weights.
|
|
101
|
+
* @property {SearcherWeights} main The main searcher's weights.
|
|
102
|
+
* @property {SearcherWeights} doc The doc searcher's weights.
|
|
103
|
+
* @property {SearcherWeights} template The template searcher's weights.
|
|
104
|
+
* @typedef {Awaited<ReturnType<typeof gatherCandidates>> & {data: SearchData}} SearchCatalog
|
|
105
|
+
*/
|
|
106
|
+
|
|
107
|
+
/** @type {SearchData | null} */
|
|
108
|
+
let searchData = null;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The search tree's static data, read once: the entry cards and each node's
|
|
112
|
+
* weights file.
|
|
113
|
+
*
|
|
114
|
+
* These files are static, reviewed data that only order matches inside a score
|
|
115
|
+
* band; ORDERING.md beside them says what each holds and how
|
|
116
|
+
* they are refreshed.
|
|
117
|
+
* @returns {SearchData}
|
|
118
|
+
*/
|
|
119
|
+
export function loadSearchData() {
|
|
120
|
+
if (!searchData) {
|
|
121
|
+
/** @param {string} file */
|
|
122
|
+
const read = file =>
|
|
123
|
+
JSON.parse(readFileSync(new URL(file, import.meta.url), 'utf8'));
|
|
124
|
+
searchData = {
|
|
125
|
+
cards: read('./_cards.json'),
|
|
126
|
+
router: read('./tree.weights.json'),
|
|
127
|
+
main: read('./_main-searcher.weights.json'),
|
|
128
|
+
doc: read('./_doc-searcher.weights.json'),
|
|
129
|
+
template: read('./_template-searcher.weights.json'),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return searchData;
|
|
133
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
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
|
+
* Score a single candidate against the search term across name, keywords,
|
|
6
|
+
* and prose signals. Returns the best (highest) score plus a human reason,
|
|
7
|
+
* or null if nothing matched above the floor.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} term - Lowercased search term.
|
|
10
|
+
* @param {object} candidate
|
|
11
|
+
* @param {string} candidate.name - Primary identifier (component/hook name, topic, template name).
|
|
12
|
+
* @param {string[]} [candidate.aliases] - Other names, scored like the name (a theme's display name).
|
|
13
|
+
* @param {string} [candidate.domain] - A component, hook, or template name
|
|
14
|
+
* also matches typed as words: `command palette` is CommandPalette.
|
|
15
|
+
* @param {string[]} [candidate.keywords] - Authored intent (componentsUsed, category words).
|
|
16
|
+
* @param {string[]} [candidate.weakKeywords] - Derived signal (components a page renders).
|
|
17
|
+
* @param {string} [candidate.description]
|
|
18
|
+
* @param {string[]} [candidate.prose] - Extra free-text blobs (doc section text, best practices).
|
|
19
|
+
* @param {string[]} [candidate.guidance] - Usage guidance (features, best practices) — scored a tier below description.
|
|
20
|
+
* @param {{fuzzy?: boolean, joins?: boolean}} [opts] - `fuzzy`: allow edit-distance (typo) matches. Default true; multi-word queries pass false. `joins`: read a spaced query joined into a name or keyword (default true); off when the query names an entry outright.
|
|
21
|
+
* @returns {{score: number, reason: string, alias?: 'partial' | 'keyword'} | null}
|
|
22
|
+
* `alias` marks a hit that read the query's words joined.
|
|
23
|
+
*/
|
|
24
|
+
export function scoreCandidate(term: string, { name, aliases, domain, keywords, weakKeywords, description, prose, guidance, }: {
|
|
25
|
+
name: string;
|
|
26
|
+
aliases?: string[] | undefined;
|
|
27
|
+
domain?: string | undefined;
|
|
28
|
+
keywords?: string[] | undefined;
|
|
29
|
+
weakKeywords?: string[] | undefined;
|
|
30
|
+
description?: string | undefined;
|
|
31
|
+
prose?: string[] | undefined;
|
|
32
|
+
guidance?: string[] | undefined;
|
|
33
|
+
}, { fuzzy, joins }?: {
|
|
34
|
+
fuzzy?: boolean;
|
|
35
|
+
joins?: boolean;
|
|
36
|
+
}): {
|
|
37
|
+
score: number;
|
|
38
|
+
reason: string;
|
|
39
|
+
alias?: "partial" | "keyword";
|
|
40
|
+
} | null;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file One search term's score against one candidate: name, keyword, prose,
|
|
5
|
+
* and typo signals.
|
|
6
|
+
*
|
|
7
|
+
* Scoring is keyword + fuzzy ranking (NOT semantic / embeddings — that is a
|
|
8
|
+
* deliberate future follow-up). It reuses the same signal weighting as the
|
|
9
|
+
* component fuzzy resolver in lib/string-utils.mjs:
|
|
10
|
+
*
|
|
11
|
+
* 100 exact name match
|
|
12
|
+
* 95 name is the term's plural or stem form ("buttons" -> Button)
|
|
13
|
+
* 90 exact keyword match
|
|
14
|
+
* 88 keyword is the term's plural or stem form
|
|
15
|
+
* 80 name Levenshtein distance 1 (one-word lookups, words of 5+ letters)
|
|
16
|
+
* 70 keyword word prefix / distance 1 (distance: one-word lookups, 5+ letters)
|
|
17
|
+
* 60 name word prefix (>=4 chars, >=50% coverage)
|
|
18
|
+
* 60 exact weak-keyword match
|
|
19
|
+
* 50 description / prose mentions the term
|
|
20
|
+
* 45 usage guidance mentions the term
|
|
21
|
+
* 40 name Levenshtein distance 2 (one-word lookups, 8+ letters)
|
|
22
|
+
* 40 weak-keyword word prefix
|
|
23
|
+
* 30 keyword Levenshtein distance 2 (one-word lookups, 8+ letters)
|
|
24
|
+
* 20 name Levenshtein distance 3 (one-word lookups, 11+ letters)
|
|
25
|
+
*
|
|
26
|
+
* Name + keyword signals always outweigh description/prose, so an exact match
|
|
27
|
+
* sorts above an incidental mention.
|
|
28
|
+
*
|
|
29
|
+
* A term matches inside a name or keyword only at the start of one of its
|
|
30
|
+
* words: "dash" finds "dashboard" and "input" finds "TextInput", but "file"
|
|
31
|
+
* does not find "profile". Edit distance is typo tolerance, so it applies only
|
|
32
|
+
* to a one-word lookup, where a typo is the likely explanation, and only to
|
|
33
|
+
* words long enough that one edit rarely makes another real word. In a
|
|
34
|
+
* sentence, a near miss is usually a different word: "site" is not "side",
|
|
35
|
+
* "cable" is not "table".
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import {levenshteinDistance} from '../../foundation/text/string-utils.mjs';
|
|
39
|
+
import {
|
|
40
|
+
stem,
|
|
41
|
+
wordForms,
|
|
42
|
+
sameWord,
|
|
43
|
+
startsAWordOf,
|
|
44
|
+
joined,
|
|
45
|
+
isTypo,
|
|
46
|
+
} from './_words.mjs';
|
|
47
|
+
import {pluralOf} from './_query.mjs';
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Score a single candidate against the search term across name, keywords,
|
|
51
|
+
* and prose signals. Returns the best (highest) score plus a human reason,
|
|
52
|
+
* or null if nothing matched above the floor.
|
|
53
|
+
*
|
|
54
|
+
* @param {string} term - Lowercased search term.
|
|
55
|
+
* @param {object} candidate
|
|
56
|
+
* @param {string} candidate.name - Primary identifier (component/hook name, topic, template name).
|
|
57
|
+
* @param {string[]} [candidate.aliases] - Other names, scored like the name (a theme's display name).
|
|
58
|
+
* @param {string} [candidate.domain] - A component, hook, or template name
|
|
59
|
+
* also matches typed as words: `command palette` is CommandPalette.
|
|
60
|
+
* @param {string[]} [candidate.keywords] - Authored intent (componentsUsed, category words).
|
|
61
|
+
* @param {string[]} [candidate.weakKeywords] - Derived signal (components a page renders).
|
|
62
|
+
* @param {string} [candidate.description]
|
|
63
|
+
* @param {string[]} [candidate.prose] - Extra free-text blobs (doc section text, best practices).
|
|
64
|
+
* @param {string[]} [candidate.guidance] - Usage guidance (features, best practices) — scored a tier below description.
|
|
65
|
+
* @param {{fuzzy?: boolean, joins?: boolean}} [opts] - `fuzzy`: allow edit-distance (typo) matches. Default true; multi-word queries pass false. `joins`: read a spaced query joined into a name or keyword (default true); off when the query names an entry outright.
|
|
66
|
+
* @returns {{score: number, reason: string, alias?: 'partial' | 'keyword'} | null}
|
|
67
|
+
* `alias` marks a hit that read the query's words joined.
|
|
68
|
+
*/
|
|
69
|
+
export function scoreCandidate(
|
|
70
|
+
term,
|
|
71
|
+
{
|
|
72
|
+
name,
|
|
73
|
+
aliases = [],
|
|
74
|
+
domain,
|
|
75
|
+
keywords = [],
|
|
76
|
+
weakKeywords = [],
|
|
77
|
+
description = '',
|
|
78
|
+
prose = [],
|
|
79
|
+
guidance = [],
|
|
80
|
+
},
|
|
81
|
+
{fuzzy = true, joins = true} = {},
|
|
82
|
+
) {
|
|
83
|
+
let best = 0;
|
|
84
|
+
let reason = '';
|
|
85
|
+
/** @type {'partial' | 'keyword' | false} */
|
|
86
|
+
let alias = false;
|
|
87
|
+
/**
|
|
88
|
+
* @param {number} score
|
|
89
|
+
* @param {string} why
|
|
90
|
+
* @param {'partial' | 'keyword' | false} [byAlias] - the hit reads the
|
|
91
|
+
* query's words joined, into part of a name or into a keyword
|
|
92
|
+
*/
|
|
93
|
+
const consider = (score, why, byAlias = false) => {
|
|
94
|
+
if (score > best) {
|
|
95
|
+
best = score;
|
|
96
|
+
reason = why;
|
|
97
|
+
alias = byAlias;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// ── Name signals ────────────────────────────────────────────────
|
|
102
|
+
// An alias is a name too: a theme answers to its display name as well as
|
|
103
|
+
// its slug.
|
|
104
|
+
for (const candidateName of [name, ...aliases.filter(Boolean)]) {
|
|
105
|
+
const nameLower = candidateName.toLowerCase();
|
|
106
|
+
// A placed guide's name is its route, and the route's last segment is its
|
|
107
|
+
// name too, as a flat topic's is: `codemods` is cli/integrations/codemods.
|
|
108
|
+
const leafLower = nameLower.slice(nameLower.lastIndexOf('/') + 1);
|
|
109
|
+
|
|
110
|
+
// A plural of the name is the name: `integration` is the `integrations`
|
|
111
|
+
// guides, `tab` the `tabs` doc.
|
|
112
|
+
// A component, hook, or template name typed as words is its name:
|
|
113
|
+
// `command palette` is CommandPalette. A doc's name is a route or key,
|
|
114
|
+
// matched as written.
|
|
115
|
+
const spelled =
|
|
116
|
+
domain !== 'doc' &&
|
|
117
|
+
!/[\s_-]/.test(nameLower) &&
|
|
118
|
+
nameLower === term.replace(/\s+/g, '');
|
|
119
|
+
if (nameLower === term || leafLower === term || spelled) {
|
|
120
|
+
consider(100, 'exact name');
|
|
121
|
+
} else if (
|
|
122
|
+
joins &&
|
|
123
|
+
(domain === 'component' || domain === 'hook') &&
|
|
124
|
+
/\s/.test(term) &&
|
|
125
|
+
nameLower.startsWith(term.replace(/\s+/g, '')) &&
|
|
126
|
+
term.replace(/\s+/g, '').length / nameLower.length >= 0.75
|
|
127
|
+
) {
|
|
128
|
+
// Most of the name typed as words, cut short: `complex select` is
|
|
129
|
+
// ComplexSelector, ahead of a one-word keyword elsewhere.
|
|
130
|
+
consider(95, `name "${candidateName}" spelled`, 'partial');
|
|
131
|
+
} else if (pluralOf(nameLower, term) || pluralOf(term, nameLower)) {
|
|
132
|
+
// One point under the exact spelling, so the doc named `tokens` still
|
|
133
|
+
// outranks the Token component for `tokens`.
|
|
134
|
+
consider(99, 'plural of the name');
|
|
135
|
+
} else {
|
|
136
|
+
if (sameWord(term, nameLower)) consider(95, `name "${candidateName}"`);
|
|
137
|
+
// The term is a word of the name, or starts one: "input" in TextInput.
|
|
138
|
+
else if (startsAWordOf(term, candidateName)) {
|
|
139
|
+
consider(60, `name contains "${term}"`);
|
|
140
|
+
}
|
|
141
|
+
if (fuzzy) {
|
|
142
|
+
const dist = levenshteinDistance(term, nameLower);
|
|
143
|
+
if (isTypo(term, nameLower, dist)) {
|
|
144
|
+
consider(
|
|
145
|
+
dist === 1 ? 80 : dist === 2 ? 40 : 20,
|
|
146
|
+
`similar name (distance ${dist})`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ── Keyword signals ─────────────────────────────────────────────
|
|
154
|
+
const termJoined = joined(term);
|
|
155
|
+
for (const kw of keywords) {
|
|
156
|
+
const kwLower = String(kw).toLowerCase();
|
|
157
|
+
// A one-word keyword typed as separate words is the keyword: `number
|
|
158
|
+
// field` is `numberfield`. Only a
|
|
159
|
+
// lowercase keyword, one an author declares for a reader to type: a
|
|
160
|
+
// capitalized one names another entry (a block's `componentsUsed`). A
|
|
161
|
+
// doc's keywords include code terms, matched as written.
|
|
162
|
+
if (kwLower === term) {
|
|
163
|
+
consider(90, `keyword "${kw}"`);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (
|
|
167
|
+
joins &&
|
|
168
|
+
domain !== 'doc' &&
|
|
169
|
+
/\s/.test(term) &&
|
|
170
|
+
String(kw) === termJoined
|
|
171
|
+
) {
|
|
172
|
+
consider(90, `keyword "${kw}"`, 'keyword');
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (sameWord(term, kwLower)) {
|
|
176
|
+
consider(88, `keyword "${kw}"`);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (startsAWordOf(term, kw)) consider(70, `keyword "${kw}"`);
|
|
180
|
+
if (fuzzy) {
|
|
181
|
+
const dist = levenshteinDistance(term, kwLower);
|
|
182
|
+
if (isTypo(term, kwLower, dist) && dist <= 2) {
|
|
183
|
+
consider(dist === 1 ? 70 : 30, `keyword "${kw}" (distance ${dist})`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ── Weak keyword signals (derived, not authored) ─────────────────
|
|
189
|
+
// Capped at 60 so one incidental component match cannot clear the
|
|
190
|
+
// confident-match gate on its own; it still counts toward multi-term
|
|
191
|
+
// coverage, which is where "this page renders that" is real evidence.
|
|
192
|
+
// No Levenshtein tier — fuzzy matching a derived signal is pure noise.
|
|
193
|
+
for (const kw of weakKeywords) {
|
|
194
|
+
const kwLower = String(kw).toLowerCase();
|
|
195
|
+
if (kwLower === term || sameWord(term, kwLower)) {
|
|
196
|
+
consider(60, `renders ${kw}`);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (startsAWordOf(term, kw)) consider(40, `renders ${kw}`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ── Prose / description / guidance signals (stem-tolerant whole word) ──
|
|
203
|
+
// Match the term's stem as a whole word, tolerating plural/gerund suffixes
|
|
204
|
+
// so "chart" matches "charts" and "filter" matches "filtering".
|
|
205
|
+
if (term.length >= 3) {
|
|
206
|
+
// Any form of the term, with any common suffix: `spacing` finds
|
|
207
|
+
// "space" and "spaces", `tables` finds "table".
|
|
208
|
+
const roots = [...new Set([stem(term), ...wordForms(term)])]
|
|
209
|
+
.filter(f => f.length >= 3)
|
|
210
|
+
.map(f => f.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
211
|
+
const re = new RegExp(`\\b(?:${roots.join('|')})(s|es|ing|ed|ies)?\\b`);
|
|
212
|
+
if (description && re.test(description.toLowerCase())) {
|
|
213
|
+
consider(50, `description mentions "${term}"`);
|
|
214
|
+
} else {
|
|
215
|
+
let matchedProse = false;
|
|
216
|
+
for (const blob of prose) {
|
|
217
|
+
if (blob && re.test(String(blob).toLowerCase())) {
|
|
218
|
+
consider(50, `docs mention "${term}"`);
|
|
219
|
+
matchedProse = true;
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// A tier below prose: guidance is what a component says about USING it,
|
|
224
|
+
// so the term appearing there is weaker evidence than the component's own
|
|
225
|
+
// summary. Only consulted when nothing stronger matched.
|
|
226
|
+
if (!matchedProse) {
|
|
227
|
+
for (const blob of guidance) {
|
|
228
|
+
if (blob && re.test(String(blob).toLowerCase())) {
|
|
229
|
+
consider(45, `guidance mentions "${term}"`);
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return best > 0 ? {score: best, reason, ...(alias ? {alias} : {})} : null;
|
|
238
|
+
}
|