@astryxdesign/cli 0.7.2 → 0.7.3-canary.02f5fe4
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 +104 -71
- package/api/build/_adapter.d.mts +58 -0
- package/api/build/_adapter.mjs +127 -3
- package/api/build/blocks/blocks.d.mts +92 -0
- package/api/build/blocks/blocks.mjs +528 -0
- package/api/build/blocks/blocks.test.mjs +238 -0
- package/api/build/blocks/blocks.weights.json +1 -0
- package/api/build/build.mjs +25 -6
- package/api/build/build.type.mjs +1 -1
- package/api/build/kit/answer.d.mts +18 -0
- package/api/build/kit/answer.mjs +195 -0
- package/api/build/kit/answer.test.mjs +108 -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 +79 -0
- package/api/build/kit/signals.mjs +295 -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 +53 -0
- package/api/build/tree.test.mjs +185 -0
- package/api/build/tree.weights.json +1 -0
- package/api/component/_adapter.mjs +39 -5
- package/api/component/component-replacement.test.mjs +1 -0
- package/api/component/component.mjs +17 -3
- package/api/component/component.test.mjs +15 -0
- package/api/component/doc-links.test.mjs +2 -2
- package/api/discover/_adapter.d.mts +1 -2
- package/api/discover/_adapter.mjs +42 -17
- package/api/discover/_adapter.test.mjs +42 -0
- package/api/discover/discover.mjs +0 -1
- package/api/docs/_adapter.d.mts +2 -2
- package/api/docs/_adapter.mjs +82 -23
- package/api/docs/docs-homes.test.mjs +127 -0
- package/api/docs/docs.doc.mjs +3 -3
- package/api/docs/docs.test.mjs +35 -19
- package/api/docs/integration-tree.test.mjs +20 -20
- package/api/docs/integrationDocs.test.mjs +1 -1
- package/api/docs/namespace-section.test.mjs +6 -6
- package/api/docs/reference-blocks.test.mjs +2 -2
- package/api/doctor/_adapter.d.mts +33 -13
- package/api/doctor/_adapter.mjs +55 -25
- package/api/doctor/doctor.d.mts +23 -5
- package/api/doctor/doctor.mjs +183 -40
- package/api/doctor/doctor.test.mjs +95 -12
- package/api/hook/hook.mjs +1 -1
- 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/_adapter.d.mts +8 -0
- package/api/integration/_adapter.mjs +21 -1
- package/api/integration/authoring-checks.mjs +83 -9
- package/api/integration/authoring-checks.test.mjs +220 -0
- 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-fixes.test.mjs +6 -5
- package/api/integration/validate-integration-gaps.test.mjs +1975 -0
- package/api/integration/validate-integration.d.mts +15 -2
- package/api/integration/validate-integration.mjs +298 -39
- package/api/integration/validate-integration.type.d.mts +90 -1
- package/api/integration/validate-integration.type.mjs +67 -2
- package/api/integration/validateIntegration.doc.mjs +1 -1
- package/api/search/ORDERING.md +28 -0
- package/api/search/_adapter.d.mts +82 -2
- package/api/search/_adapter.mjs +157 -10
- package/api/search/_adapter.test.mjs +173 -0
- 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 +63 -0
- package/api/search/_entries.mjs +232 -0
- package/api/search/_entries.test.mjs +48 -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 +8 -6
- package/api/search/search.mjs +56 -2482
- package/api/search/search.test.mjs +14 -14
- 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/template/template.mjs +5 -1
- 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/author-a-theme.doc.mjs +2 -2
- package/assets/docs/is-my-code-good.doc.mjs +287 -0
- package/assets/docs/tree/add-a-component.doc.mjs +1 -1
- package/assets/docs/tree/add-a-theme.doc.mjs +1 -1
- package/assets/docs/tree/add-a-topic.doc.mjs +2 -2
- package/assets/docs/tree/agent-guidance.doc.mjs +1 -1
- package/assets/docs/tree/api.doc.mjs +6 -0
- package/assets/docs/tree/{integrations.doc.mjs → authoring.doc.mjs} +15 -6
- package/assets/docs/tree/block-template.doc.mjs +1 -1
- package/assets/docs/tree/build-the-template.doc.mjs +1 -1
- package/assets/docs/tree/building-blocks.doc.mjs +2 -2
- package/assets/docs/tree/check-the-props.doc.mjs +1 -1
- package/assets/docs/tree/check-your-docs.doc.mjs +2 -2
- package/assets/docs/tree/checks.doc.mjs +3 -3
- package/assets/docs/tree/ci-and-containers.doc.mjs +13 -0
- package/assets/docs/tree/cli-command.doc.mjs +1 -1
- package/assets/docs/tree/cli.doc.mjs +1 -1
- package/assets/docs/tree/codemods.doc.mjs +1 -1
- package/assets/docs/tree/component-family.doc.mjs +1 -1
- package/assets/docs/tree/component-imports.doc.mjs +1 -1
- package/assets/docs/tree/components.doc.mjs +1 -1
- package/assets/docs/tree/configuration.doc.mjs +1 -1
- package/assets/docs/tree/debug-and-gap-reports.doc.mjs +1 -1
- package/assets/docs/tree/define-the-theme.doc.mjs +1 -1
- package/assets/docs/tree/describe-the-component.doc.mjs +1 -1
- package/assets/docs/tree/docs.doc.mjs +1 -1
- package/assets/docs/tree/document-the-template.doc.mjs +1 -1
- package/assets/docs/tree/document-the-theme.doc.mjs +1 -1
- package/assets/docs/tree/export-template-assets.doc.mjs +1 -1
- package/assets/docs/tree/extend-or-replace.doc.mjs +1 -1
- package/assets/docs/{authoring.doc.mjs → tree/field-reference.doc.mjs} +4 -3
- package/assets/docs/tree/fonts-and-assets.doc.mjs +1 -1
- package/assets/docs/tree/generate-a-palette.doc.mjs +1 -1
- package/assets/docs/tree/global-integrations.doc.mjs +2 -2
- package/assets/docs/tree/grade-template-with-agent.doc.mjs +4 -4
- package/assets/docs/tree/help.doc.mjs +2 -2
- package/assets/docs/tree/host-entry.doc.mjs +128 -0
- package/assets/docs/tree/links.doc.mjs +1 -1
- package/assets/docs/tree/package-and-test.doc.mjs +1 -1
- package/assets/docs/tree/page-template.doc.mjs +1 -1
- package/assets/docs/tree/publishing.doc.mjs +1 -1
- package/assets/docs/tree/quick-start.doc.mjs +1 -1
- package/assets/docs/tree/replace-a-core-component.doc.mjs +1 -1
- package/assets/docs/tree/replace-a-core-template.doc.mjs +1 -1
- package/assets/docs/tree/sections-and-placement.doc.mjs +1 -1
- package/assets/docs/tree/see-it-in-an-app.doc.mjs +1 -1
- package/assets/docs/tree/ship.doc.mjs +2 -2
- package/assets/docs/tree/short-and-findable.doc.mjs +1 -1
- package/assets/docs/tree/single-component.doc.mjs +1 -1
- package/assets/docs/tree/start-a-template.doc.mjs +1 -1
- package/assets/docs/tree/subcomponent.doc.mjs +1 -1
- package/assets/docs/tree/template-assets.doc.mjs +1 -1
- package/assets/docs/tree/template-doc-overview.doc.mjs +2 -2
- package/assets/docs/tree/template-fonts.doc.mjs +1 -1
- package/assets/docs/tree/template-grading-rubric.doc.mjs +17 -38
- package/assets/docs/tree/template-icons.doc.mjs +1 -1
- package/assets/docs/tree/template-images-media.doc.mjs +1 -1
- package/assets/docs/tree/template-styles.doc.mjs +1 -1
- package/assets/docs/tree/templates.doc.mjs +1 -1
- package/assets/docs/tree/test-in-an-app.doc.mjs +1 -1
- package/assets/docs/tree/test-template-in-app.doc.mjs +1 -1
- package/assets/docs/tree/themes.doc.mjs +1 -1
- package/assets/docs/tree/tokens-data.doc.mjs +2 -2
- package/assets/docs/tree/troubleshooting.doc.mjs +1 -1
- package/assets/docs/tree/upgrading.doc.mjs +1 -1
- package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +1 -1
- package/assets/docs/tree/verify-packed-template.doc.mjs +1 -1
- package/assets/docs/tree/versioning.doc.mjs +5 -1
- package/assets/docs/tree/write-good-templates.doc.mjs +1 -1
- package/assets/docs/tree/write-the-template-file.doc.mjs +1 -1
- package/assets/docs/use-integrations.doc.mjs +120 -0
- 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/authoring/doctypes/schema/schema.doc.mjs +1 -1
- package/authoring/integration/integration.doc.mjs +1 -1
- package/authoring/integration/type.ts +1 -1
- package/clients/cli/bin/astryx.mjs +7 -78
- package/clients/cli/command-result-coverage.test.mjs +6 -5
- package/clients/cli/commands/build-theme.color-scheme.test.mjs +1 -1
- package/clients/cli/commands/{component/index.mjs → component.mjs} +37 -37
- package/clients/cli/commands/component.test.mjs +6 -6
- package/clients/cli/commands/docs.doc.mjs +2 -2
- package/clients/cli/commands/docs.mjs +1 -1
- package/clients/cli/commands/docs.test.mjs +14 -14
- package/clients/cli/commands/doctor-integration.test.mjs +49 -1
- package/clients/cli/commands/doctor.doc.mjs +1 -1
- package/clients/cli/commands/doctor.mjs +40 -0
- package/clients/cli/commands/doctor.test.mjs +65 -0
- package/clients/cli/commands/{hook/index.mjs → hook.mjs} +22 -22
- package/clients/cli/commands/import-hint-correctness.test.mjs +1 -1
- 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/integration-check.test.mjs +48 -0
- package/clients/cli/commands/integration.doc.mjs +1 -1
- package/clients/cli/commands/manifest.doc.mjs +1 -1
- package/clients/cli/commands/manifest.mjs +67 -0
- package/clients/cli/commands/outside-project.test.mjs +101 -1
- package/clients/cli/commands/postinstall.doc.mjs +23 -0
- package/clients/cli/commands/postinstall.mjs +31 -0
- package/clients/cli/commands/setup-nudge.test.mjs +78 -0
- package/clients/cli/commands/{build-theme.mjs → theme.mjs} +1 -1
- 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 +54 -94
- package/clients/cli/lib/component-format.mjs +1 -1
- package/clients/cli/lib/define-command.mjs +12 -7
- package/clients/cli/lib/exit-codes.test.mjs +5 -0
- package/clients/cli/lib/manifest.test.mjs +12 -0
- package/clients/cli/lib/welcome-box.mjs +50 -0
- package/clients/cli/run.mjs +108 -0
- package/foundation/agent-docs/agent-doc-state.d.mts +32 -7
- package/foundation/agent-docs/agent-doc-state.mjs +58 -9
- package/foundation/agent-docs/agent-docs.d.mts +100 -12
- package/foundation/agent-docs/agent-docs.mjs +1017 -105
- package/foundation/agent-docs/agent-docs.path-safety.test.mjs +1239 -17
- package/foundation/agent-docs/agent-docs.test.mjs +683 -39
- package/foundation/agent-docs/computer-agent-text.d.mts +39 -2
- package/foundation/agent-docs/computer-agent-text.mjs +135 -8
- package/foundation/agent-docs/computer-agent-text.test.mjs +99 -2
- package/foundation/config/project.mjs +20 -22
- package/foundation/config/project.test.mjs +56 -0
- package/foundation/discovery/authoring-self-docs.d.mts +7 -0
- package/foundation/discovery/authoring-self-docs.mjs +16 -4
- package/foundation/discovery/authoring-self-docs.test.mjs +15 -3
- package/foundation/discovery/authoring-surface.d.mts +1 -1
- package/foundation/discovery/authoring-surface.mjs +2 -2
- package/foundation/discovery/authoring-surface.test.mjs +1 -1
- package/foundation/discovery/cli-self-docs.d.mts +2 -1
- package/foundation/discovery/cli-self-docs.mjs +4 -3
- package/foundation/discovery/cli-self-docs.test.mjs +2 -2
- 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/runner-allowlist.d.mts +2 -4
- package/foundation/discovery/runner-allowlist.mjs +2 -4
- package/foundation/discovery/runner-commands.d.mts +77 -0
- package/foundation/discovery/runner-commands.mjs +294 -0
- package/foundation/discovery/runner-commands.test.mjs +128 -0
- package/foundation/discovery/stray-pointers.test.mjs +1 -1
- 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 +33 -9
- package/foundation/doc-compiler/read.mjs +59 -1
- package/foundation/doc-compiler/read.test.mjs +11 -2
- package/foundation/doc-compiler/split-topic-keywords.test.mjs +3 -2
- package/foundation/doc-compiler/tree.d.mts +32 -6
- package/foundation/doc-compiler/tree.mjs +77 -43
- package/foundation/doc-compiler/tree.test.mjs +7 -7
- 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/fs/read-record.d.mts +18 -0
- package/foundation/fs/read-record.mjs +131 -0
- package/foundation/fs/read-record.test.mjs +77 -0
- package/foundation/integrations/autolink.d.mts +2 -1
- package/foundation/integrations/autolink.mjs +4 -13
- package/foundation/integrations/cli-peer-version.d.mts +36 -0
- package/foundation/integrations/cli-peer-version.mjs +132 -0
- package/foundation/integrations/cli-peer-version.test.mjs +354 -0
- 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/exports-subpaths.d.mts +57 -0
- package/foundation/integrations/exports-subpaths.mjs +113 -0
- package/foundation/integrations/integrations.d.mts +27 -1
- package/foundation/integrations/integrations.mjs +61 -2
- package/foundation/integrations/public-imports.d.mts +118 -0
- package/foundation/integrations/public-imports.mjs +1769 -0
- package/foundation/integrations/public-imports.test.mjs +1793 -0
- package/foundation/integrations/validate-contributions.d.mts +115 -0
- package/foundation/integrations/validate-contributions.mjs +1168 -13
- package/foundation/release/cache.test.mjs +24 -21
- package/foundation/release/global-state.d.mts +8 -4
- package/foundation/release/global-state.mjs +4 -2
- package/foundation/release/handoff.d.mts +23 -4
- package/foundation/release/handoff.mjs +175 -8
- package/foundation/release/notices.d.mts +62 -8
- package/foundation/release/notices.mjs +136 -25
- package/foundation/release/notices.test.mjs +21 -0
- 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/foundation/text/string-utils.d.mts +2 -1
- package/foundation/text/string-utils.mjs +16 -5
- package/foundation/text/suggest-order.d.mts +72 -0
- package/foundation/text/suggest-order.mjs +363 -0
- package/foundation/text/suggest-order.test.mjs +259 -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
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Search's template and theme candidates. Imported only by the adapter.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {CORE_PACKAGE} from '../../foundation/discovery/component-discovery.mjs';
|
|
8
|
+
import {discoverTemplates, extractComponents} from '../template/template.mjs';
|
|
9
|
+
import {listAvailableThemes} from '../theme/_adapter.mjs';
|
|
10
|
+
import {templateLookupIds} from '../../foundation/discovery/template-adapter.mjs';
|
|
11
|
+
|
|
12
|
+
/** @typedef {import('./_lexical.mjs').Candidate} Candidate */
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Build template candidates (page + block) from the template discovery API.
|
|
16
|
+
* @param {string} cwd
|
|
17
|
+
* @returns {Promise<Candidate[]>}
|
|
18
|
+
*/
|
|
19
|
+
export async function gatherTemplates(cwd) {
|
|
20
|
+
let templates;
|
|
21
|
+
try {
|
|
22
|
+
templates = await discoverTemplates(cwd);
|
|
23
|
+
} catch {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
return templates.map(t => {
|
|
27
|
+
// A replacement's target is its canonical unqualified lookup id. Keep the
|
|
28
|
+
// integration-owned id as a keyword and response label, but score and print
|
|
29
|
+
// commands against the id that `template()` resolves back to this entry.
|
|
30
|
+
// This matters for replacement chains: one replacement's own id can be the
|
|
31
|
+
// target of another, so using that shadowed id as the command would select
|
|
32
|
+
// the other template.
|
|
33
|
+
const commandName = t.replaces ?? t.dirName;
|
|
34
|
+
// Blocks ship an authored componentsUsed; page templates don't, so derive
|
|
35
|
+
// them from the source. Category words (e.g. "Dashboard - Analytics") are
|
|
36
|
+
// strong intent signal for pages, which otherwise only index on name +
|
|
37
|
+
// description.
|
|
38
|
+
//
|
|
39
|
+
// Authored signal and derived signal are kept apart: componentsUsed and
|
|
40
|
+
// category words are a deliberate statement of what the template is for,
|
|
41
|
+
// while scraped JSX tags only say what it happens to render. See the
|
|
42
|
+
// scoring table at the top of this file for why the derived set is capped.
|
|
43
|
+
const keywords = Array.isArray(t.componentsUsed)
|
|
44
|
+
? [...t.componentsUsed]
|
|
45
|
+
: [];
|
|
46
|
+
keywords.push(...templateLookupIds(t).filter(id => id !== commandName));
|
|
47
|
+
/** @type {string[]} */
|
|
48
|
+
let weakKeywords = [];
|
|
49
|
+
if (t.type === 'page') {
|
|
50
|
+
if (t.filePath) {
|
|
51
|
+
try {
|
|
52
|
+
weakKeywords = extractComponents(t.filePath);
|
|
53
|
+
} catch {
|
|
54
|
+
// Best-effort: skip keyword enrichment if the source can't be read.
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (t.category)
|
|
58
|
+
keywords.push(...t.category.split(/[^A-Za-z0-9]+/).filter(Boolean));
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
domain: 'template',
|
|
62
|
+
name: commandName,
|
|
63
|
+
keywords,
|
|
64
|
+
weakKeywords,
|
|
65
|
+
description: t.description || '',
|
|
66
|
+
// A template's own keywords name the many ideas it serves, so they score
|
|
67
|
+
// as its prose does, not as a name: one of them matching one word of a
|
|
68
|
+
// short idea is not a direct match.
|
|
69
|
+
prose: t.keywords ?? [],
|
|
70
|
+
_displayName: t.name,
|
|
71
|
+
_package: t.package ?? CORE_PACKAGE,
|
|
72
|
+
_kind: t.type, // 'page' | 'block'
|
|
73
|
+
_resultName: t.dirName,
|
|
74
|
+
_commandName: commandName,
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Build theme candidates from bundled and integration-provided themes. Without
|
|
81
|
+
* this, an integration's themes are invisible to `search` even though
|
|
82
|
+
* `theme list` and `discover` already resolve them.
|
|
83
|
+
*
|
|
84
|
+
* A theme's slug and display name are its names, and its description is prose
|
|
85
|
+
* (`spec:AST-050/FR14`). A theme declares no keywords, so a word it shares with
|
|
86
|
+
* the query only through its description is a description mention: read as a
|
|
87
|
+
* keyword, every word of the description would outrank the components and docs
|
|
88
|
+
* that declare that word.
|
|
89
|
+
* @param {string} cwd
|
|
90
|
+
* @returns {Promise<Candidate[]>}
|
|
91
|
+
*/
|
|
92
|
+
export async function gatherThemes(cwd) {
|
|
93
|
+
let themes;
|
|
94
|
+
try {
|
|
95
|
+
themes = await listAvailableThemes(cwd);
|
|
96
|
+
} catch {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
return themes.map(t => ({
|
|
100
|
+
domain: 'theme',
|
|
101
|
+
name: t.slug,
|
|
102
|
+
aliases: t.displayName ? [t.displayName] : [],
|
|
103
|
+
description: t.description || '',
|
|
104
|
+
_displayName: t.displayName,
|
|
105
|
+
_package: t.package,
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
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
|
+
* A search candidate gathered from one content domain. Extra underscore-
|
|
6
|
+
* prefixed fields carry domain-specific payload used only by {@link toResult}.
|
|
7
|
+
* @typedef {object} Candidate
|
|
8
|
+
* @property {'component'|'hook'|'doc'|'template'|'theme'} domain
|
|
9
|
+
* @property {string} name
|
|
10
|
+
* @property {string[]} [aliases] - Other names the candidate answers to, scored
|
|
11
|
+
* with the same name signals: a theme's display name.
|
|
12
|
+
* @property {string[]} [keywords]
|
|
13
|
+
* @property {string[]} [_declared] - A doc's own declared keywords: what its
|
|
14
|
+
* author lists in `keywords`, without the code terms and table cells its
|
|
15
|
+
* other keywords add.
|
|
16
|
+
* @property {string[]} [weakKeywords]
|
|
17
|
+
* @property {string} [description]
|
|
18
|
+
* @property {string[]} [prose]
|
|
19
|
+
* @property {string[]} [guidance]
|
|
20
|
+
* @property {string[]} [titles] - A doc's title and the headings inside it:
|
|
21
|
+
* the lines a reader scans to pick it. The whole query standing in one of
|
|
22
|
+
* them, or one of them standing whole in the query, is a top-tier match.
|
|
23
|
+
* @property {string} [_import]
|
|
24
|
+
* @property {string} [_title]
|
|
25
|
+
* @property {string} [_topic] - A doc result's topic or docs-tree route.
|
|
26
|
+
* @property {string} [_section] - A doc result's section key, when it is one section.
|
|
27
|
+
* @property {string} [_command] - The command that reads exactly this doc part.
|
|
28
|
+
* @property {string} [_parent] - The command that opens the level above a doc
|
|
29
|
+
* part: its topic's section list, or the docs-tree namespace it sits in.
|
|
30
|
+
* @property {string} [_package] - The npm package that owns the candidate:
|
|
31
|
+
* Core's package for its components, hooks, and templates, the integration's
|
|
32
|
+
* for what it contributed, and for a doc part the package that wrote it.
|
|
33
|
+
* @property {string} [_displayName]
|
|
34
|
+
* @property {'page'|'block'} [_kind]
|
|
35
|
+
* @property {string} [_resultName]
|
|
36
|
+
* @property {string} [_commandName]
|
|
37
|
+
* @property {'file'|'doc'} [_hold] - How an integration component holds its
|
|
38
|
+
* name, for the owner rule.
|
|
39
|
+
* @property {string} [_docPath] - An integration component's doc.
|
|
40
|
+
*/
|
|
41
|
+
/**
|
|
42
|
+
* True when the query is the candidate's whole name or another name of it,
|
|
43
|
+
* typed as is or, for anything but a doc, with its spaces removed. Such a
|
|
44
|
+
* query names that entry outright.
|
|
45
|
+
* @param {Candidate} candidate
|
|
46
|
+
* @param {string} term the query, trimmed and lowercased
|
|
47
|
+
* @returns {boolean}
|
|
48
|
+
*/
|
|
49
|
+
export function namesWhole(candidate: Candidate, term: string): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Today's lexical ranking of a gathered catalog: the search response
|
|
52
|
+
* `searchCore` returns.
|
|
53
|
+
*
|
|
54
|
+
* @param {string} query
|
|
55
|
+
* @param {{limit?: number}} options
|
|
56
|
+
* @param {{all: Candidate[], components: Candidate[], searchedComponents: boolean, outside?: boolean, coreSkipped?: string | null}} catalog
|
|
57
|
+
* @returns {import('./search.type.mjs').SearchResponse}
|
|
58
|
+
*/
|
|
59
|
+
export function rankLexical(query: string, options: {
|
|
60
|
+
limit?: number;
|
|
61
|
+
}, catalog: {
|
|
62
|
+
all: Candidate[];
|
|
63
|
+
components: Candidate[];
|
|
64
|
+
searchedComponents: boolean;
|
|
65
|
+
outside?: boolean;
|
|
66
|
+
coreSkipped?: string | null;
|
|
67
|
+
}): import("./search.type.mjs").SearchResponse;
|
|
68
|
+
/**
|
|
69
|
+
* A search candidate gathered from one content domain. Extra underscore-
|
|
70
|
+
* prefixed fields carry domain-specific payload used only by {@link toResult}.
|
|
71
|
+
*/
|
|
72
|
+
export type Candidate = {
|
|
73
|
+
domain: "component" | "hook" | "doc" | "template" | "theme";
|
|
74
|
+
name: string;
|
|
75
|
+
/**
|
|
76
|
+
* - Other names the candidate answers to, scored
|
|
77
|
+
* with the same name signals: a theme's display name.
|
|
78
|
+
*/
|
|
79
|
+
aliases?: string[] | undefined;
|
|
80
|
+
keywords?: string[] | undefined;
|
|
81
|
+
/**
|
|
82
|
+
* - A doc's own declared keywords: what its
|
|
83
|
+
* author lists in `keywords`, without the code terms and table cells its
|
|
84
|
+
* other keywords add.
|
|
85
|
+
*/
|
|
86
|
+
_declared?: string[] | undefined;
|
|
87
|
+
weakKeywords?: string[] | undefined;
|
|
88
|
+
description?: string | undefined;
|
|
89
|
+
prose?: string[] | undefined;
|
|
90
|
+
guidance?: string[] | undefined;
|
|
91
|
+
/**
|
|
92
|
+
* - A doc's title and the headings inside it:
|
|
93
|
+
* the lines a reader scans to pick it. The whole query standing in one of
|
|
94
|
+
* them, or one of them standing whole in the query, is a top-tier match.
|
|
95
|
+
*/
|
|
96
|
+
titles?: string[] | undefined;
|
|
97
|
+
_import?: string | undefined;
|
|
98
|
+
_title?: string | undefined;
|
|
99
|
+
/**
|
|
100
|
+
* - A doc result's topic or docs-tree route.
|
|
101
|
+
*/
|
|
102
|
+
_topic?: string | undefined;
|
|
103
|
+
/**
|
|
104
|
+
* - A doc result's section key, when it is one section.
|
|
105
|
+
*/
|
|
106
|
+
_section?: string | undefined;
|
|
107
|
+
/**
|
|
108
|
+
* - The command that reads exactly this doc part.
|
|
109
|
+
*/
|
|
110
|
+
_command?: string | undefined;
|
|
111
|
+
/**
|
|
112
|
+
* - The command that opens the level above a doc
|
|
113
|
+
* part: its topic's section list, or the docs-tree namespace it sits in.
|
|
114
|
+
*/
|
|
115
|
+
_parent?: string | undefined;
|
|
116
|
+
/**
|
|
117
|
+
* - The npm package that owns the candidate:
|
|
118
|
+
* Core's package for its components, hooks, and templates, the integration's
|
|
119
|
+
* for what it contributed, and for a doc part the package that wrote it.
|
|
120
|
+
*/
|
|
121
|
+
_package?: string | undefined;
|
|
122
|
+
_displayName?: string | undefined;
|
|
123
|
+
_kind?: "page" | "block" | undefined;
|
|
124
|
+
_resultName?: string | undefined;
|
|
125
|
+
_commandName?: string | undefined;
|
|
126
|
+
/**
|
|
127
|
+
* - How an integration component holds its
|
|
128
|
+
* name, for the owner rule.
|
|
129
|
+
*/
|
|
130
|
+
_hold?: "file" | "doc" | undefined;
|
|
131
|
+
/**
|
|
132
|
+
* - An integration component's doc.
|
|
133
|
+
*/
|
|
134
|
+
_docPath?: string | undefined;
|
|
135
|
+
};
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file The lexical ranker: keyword and fuzzy scoring of every gathered
|
|
5
|
+
* candidate, as one pure function (`rankLexical`). `searchCore` (search.mjs)
|
|
6
|
+
* runs it alone, and search's searchers read its ranking. No I/O: the adapter
|
|
7
|
+
* gathers the candidates. Word handling lives in _words.mjs, _synonyms.mjs and
|
|
8
|
+
* _query.mjs; a candidate's score in _score.mjs and _candidate-score.mjs.
|
|
9
|
+
*
|
|
10
|
+
*
|
|
11
|
+
* Returns the same typed envelope { type, data } that `xds --json search`
|
|
12
|
+
* outputs. The CLI command handler is a thin wrapper around this function.
|
|
13
|
+
*
|
|
14
|
+
* Scoring is keyword + fuzzy ranking; the per-term signal weights are
|
|
15
|
+
* listed in _candidate-score.mjs.
|
|
16
|
+
*
|
|
17
|
+
* A multi-word query has a reserved top tier (see {@link scoreQuery}): the
|
|
18
|
+
* whole query as a candidate's name or keyword (190-200), then the whole query
|
|
19
|
+
* as a phrase inside a doc's title or one of its headings (170), then a whole
|
|
20
|
+
* title of two words or more inside the query (160-169), then a candidate that
|
|
21
|
+
* matches every word of the query, at least one of them by name or keyword
|
|
22
|
+
* (151-159). Below those sits everything else: a partial match, or every word
|
|
23
|
+
* matched only in prose or through the components a page renders. A section titled "Light/Dark Mode" answers `dark
|
|
24
|
+
* mode` better than any doc that merely names `mode` in code, however exactly;
|
|
25
|
+
* "Dark mode" answers `how do I add dark mode`; and a guide whose title and
|
|
26
|
+
* description hold both words of `troubleshoot integration` answers it better
|
|
27
|
+
* than a doc named `integration`.
|
|
28
|
+
*
|
|
29
|
+
* Between domains, ranking adds one rule ahead of the score: domain priority
|
|
30
|
+
* (see {@link domainPriority}). A component, hook, template, or theme whose
|
|
31
|
+
* name or keyword a query word hits ranks ahead of every doc the reader did not
|
|
32
|
+
* ask for by name, title, or a declared phrase, so `font size` finds `Text`
|
|
33
|
+
* before a token page whose heading holds the phrase. A doc the query names,
|
|
34
|
+
* whose title the query holds, or that declares the whole query as a keyword
|
|
35
|
+
* keeps its place.
|
|
36
|
+
*
|
|
37
|
+
* Description and guidance are separate tiers on purpose. A component's own
|
|
38
|
+
* one-line description saying "notification" is a claim about what it IS; the
|
|
39
|
+
* same word inside another component's best-practice advice is a passing
|
|
40
|
+
* mention. Scored equally, `Toast` — "a brief, non-blocking notification" —
|
|
41
|
+
* ties with `Card`, `Dialog` and `Item`, which merely mention notifications in
|
|
42
|
+
* their guidance, and ties break alphabetically, so Toast falls off the end of
|
|
43
|
+
* its own best query.
|
|
44
|
+
*
|
|
45
|
+
* `keywords` carry AUTHORED intent — a block's `componentsUsed`, a page's
|
|
46
|
+
* `category` words. `weakKeywords` are DERIVED: the components a page template
|
|
47
|
+
* happens to render, scraped out of its JSX. Derived signal is deliberately
|
|
48
|
+
* capped below the confident-match gate on its own, because breadth is not
|
|
49
|
+
* relevance. At full keyword strength every rendered component is an
|
|
50
|
+
* independent 90-point shot with no penalty for how many a template has, so
|
|
51
|
+
* the broadest pages (a theme showcase rendering one of everything) win
|
|
52
|
+
* queries they have nothing to do with.
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
import {spellIdentifiers, joined} from './_words.mjs';
|
|
56
|
+
import {tokenizeQuery, phraseWords} from './_query.mjs';
|
|
57
|
+
import {MAX_RUN_WORDS} from './_compound.mjs';
|
|
58
|
+
import {scoreQuery} from './_score.mjs';
|
|
59
|
+
import {scoreCandidate} from './_candidate-score.mjs';
|
|
60
|
+
import {domainPriority} from './_priority.mjs';
|
|
61
|
+
import {toResult, searchedComponentsOf} from './_result.mjs';
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* A search candidate gathered from one content domain. Extra underscore-
|
|
65
|
+
* prefixed fields carry domain-specific payload used only by {@link toResult}.
|
|
66
|
+
* @typedef {object} Candidate
|
|
67
|
+
* @property {'component'|'hook'|'doc'|'template'|'theme'} domain
|
|
68
|
+
* @property {string} name
|
|
69
|
+
* @property {string[]} [aliases] - Other names the candidate answers to, scored
|
|
70
|
+
* with the same name signals: a theme's display name.
|
|
71
|
+
* @property {string[]} [keywords]
|
|
72
|
+
* @property {string[]} [_declared] - A doc's own declared keywords: what its
|
|
73
|
+
* author lists in `keywords`, without the code terms and table cells its
|
|
74
|
+
* other keywords add.
|
|
75
|
+
* @property {string[]} [weakKeywords]
|
|
76
|
+
* @property {string} [description]
|
|
77
|
+
* @property {string[]} [prose]
|
|
78
|
+
* @property {string[]} [guidance]
|
|
79
|
+
* @property {string[]} [titles] - A doc's title and the headings inside it:
|
|
80
|
+
* the lines a reader scans to pick it. The whole query standing in one of
|
|
81
|
+
* them, or one of them standing whole in the query, is a top-tier match.
|
|
82
|
+
* @property {string} [_import]
|
|
83
|
+
* @property {string} [_title]
|
|
84
|
+
* @property {string} [_topic] - A doc result's topic or docs-tree route.
|
|
85
|
+
* @property {string} [_section] - A doc result's section key, when it is one section.
|
|
86
|
+
* @property {string} [_command] - The command that reads exactly this doc part.
|
|
87
|
+
* @property {string} [_parent] - The command that opens the level above a doc
|
|
88
|
+
* part: its topic's section list, or the docs-tree namespace it sits in.
|
|
89
|
+
* @property {string} [_package] - The npm package that owns the candidate:
|
|
90
|
+
* Core's package for its components, hooks, and templates, the integration's
|
|
91
|
+
* for what it contributed, and for a doc part the package that wrote it.
|
|
92
|
+
* @property {string} [_displayName]
|
|
93
|
+
* @property {'page'|'block'} [_kind]
|
|
94
|
+
* @property {string} [_resultName]
|
|
95
|
+
* @property {string} [_commandName]
|
|
96
|
+
* @property {'file'|'doc'} [_hold] - How an integration component holds its
|
|
97
|
+
* name, for the owner rule.
|
|
98
|
+
* @property {string} [_docPath] - An integration component's doc.
|
|
99
|
+
*/
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* True when the query is the candidate's whole name or another name of it,
|
|
103
|
+
* typed as is or, for anything but a doc, with its spaces removed. Such a
|
|
104
|
+
* query names that entry outright.
|
|
105
|
+
* @param {Candidate} candidate
|
|
106
|
+
* @param {string} term the query, trimmed and lowercased
|
|
107
|
+
* @returns {boolean}
|
|
108
|
+
*/
|
|
109
|
+
export function namesWhole(candidate, term) {
|
|
110
|
+
return [candidate.name, ...(candidate.aliases ?? [])].some(name => {
|
|
111
|
+
const lower = String(name).toLowerCase();
|
|
112
|
+
return (
|
|
113
|
+
lower === term ||
|
|
114
|
+
(candidate.domain !== 'doc' &&
|
|
115
|
+
!/[\s_-]/.test(lower) &&
|
|
116
|
+
lower === term.replace(/\s+/g, ''))
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Today's lexical ranking of a gathered catalog: the search response
|
|
123
|
+
* `searchCore` returns.
|
|
124
|
+
*
|
|
125
|
+
* @param {string} query
|
|
126
|
+
* @param {{limit?: number}} options
|
|
127
|
+
* @param {{all: Candidate[], components: Candidate[], searchedComponents: boolean, outside?: boolean, coreSkipped?: string | null}} catalog
|
|
128
|
+
* @returns {import('./search.type.mjs').SearchResponse}
|
|
129
|
+
*/
|
|
130
|
+
export function rankLexical(query, options, catalog) {
|
|
131
|
+
const {limit = 20} = options;
|
|
132
|
+
const term = String(query).trim().toLowerCase();
|
|
133
|
+
const {all, components} = catalog;
|
|
134
|
+
|
|
135
|
+
// A query that is one identifier counts as the words it spells
|
|
136
|
+
// (`SidebarToggle`), and still matches whole as typed. In a query of several
|
|
137
|
+
// words, each identifier stays one term (`ChatComposer ChatLayout` names two
|
|
138
|
+
// components), and its words still join into names (see compoundHit). Both
|
|
139
|
+
// only help a query that names no entry outright: when the query is an
|
|
140
|
+
// entry's name, typed whole or as its words, that entry answers it, and
|
|
141
|
+
// neither reading may add near siblings beside it.
|
|
142
|
+
const spelled = spellIdentifiers(String(query).trim());
|
|
143
|
+
const exact = all.some(candidate => namesWhole(candidate, term));
|
|
144
|
+
// One identifier is read as its words only when, read whole, it matches
|
|
145
|
+
// nothing: a prop or hook name a doc already mentions keeps the
|
|
146
|
+
// entries that mention it.
|
|
147
|
+
const split =
|
|
148
|
+
!exact &&
|
|
149
|
+
!/\s/.test(term) &&
|
|
150
|
+
spelled !== term &&
|
|
151
|
+
!all.some(candidate => scoreCandidate(term, candidate));
|
|
152
|
+
const tokens = tokenizeQuery(split ? spelled : term);
|
|
153
|
+
/** @type {string[]} */
|
|
154
|
+
const words = exact
|
|
155
|
+
? []
|
|
156
|
+
: phraseWords(split || /\s/.test(term) ? spelled : term);
|
|
157
|
+
// The entries the query names: a component, hook, or template whose whole
|
|
158
|
+
// name is adjacent query words joined (`icon button in a card header` names
|
|
159
|
+
// IconButton), or one identifier typed as such. A name inside a longer named
|
|
160
|
+
// run does not count (`icon` there is part of IconButton).
|
|
161
|
+
const queryWords = phraseWords(spelled);
|
|
162
|
+
const typedIdentifiers = new Set(
|
|
163
|
+
(String(query).match(/[A-Za-z0-9]+/g) ?? [])
|
|
164
|
+
.filter(w => /[A-Z]/.test(w))
|
|
165
|
+
.map(w => w.toLowerCase()),
|
|
166
|
+
);
|
|
167
|
+
/** @type {Map<string, [number, number]>} each run joined, longest first */
|
|
168
|
+
const spans = new Map();
|
|
169
|
+
for (let len = Math.min(queryWords.length, MAX_RUN_WORDS); len >= 1; len--) {
|
|
170
|
+
for (let a = 0; a + len <= queryWords.length; a++) {
|
|
171
|
+
// One plain word names too much (`list`, `field`); a named entry is a
|
|
172
|
+
// run of joined words, or an identifier typed as one.
|
|
173
|
+
if (len === 1 && !typedIdentifiers.has(queryWords[a])) continue;
|
|
174
|
+
const run = queryWords.slice(a, a + len).join('');
|
|
175
|
+
if (!spans.has(run)) spans.set(run, [a, a + len]);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Where a query word is the candidate's whole name, or null: such an entry
|
|
180
|
+
* keeps the rank its name earns, whatever else it matched.
|
|
181
|
+
* @param {Candidate} candidate
|
|
182
|
+
* @returns {[number, number] | null}
|
|
183
|
+
*/
|
|
184
|
+
const wordNamed = candidate => {
|
|
185
|
+
if (candidate.domain === 'doc') return null;
|
|
186
|
+
for (const name of [candidate.name, ...(candidate.aliases ?? [])]) {
|
|
187
|
+
const n = joined(name);
|
|
188
|
+
const at = queryWords.findIndex(w => w === n || w === `${n}s`);
|
|
189
|
+
if (at >= 0) return [at, at + 1];
|
|
190
|
+
}
|
|
191
|
+
return null;
|
|
192
|
+
};
|
|
193
|
+
/** @param {Candidate} candidate @returns {[number, number] | null} */
|
|
194
|
+
const namedSpan = candidate => {
|
|
195
|
+
if (candidate.domain === 'doc') return null;
|
|
196
|
+
/** @type {[number, number] | null} */
|
|
197
|
+
let best = null;
|
|
198
|
+
for (const name of [candidate.name, ...(candidate.aliases ?? [])]) {
|
|
199
|
+
const n = joined(name);
|
|
200
|
+
const span =
|
|
201
|
+
spans.get(n) ??
|
|
202
|
+
spans.get(`${n}s`) ??
|
|
203
|
+
(/(?:s|x|z|ch|sh)$/.test(n) ? spans.get(`${n}es`) : undefined);
|
|
204
|
+
if (span && (!best || span[1] - span[0] > best[1] - best[0])) {
|
|
205
|
+
best = span;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return best;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// Score every candidate on its own merits. The consumer groups results by
|
|
212
|
+
// role (page / block / component) and takes the top of each, so there's no
|
|
213
|
+
// cross-role competition to engineer — a target page only needs to be the
|
|
214
|
+
// strongest PAGE, not outrank every component.
|
|
215
|
+
/** @type {{result: any, priority: 0 | 1 | 2, key: number, alias: 'partial' | 'keyword' | false, aliasWords: number, candidate: Candidate, span: [number, number] | null, word: [number, number] | null}[]} */
|
|
216
|
+
const ranked = [];
|
|
217
|
+
for (const candidate of all) {
|
|
218
|
+
const hit = scoreQuery(term, tokens, candidate, words);
|
|
219
|
+
if (hit)
|
|
220
|
+
ranked.push({
|
|
221
|
+
result: toResult(
|
|
222
|
+
candidate,
|
|
223
|
+
hit.score,
|
|
224
|
+
hit.reason,
|
|
225
|
+
hit.matched,
|
|
226
|
+
hit.total,
|
|
227
|
+
catalog.outside,
|
|
228
|
+
),
|
|
229
|
+
priority: domainPriority(term, tokens, candidate, hit, words),
|
|
230
|
+
key: hit.score,
|
|
231
|
+
alias: hit.alias ?? false,
|
|
232
|
+
aliasWords: hit.aliasWords ?? 0,
|
|
233
|
+
candidate,
|
|
234
|
+
span: namedSpan(candidate),
|
|
235
|
+
word: wordNamed(candidate),
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
// A match on a keyword read joined never outranks an entry the query names,
|
|
239
|
+
// and neither does a match on part of another name unless it spans more
|
|
240
|
+
// query words than any named run: `table header with sticky columns` keeps
|
|
241
|
+
// TableHeader above useTableStickyColumns, which only holds the words
|
|
242
|
+
// `sticky columns`. Only the order changes; each result keeps the score its
|
|
243
|
+
// text earned.
|
|
244
|
+
/** @param {[number, number]} inner @param {[number, number]} outer */
|
|
245
|
+
const inside = (inner, outer) =>
|
|
246
|
+
outer[0] <= inner[0] &&
|
|
247
|
+
outer[1] >= inner[1] &&
|
|
248
|
+
outer[1] - outer[0] > inner[1] - inner[0];
|
|
249
|
+
const named = ranked.filter(r => {
|
|
250
|
+
const span = r.span;
|
|
251
|
+
return (
|
|
252
|
+
span != null &&
|
|
253
|
+
!ranked.some(o => o !== r && o.span != null && inside(span, o.span))
|
|
254
|
+
);
|
|
255
|
+
});
|
|
256
|
+
/** @param {{key: number}[]} rows */
|
|
257
|
+
const floorOf = rows =>
|
|
258
|
+
rows.length ? Math.min(...rows.map(r => r.key)) : Infinity;
|
|
259
|
+
const namedFloor = floorOf(named);
|
|
260
|
+
const longestNamed = Math.max(
|
|
261
|
+
0,
|
|
262
|
+
...named.map(r => (r.span ? r.span[1] - r.span[0] : 0)),
|
|
263
|
+
);
|
|
264
|
+
for (const r of ranked) {
|
|
265
|
+
if (named.includes(r)) continue;
|
|
266
|
+
if (!r.alias || r.word) continue;
|
|
267
|
+
// A match on part of a longer name that spans more words than any named
|
|
268
|
+
// run reads more of the query than the named entry does (`dropdown menu
|
|
269
|
+
// radio` and DropdownMenuRadioItem, against DropdownMenu), so it is not
|
|
270
|
+
// held back. A joined keyword always is.
|
|
271
|
+
if (r.alias === 'partial' && r.aliasWords > longestNamed) continue;
|
|
272
|
+
// It keeps what it earns without those matches.
|
|
273
|
+
const own =
|
|
274
|
+
scoreQuery(term, tokens, r.candidate, words, {plain: true})?.score ?? 0;
|
|
275
|
+
r.key = Math.max(own, Math.min(r.key, namedFloor - 0.5));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Docs and everything else are each sorted by score desc, then domain
|
|
279
|
+
// (stable order), then name. The two are merged: at each step the stronger
|
|
280
|
+
// head goes next, except that a head with domain priority goes ahead of a
|
|
281
|
+
// doc head without it (see domainPriority). Each side keeps its own order.
|
|
282
|
+
/** @type {Record<string, number>} */
|
|
283
|
+
const domainOrder = {component: 0, hook: 1, doc: 2, template: 3, theme: 4};
|
|
284
|
+
/**
|
|
285
|
+
* @param {{result: any, key: number}} x
|
|
286
|
+
* @param {{result: any, key: number}} y
|
|
287
|
+
*/
|
|
288
|
+
const byScore = ({result: a, key: ka}, {result: b, key: kb}) =>
|
|
289
|
+
kb - ka ||
|
|
290
|
+
(domainOrder[a.domain] ?? 9) - (domainOrder[b.domain] ?? 9) ||
|
|
291
|
+
a.name.localeCompare(b.name);
|
|
292
|
+
const others = ranked.filter(r => r.result.domain !== 'doc').sort(byScore);
|
|
293
|
+
// Against other domains, a doc the reader asked for competes ahead of docs
|
|
294
|
+
// they did not, so it is not held behind a broad page that outscores it.
|
|
295
|
+
// Docs alone keep their text-match order, with a doc the whole query names
|
|
296
|
+
// first.
|
|
297
|
+
const docs = ranked
|
|
298
|
+
.filter(r => r.result.domain === 'doc')
|
|
299
|
+
.sort((x, y) =>
|
|
300
|
+
others.length > 0
|
|
301
|
+
? y.priority - x.priority || byScore(x, y)
|
|
302
|
+
: Number(y.priority === 2) - Number(x.priority === 2) || byScore(x, y),
|
|
303
|
+
);
|
|
304
|
+
const scored = [];
|
|
305
|
+
let i = 0;
|
|
306
|
+
let j = 0;
|
|
307
|
+
while (i < others.length || j < docs.length) {
|
|
308
|
+
const other = others[i];
|
|
309
|
+
const doc = docs[j];
|
|
310
|
+
const otherFirst =
|
|
311
|
+
!doc ||
|
|
312
|
+
(other != null &&
|
|
313
|
+
((other.priority && !doc.priority) || byScore(other, doc) < 0));
|
|
314
|
+
scored.push(otherFirst ? others[i++].result : docs[j++].result);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// `results` is bounded by `limit` so a caller (and the recorded run that
|
|
318
|
+
// quotes it) never carries an unbounded payload. `matchCount` is the number
|
|
319
|
+
// of matches that bound was applied TO — reporting `results.length` there
|
|
320
|
+
// would report the cap back as if it were the answer, so a query matching
|
|
321
|
+
// 57 things and one matching exactly 20 would be indistinguishable.
|
|
322
|
+
const limited = scored.slice(0, limit);
|
|
323
|
+
|
|
324
|
+
/** @type {import('./search.type.mjs').SearchResponse} */
|
|
325
|
+
const response = {
|
|
326
|
+
type: 'search',
|
|
327
|
+
data: {
|
|
328
|
+
query: String(query).trim(),
|
|
329
|
+
matchCount: scored.length,
|
|
330
|
+
// toResult gives every domain its command and domain fields.
|
|
331
|
+
results: /** @type {import('./search.type.mjs').SearchResultEntry[]} */ (
|
|
332
|
+
limited
|
|
333
|
+
),
|
|
334
|
+
},
|
|
335
|
+
};
|
|
336
|
+
if (catalog.coreSkipped) {
|
|
337
|
+
response.meta = {
|
|
338
|
+
partial: {
|
|
339
|
+
skipped: ['component', 'hook', 'template'],
|
|
340
|
+
reason: catalog.coreSkipped,
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
if (catalog.searchedComponents) {
|
|
345
|
+
searchedComponentsOf.set(
|
|
346
|
+
response,
|
|
347
|
+
components.map(c => ({name: c.name, keywords: c.keywords ?? []})),
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
return response;
|
|
351
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
* Ranks with one searcher's weights. Shared by the main searcher and every
|
|
6
|
+
* topic searcher, which differ only in their weights.
|
|
7
|
+
* @param {import('./tree.mjs').SearchRequest} request
|
|
8
|
+
* @param {import('./tree.mjs').SearchCatalog} catalog
|
|
9
|
+
* @param {SearcherWeights} weights
|
|
10
|
+
* @returns {import('./search.type.mjs').SearchResponse}
|
|
11
|
+
*/
|
|
12
|
+
export function rankWith(request: import("./tree.mjs").SearchRequest, catalog: import("./tree.mjs").SearchCatalog, weights: SearcherWeights): import("./search.type.mjs").SearchResponse;
|
|
13
|
+
export function asWeights(weights: unknown): SearcherWeights | null;
|
|
14
|
+
export const main: import("../../foundation/route/route.mjs").Searcher<import("./tree.mjs").SearchRequest, import("./_adapter.mjs").SearchCatalog, import("./search.type.mjs").SearchResponse>;
|
|
15
|
+
export type SearcherWeights = {
|
|
16
|
+
w: number[];
|
|
17
|
+
mu: number[];
|
|
18
|
+
sd: number[];
|
|
19
|
+
};
|