@astryxdesign/cli 0.7.2-canary.fe2c655 → 0.7.2
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/CHANGELOG.md +119 -0
- package/README.md +175 -154
- package/api/build/_adapter.d.mts +8 -0
- package/api/build/_adapter.mjs +12 -1
- package/api/build/build.doc.mjs +4 -0
- package/api/build/help/help.mjs +1 -1
- package/api/build/kit/kit.mjs +3 -11
- package/api/component/_adapter.d.mts +72 -1
- package/api/component/_adapter.mjs +212 -18
- package/api/component/component.doc.mjs +6 -1
- package/api/component/component.mjs +82 -15
- package/api/component/component.test.mjs +80 -0
- package/api/component/doc-links.test.mjs +386 -0
- package/api/component/integration-export-name.test.mjs +644 -0
- package/api/component/list/list.mjs +42 -4
- package/api/discover/_adapter.d.mts +132 -12
- package/api/discover/_adapter.mjs +488 -27
- package/api/discover/_adapter.test.mjs +599 -17
- package/api/discover/_catalog-view.d.mts +16 -5
- package/api/discover/_catalog-view.mjs +32 -8
- package/api/discover/detail/doc/doc.mjs +9 -1
- package/api/discover/discover.d.mts +11 -0
- package/api/discover/discover.doc.mjs +9 -7
- package/api/discover/discover.mjs +121 -19
- package/api/discover/discover.type.d.mts +20 -7
- package/api/discover/discover.type.mjs +17 -7
- package/api/discover/list/list.d.mts +4 -3
- package/api/discover/list/list.mjs +17 -10
- package/api/docs/_adapter.d.mts +74 -0
- package/api/docs/_adapter.mjs +553 -16
- package/api/docs/docs.doc.mjs +2 -1
- package/api/docs/docs.mjs +2 -1
- package/api/docs/docs.test.mjs +5 -2
- package/api/docs/integration-tree.test.mjs +22 -0
- package/api/docs/integrationDocs.test.mjs +124 -0
- package/api/docs/list/list.mjs +12 -2
- package/api/docs/reference-blocks.test.mjs +1 -3
- package/api/doctor/_adapter.d.mts +108 -0
- package/api/doctor/_adapter.mjs +223 -4
- package/api/doctor/doctor.d.mts +199 -0
- package/api/doctor/doctor.doc.mjs +3 -2
- package/api/doctor/doctor.mjs +530 -34
- package/api/doctor/doctor.test.mjs +180 -14
- package/api/doctor/release-checks.test.mjs +156 -0
- package/api/doctor/theme-checks.mjs +17 -4
- package/api/doctor/theme-checks.test.mjs +223 -7
- package/api/error.d.mts +10 -0
- package/api/error.mjs +25 -0
- package/api/gap-report/gap-report.d.mts +3 -0
- package/api/gap-report/gap-report.doc.mjs +11 -3
- package/api/gap-report/gap-report.mjs +219 -52
- package/api/gap-report/gap-report.test.mjs +233 -0
- package/api/gap-report/gap-report.type.d.mts +41 -0
- package/api/gap-report/gap-report.type.mjs +18 -0
- package/api/hook/_adapter.mjs +3 -11
- package/api/hook/hook.doc.mjs +4 -0
- package/api/hook/list/list.d.mts +1 -1
- package/api/index.d.mts +2 -0
- package/api/index.mjs +2 -0
- package/api/init/_adapter.d.mts +22 -0
- package/api/init/_adapter.mjs +59 -0
- package/api/init/init.d.mts +7 -5
- package/api/init/init.doc.mjs +28 -7
- package/api/init/init.mjs +28 -4
- package/api/init/init.type.d.mts +48 -3
- package/api/init/init.type.mjs +37 -3
- package/api/init/run/run.d.mts +1 -1
- package/api/init/run/run.mjs +17 -6
- package/api/init/user/user.d.mts +14 -0
- package/api/init/user/user.mjs +87 -0
- package/api/init/user/user.test.mjs +232 -0
- package/api/integration/_adapter.d.mts +45 -0
- package/api/integration/_adapter.mjs +112 -0
- package/api/integration/add-contribution.component-names.test.mjs +66 -0
- package/api/integration/add-contribution.mjs +8 -4
- package/api/integration/add-contribution.test.mjs +4 -2
- package/api/integration/authoring-checks.mjs +110 -6
- package/api/integration/authoring-checks.test.mjs +808 -0
- package/api/integration/authoring-checks.type.d.mts +143 -0
- package/api/integration/authoring-checks.type.mjs +74 -2
- package/api/integration/check/check.d.mts +96 -0
- package/api/integration/check/check.mjs +357 -0
- package/api/integration/check/check.test.mjs +489 -0
- package/api/integration/integration-check.type.d.mts +118 -0
- package/api/integration/integration-check.type.mjs +84 -0
- package/api/integration/integrationCheck.doc.d.mts +10 -0
- package/api/integration/integrationCheck.doc.mjs +73 -0
- package/api/integration/integrationComponentConflicts.doc.mjs +45 -6
- package/api/integration/integrationDocConflicts.doc.mjs +22 -5
- package/api/integration/pack-check.mjs +364 -120
- package/api/integration/pack-check.test.mjs +730 -10
- package/api/integration/validate-integration-fixes.test.mjs +67 -0
- package/api/integration/validate-integration.d.mts +8 -0
- package/api/integration/validate-integration.mjs +63 -9
- package/api/integration/validate-integration.test.mjs +88 -8
- package/api/integration/validate-integration.type.d.mts +16 -0
- package/api/integration/validate-integration.type.mjs +14 -1
- package/api/integration/validate-unread-theme-folders.test.mjs +3 -2
- package/api/integration/validateIntegration.doc.mjs +19 -3
- package/api/search/_adapter.d.mts +11 -0
- package/api/search/_adapter.mjs +25 -0
- package/api/search/search.d.mts +82 -13
- package/api/search/search.doc.mjs +8 -3
- package/api/search/search.domain-priority.test.mjs +108 -18
- package/api/search/search.integration-check.test.mjs +24 -0
- package/api/search/search.mjs +645 -64
- package/api/search/search.test.mjs +179 -1
- package/api/search/search.type.d.mts +14 -1
- package/api/search/search.type.mjs +6 -1
- package/api/swizzle/_adapter.mjs +2 -2
- package/api/swizzle/copy/copy.mjs +29 -15
- package/api/template/template-integration.test.mjs +1 -1
- package/api/template/template.test.mjs +120 -3
- package/api/theme/_adapter.d.mts +16 -0
- package/api/theme/_adapter.mjs +32 -5
- package/api/theme/build/build.d.mts +2 -1
- package/api/theme/build/build.family.test.mjs +64 -0
- package/api/theme/build/build.mjs +250 -38
- package/api/theme/build/build.packed-old-core.test.mjs +20 -3
- package/api/theme/build/font-warning.mjs +5 -3
- package/api/theme/build/font-warning.test.mjs +2 -1
- package/api/theme/remove/remove.test.mjs +2 -2
- package/api/theme/targets/targets.mjs +3 -10
- package/api/theme/themeBuild.doc.mjs +4 -4
- package/api/theme/themeTargets.doc.mjs +4 -0
- package/api/theme/use/use.test.mjs +1 -1
- package/api/upgrade/_adapter.d.mts +40 -2
- package/api/upgrade/_adapter.mjs +118 -5
- package/api/upgrade/run/cli-behind-core.test.mjs +438 -0
- package/api/upgrade/run/run.mjs +35 -2
- package/api/upgrade/status/status.d.mts +21 -5
- package/api/upgrade/status/status.mjs +45 -8
- package/api/upgrade/upgrade.doc.mjs +6 -3
- package/api/upgrade/upgrade.mjs +5 -1
- package/api/upgrade/upgrade.type.d.mts +23 -0
- package/api/upgrade/upgrade.type.mjs +16 -1
- package/assets/codemods/__tests__/registry.test.mjs +1 -0
- package/assets/codemods/registry.mjs +1 -0
- package/assets/codemods/runner.mjs +7 -1
- package/assets/codemods/transforms/v0.7.2/__tests__/migrate-list-edge-compensation-to-edge.test.mjs +120 -0
- package/assets/codemods/transforms/v0.7.2/index.mjs +20 -0
- package/assets/codemods/transforms/v0.7.2/migrate-list-edge-compensation-to-edge.mjs +198 -0
- package/assets/discover/official-packages.json +367 -0
- package/assets/docs/README.md +10 -0
- package/assets/docs/author-a-theme.doc.mjs +3 -3
- package/assets/docs/browser-support.doc.mjs +1 -1
- package/assets/docs/getting-started.doc.mjs +26 -6
- package/assets/docs/styling-overview.doc.mjs +1 -1
- package/assets/docs/theme.doc.dense.mjs +1 -1
- package/assets/docs/theme.doc.mjs +2 -2
- package/assets/docs/theme.doc.zh.mjs +1 -1
- package/assets/docs/tree/add-a-component.doc.mjs +22 -2
- package/assets/docs/tree/add-a-theme.doc.mjs +13 -3
- package/assets/docs/tree/add-a-topic.doc.mjs +54 -9
- package/assets/docs/tree/agent-guidance.doc.mjs +19 -5
- package/assets/docs/tree/block-template.doc.mjs +8 -20
- package/assets/docs/tree/build-the-template.doc.mjs +1 -1
- package/assets/docs/tree/building-blocks.doc.mjs +4 -0
- package/assets/docs/tree/check-the-props.doc.mjs +201 -0
- package/assets/docs/tree/check-your-docs.doc.mjs +69 -11
- package/assets/docs/tree/checks.doc.mjs +32 -24
- package/assets/docs/tree/ci-and-containers.doc.mjs +88 -0
- package/assets/docs/tree/cli-command.doc.mjs +142 -0
- package/assets/docs/tree/cli.doc.mjs +6 -0
- package/assets/docs/tree/codemods.doc.mjs +17 -7
- package/assets/docs/tree/commands.doc.mjs +7 -1
- package/assets/docs/tree/component-family.doc.mjs +8 -6
- package/assets/docs/tree/component-imports.doc.mjs +21 -3
- package/assets/docs/tree/components.doc.mjs +7 -1
- package/assets/docs/tree/computer-agent-guide.doc.mjs +160 -0
- package/assets/docs/tree/computer-integrations.doc.mjs +93 -0
- package/assets/docs/tree/configuration.doc.mjs +7 -3
- package/assets/docs/tree/debug-and-gap-reports.doc.mjs +41 -22
- package/assets/docs/tree/define-the-theme.doc.mjs +6 -5
- package/assets/docs/tree/describe-the-component.doc.mjs +3 -3
- package/assets/docs/tree/docs.doc.mjs +9 -3
- package/assets/docs/tree/document-the-template.doc.mjs +1 -1
- package/assets/docs/tree/document-the-theme.doc.mjs +3 -3
- package/assets/docs/tree/export-template-assets.doc.mjs +1 -1
- package/assets/docs/tree/extend-or-replace.doc.mjs +19 -9
- package/assets/docs/tree/fonts-and-assets.doc.mjs +2 -2
- package/assets/docs/tree/generate-a-palette.doc.mjs +7 -5
- package/assets/docs/tree/global-integrations.doc.mjs +124 -0
- package/assets/docs/tree/global.doc.mjs +131 -0
- package/assets/docs/tree/grade-template-with-agent.doc.mjs +5 -5
- package/assets/docs/tree/install-once.doc.mjs +105 -0
- package/assets/docs/tree/integrations.doc.mjs +13 -1
- package/assets/docs/tree/layout-spacing.doc.mjs +2 -3
- package/assets/docs/tree/layout.doc.mjs +6 -6
- package/assets/docs/tree/links.doc.mjs +7 -6
- package/assets/docs/tree/migration-setup.doc.mjs +3 -2
- package/assets/docs/tree/migration-verification.doc.mjs +0 -1
- package/assets/docs/tree/migration.doc.mjs +0 -1
- package/assets/docs/tree/monorepos.doc.mjs +84 -0
- package/assets/docs/tree/package-and-test.doc.mjs +1 -1
- package/assets/docs/tree/page-template.doc.mjs +4 -8
- package/assets/docs/tree/problems.doc.mjs +205 -0
- package/assets/docs/tree/project-global-or-both.doc.mjs +131 -0
- package/assets/docs/tree/publishing.doc.mjs +6 -6
- package/assets/docs/tree/quick-start.doc.mjs +53 -27
- package/assets/docs/tree/reference.doc.mjs +192 -0
- package/assets/docs/tree/replace-a-core-component.doc.mjs +15 -7
- package/assets/docs/tree/replace-a-core-template.doc.mjs +10 -12
- package/assets/docs/tree/scaffold.doc.mjs +1 -1
- package/assets/docs/tree/sections-and-placement.doc.mjs +9 -11
- package/assets/docs/tree/see-it-in-an-app.doc.mjs +17 -5
- package/assets/docs/tree/short-and-findable.doc.mjs +9 -8
- package/assets/docs/tree/single-component.doc.mjs +15 -6
- package/assets/docs/tree/start-a-template.doc.mjs +18 -8
- package/assets/docs/tree/subcomponent.doc.mjs +6 -6
- package/assets/docs/tree/template-assets.doc.mjs +2 -2
- package/assets/docs/tree/template-doc-overview.doc.mjs +6 -17
- package/assets/docs/tree/template-fonts.doc.mjs +1 -1
- 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 +2 -2
- package/assets/docs/tree/templates.doc.mjs +7 -1
- package/assets/docs/tree/test-in-an-app.doc.mjs +7 -7
- package/assets/docs/tree/test-template-in-app.doc.mjs +5 -5
- package/assets/docs/tree/themes.doc.mjs +4 -0
- package/assets/docs/tree/tokens-and-setup.doc.mjs +19 -15
- package/assets/docs/tree/troubleshooting.doc.mjs +29 -13
- package/assets/docs/tree/type-scale.doc.mjs +3 -3
- package/assets/docs/tree/uninstall.doc.mjs +109 -0
- package/assets/docs/tree/updates.doc.mjs +92 -0
- package/assets/docs/tree/upgrading.doc.mjs +6 -6
- package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +14 -2
- package/assets/docs/tree/verify-packed-template.doc.mjs +3 -3
- package/assets/docs/tree/versioning.doc.mjs +10 -9
- package/assets/docs/tree/who-answers.doc.mjs +119 -0
- package/assets/docs/tree/write-the-template-file.doc.mjs +7 -6
- package/assets/docs/use-a-theme.doc.mjs +2 -2
- package/assets/docs/working-with-ai.doc.mjs +66 -13
- package/assets/theme.template.ts +1 -1
- package/authoring/codemod/codemod.doc.mjs +7 -3
- package/authoring/config/config.doc.mjs +9 -6
- package/authoring/config/type.ts +4 -2
- package/authoring/debug/parse.d.mts +5 -5
- package/authoring/discover/discover.doc.mjs +7 -1
- package/authoring/doctypes/_schema.d.mts +8 -3
- package/authoring/doctypes/_schema.mjs +1 -0
- package/authoring/doctypes/base/graph-fields.doc.mjs +6 -4
- package/authoring/doctypes/command/command.doc.mjs +20 -12
- package/authoring/doctypes/command/type.ts +14 -0
- package/authoring/doctypes/component/component.doc.mjs +30 -27
- package/authoring/doctypes/component/type.ts +21 -8
- package/authoring/doctypes/enum/enum.doc.mjs +7 -4
- package/authoring/doctypes/function/function.doc.mjs +9 -8
- package/authoring/doctypes/hook/hook.doc.mjs +5 -4
- package/authoring/doctypes/hook/type.ts +4 -0
- package/authoring/doctypes/namespace/namespace.doc.mjs +7 -6
- package/authoring/doctypes/reference/reference.doc.mjs +12 -11
- package/authoring/doctypes/schema/schema.doc.mjs +9 -9
- package/authoring/doctypes/template/integration-template.doc.d.mts +11 -0
- package/authoring/doctypes/template/integration-template.doc.mjs +49 -0
- package/authoring/doctypes/template/parse.d.mts +6 -2
- package/authoring/doctypes/template/parse.mjs +3 -1
- package/authoring/doctypes/template/template.doc.mjs +3 -3
- package/authoring/doctypes/template/type.ts +22 -0
- package/authoring/doctypes/theme/theme.doc.mjs +2 -2
- package/authoring/gap-report/gap-report.doc.mjs +2 -2
- package/authoring/identity/identity.doc.mjs +3 -3
- package/authoring/index.d.ts +2 -0
- package/authoring/integration/integration.doc.mjs +30 -6
- package/authoring/integration/parse.test.mjs +21 -0
- package/authoring/integration/schema.d.mts +16 -3
- package/authoring/integration/schema.mjs +34 -2
- package/authoring/integration/type.ts +12 -0
- package/clients/cli/bin/astryx.mjs +20 -3
- package/clients/cli/cli-command.test.mjs +166 -0
- package/clients/cli/commands/blog.doc.mjs +1 -0
- package/clients/cli/commands/build-theme.batch-report.test.mjs +17 -3
- package/clients/cli/commands/build-theme.font-warning.test.mjs +3 -3
- package/clients/cli/commands/build-theme.mjs +26 -13
- package/clients/cli/commands/build-theme.path-safety.test.mjs +224 -6
- package/clients/cli/commands/build-theme.registry.test.mjs +5 -2
- package/clients/cli/commands/build.doc.mjs +1 -0
- package/clients/cli/commands/build.exit-codes-doc.test.mjs +4 -0
- package/clients/cli/commands/cli-postinstall.test.mjs +49 -1
- package/clients/cli/commands/component.doc.mjs +1 -0
- package/clients/cli/commands/discover.broken-integration.test.mjs +11 -7
- package/clients/cli/commands/discover.doc.mjs +9 -1
- package/clients/cli/commands/discover.mjs +144 -93
- package/clients/cli/commands/discover.no-source.test.mjs +448 -33
- package/clients/cli/commands/discover.sources.test.mjs +20 -1
- package/clients/cli/commands/docs.doc.mjs +1 -0
- package/clients/cli/commands/docs.mjs +49 -19
- package/clients/cli/commands/docs.test.mjs +2 -2
- package/clients/cli/commands/doctor-integration-components.doc.mjs +19 -4
- package/clients/cli/commands/doctor-integration-docs.doc.mjs +20 -5
- package/clients/cli/commands/doctor-integration-templates.doc.mjs +1 -0
- package/clients/cli/commands/doctor-integration-validate.doc.mjs +22 -2
- package/clients/cli/commands/doctor-integration.test.mjs +888 -3
- package/clients/cli/commands/doctor.doc.mjs +12 -2
- package/clients/cli/commands/doctor.mjs +292 -7
- package/clients/cli/commands/doctor.test.mjs +10 -4
- package/clients/cli/commands/gap-report.doc.mjs +15 -2
- package/clients/cli/commands/gap-report.mjs +24 -2
- package/clients/cli/commands/gap-report.test.mjs +79 -0
- package/clients/cli/commands/hook.doc.mjs +1 -0
- package/clients/cli/commands/init.behavior.test.mjs +5 -0
- package/clients/cli/commands/init.doc.mjs +29 -3
- package/clients/cli/commands/integration-add.doc.mjs +7 -0
- package/clients/cli/commands/integration-authoring.test.mjs +49 -3
- package/clients/cli/commands/integration-check.doc.mjs +93 -0
- package/clients/cli/commands/integration-check.test.mjs +441 -0
- package/clients/cli/commands/integration-pack.doc.mjs +8 -1
- package/clients/cli/commands/integration-verify.doc.mjs +8 -1
- package/clients/cli/commands/integration.doc.mjs +6 -2
- package/clients/cli/commands/integration.mjs +119 -7
- package/clients/cli/commands/json-contract.test.mjs +11 -2
- package/clients/cli/commands/manifest.doc.mjs +1 -0
- package/clients/cli/commands/outside-project.test.mjs +774 -0
- package/clients/cli/commands/search.doc.mjs +1 -0
- package/clients/cli/commands/search.mjs +16 -11
- package/clients/cli/commands/search.test.mjs +429 -296
- package/clients/cli/commands/setup-nudge.test.mjs +102 -2
- package/clients/cli/commands/swizzle.doc.mjs +7 -0
- package/clients/cli/commands/swizzle.mjs +5 -3
- package/clients/cli/commands/swizzle.routing.test.mjs +19 -2
- package/clients/cli/commands/template.doc.mjs +1 -0
- package/clients/cli/commands/text-json-parity.test.mjs +5 -0
- package/clients/cli/commands/theme-add-lifecycle.test.mjs +3 -3
- package/clients/cli/commands/theme-add.doc.mjs +8 -1
- package/clients/cli/commands/theme-build.doc.mjs +3 -2
- package/clients/cli/commands/theme-eject.doc.mjs +1 -0
- package/clients/cli/commands/theme-list.behavior.test.mjs +6 -0
- package/clients/cli/commands/theme-list.doc.mjs +1 -0
- package/clients/cli/commands/theme-palette-generate.doc.mjs +1 -0
- package/clients/cli/commands/theme-remove.doc.mjs +7 -0
- package/clients/cli/commands/theme-targets.doc.mjs +1 -0
- package/clients/cli/commands/theme-template.doc.mjs +1 -0
- package/clients/cli/commands/theme-use.doc.mjs +7 -0
- package/clients/cli/commands/upgrade.config-ordering.test.mjs +2 -1
- package/clients/cli/commands/upgrade.doc.mjs +22 -1
- package/clients/cli/commands/upgrade.file-protection.test.mjs +3 -2
- package/clients/cli/commands/upgrade.hook-output.test.mjs +2 -1
- package/clients/cli/commands/upgrade.older-cli.test.mjs +160 -0
- package/clients/cli/index.mjs +112 -16
- package/clients/cli/lib/cli-error.mjs +4 -1
- package/clients/cli/lib/component-format.test.mjs +1 -1
- package/clients/cli/lib/define-command.mjs +2 -2
- package/clients/cli/lib/json-shim.mjs +3 -1
- package/clients/cli/lib/manifest.d.ts +9 -0
- package/clients/cli/lib/manifest.mjs +17 -1
- package/clients/cli/lib/manifest.test.mjs +25 -0
- package/foundation/agent-docs/agent-doc-state.d.mts +35 -6
- package/foundation/agent-docs/agent-doc-state.mjs +66 -6
- package/foundation/agent-docs/agent-docs.d.mts +4 -3
- package/foundation/agent-docs/agent-docs.mjs +11 -9
- package/foundation/agent-docs/computer-agent-text.d.mts +133 -0
- package/foundation/agent-docs/computer-agent-text.mjs +623 -0
- package/foundation/agent-docs/computer-agent-text.test.mjs +602 -0
- package/foundation/config/project.d.mts +35 -3
- package/foundation/config/project.mjs +226 -7
- package/foundation/config/project.test.mjs +66 -0
- package/foundation/config/theme-state.mjs +5 -4
- package/foundation/discovery/authoring-self-docs.mjs +2 -1
- package/foundation/discovery/cli-self-docs.mjs +20 -3
- package/foundation/discovery/cli-self-docs.test.mjs +3 -2
- package/foundation/discovery/component-discovery.d.mts +190 -10
- package/foundation/discovery/component-discovery.export-names.test.mjs +760 -0
- package/foundation/discovery/component-discovery.mjs +864 -56
- package/foundation/discovery/component-discovery.test.mjs +96 -0
- package/foundation/discovery/component-replacement.mjs +20 -1
- package/foundation/discovery/component-replacement.test.mjs +40 -0
- package/foundation/discovery/copied-passages.d.mts +216 -0
- package/foundation/discovery/copied-passages.mjs +720 -0
- package/foundation/discovery/copied-passages.test.mjs +124 -0
- package/foundation/discovery/doc-consistency.d.mts +34 -0
- package/foundation/discovery/doc-consistency.mjs +513 -0
- package/foundation/discovery/doc-consistency.test.mjs +347 -0
- package/foundation/discovery/doc-fluff.d.mts +112 -0
- package/foundation/discovery/doc-fluff.mjs +328 -0
- package/foundation/discovery/doc-fluff.test.mjs +142 -0
- package/foundation/discovery/doc-types.d.mts +157 -0
- package/foundation/discovery/doc-types.mjs +1231 -0
- package/foundation/discovery/doc-types.test.mjs +744 -0
- package/foundation/discovery/docs-discovery.d.mts +88 -0
- package/foundation/discovery/docs-discovery.mjs +83 -2
- package/foundation/discovery/integration-doc-files.d.mts +22 -0
- package/foundation/discovery/integration-doc-files.mjs +231 -0
- package/foundation/discovery/integration-doc-files.test.mjs +246 -0
- package/foundation/discovery/module-exports.d.mts +27 -0
- package/foundation/discovery/module-exports.mjs +356 -31
- package/foundation/discovery/module-exports.test.mjs +172 -0
- package/foundation/discovery/package-tsconfig.d.mts +14 -0
- package/foundation/discovery/package-tsconfig.mjs +228 -0
- package/foundation/discovery/package-tsconfig.test.mjs +210 -0
- package/foundation/discovery/project-typescript.d.mts +32 -0
- package/foundation/discovery/project-typescript.mjs +89 -0
- package/foundation/discovery/prop-drift.d.mts +297 -0
- package/foundation/discovery/prop-drift.mjs +2428 -0
- package/foundation/discovery/prop-drift.test.mjs +2337 -0
- package/foundation/discovery/runner-allowlist.d.mts +40 -0
- package/foundation/discovery/runner-allowlist.mjs +53 -0
- package/foundation/discovery/stray-pointers.d.mts +52 -0
- package/foundation/discovery/stray-pointers.mjs +293 -0
- package/foundation/discovery/stray-pointers.test.mjs +86 -0
- package/foundation/discovery/template-adapter.mjs +74 -5
- package/foundation/discovery/template-needs.mjs +5 -2
- package/foundation/discovery/theme-imports.mjs +8 -4
- package/foundation/discovery/theme-imports.test.mjs +1 -1
- package/foundation/discovery/type-match.d.mts +53 -0
- package/foundation/discovery/type-match.mjs +660 -0
- package/foundation/discovery/type-match.test.mjs +169 -0
- package/foundation/discovery/type-text.d.mts +68 -0
- package/foundation/discovery/type-text.mjs +658 -0
- package/foundation/discovery/type-text.test.mjs +196 -0
- package/foundation/discovery/unresolved-references.d.mts +62 -0
- package/foundation/discovery/unresolved-references.mjs +790 -0
- package/foundation/discovery/unresolved-references.test.mjs +567 -0
- package/foundation/doc-compiler/doc-loads.test.mjs +51 -1
- package/foundation/doc-compiler/inputs.mjs +27 -1
- package/foundation/doc-compiler/inputs.test.mjs +0 -1
- package/foundation/doc-compiler/links.d.mts +41 -5
- package/foundation/doc-compiler/links.mjs +167 -28
- package/foundation/doc-compiler/links.test.mjs +97 -4
- package/foundation/doc-compiler/read.d.mts +1 -1
- package/foundation/doc-compiler/tree.mjs +30 -1
- package/foundation/doc-compiler/tree.test.mjs +8 -1
- package/foundation/env/cli-command.d.mts +83 -0
- package/foundation/env/cli-command.mjs +152 -0
- package/foundation/env/cli-command.test.mjs +129 -0
- package/foundation/env/node-version.test.mjs +15 -3
- package/foundation/env/package-manager.d.mts +93 -9
- package/foundation/env/package-manager.global.test.mjs +66 -0
- package/foundation/env/package-manager.mjs +184 -13
- package/foundation/env/package-manager.test.mjs +341 -15
- package/foundation/env/project-scope.d.mts +9 -0
- package/foundation/env/project-scope.mjs +33 -0
- package/foundation/env/search-path.d.mts +42 -0
- package/foundation/env/search-path.mjs +205 -0
- package/foundation/env/search-path.test.mjs +122 -0
- package/foundation/env/user-dirs.d.mts +39 -0
- package/foundation/env/user-dirs.mjs +93 -0
- package/foundation/env/user-dirs.test.mjs +72 -0
- package/foundation/env/user-settings.d.mts +46 -0
- package/foundation/env/user-settings.mjs +106 -0
- package/foundation/env/user-settings.test.mjs +64 -0
- package/foundation/fs/file-protection.mjs +15 -1
- package/foundation/fs/module-loader.d.mts +2 -1
- package/foundation/fs/module-loader.mjs +60 -4
- package/foundation/fs/module-loader.stdout.test.mjs +3 -2
- package/foundation/fs/module-loader.test.mjs +194 -0
- package/foundation/fs/path-compare.d.mts +33 -0
- package/foundation/fs/path-compare.mjs +73 -0
- package/foundation/fs/paths.d.mts +57 -1
- package/foundation/fs/paths.mjs +131 -3
- package/foundation/fs/paths.test.mjs +10 -1
- package/foundation/integrations/computer-integrations.d.mts +78 -0
- package/foundation/integrations/computer-integrations.mjs +316 -0
- package/foundation/integrations/computer-integrations.test.mjs +292 -0
- package/foundation/integrations/contribution-inventory.d.mts +11 -0
- package/foundation/integrations/contribution-inventory.mjs +56 -3
- package/foundation/integrations/integration-warnings.mjs +3 -2
- package/foundation/integrations/integration-warnings.test.mjs +8 -9
- package/foundation/integrations/integrations.d.mts +25 -1
- package/foundation/integrations/integrations.mjs +46 -3
- package/foundation/integrations/issue.ts +6 -0
- package/foundation/integrations/validate-contributions.d.mts +2 -0
- package/foundation/integrations/validate-contributions.mjs +41 -1
- package/foundation/release/agent-text-worker.d.mts +4 -0
- package/foundation/release/agent-text-worker.mjs +52 -0
- package/foundation/release/cache.d.mts +310 -0
- package/foundation/release/cache.mjs +1216 -0
- package/foundation/release/cache.test.mjs +920 -0
- package/foundation/release/choose.d.mts +207 -0
- package/foundation/release/choose.mjs +677 -0
- package/foundation/release/choose.test.mjs +427 -0
- package/foundation/release/core-worker.d.mts +4 -0
- package/foundation/release/core-worker.mjs +38 -0
- package/foundation/release/global-state.d.mts +259 -0
- package/foundation/release/global-state.mjs +472 -0
- package/foundation/release/global-state.test.mjs +381 -0
- package/foundation/release/handoff.d.mts +60 -0
- package/foundation/release/handoff.e2e.test.mjs +278 -0
- package/foundation/release/handoff.mjs +357 -0
- package/foundation/release/lock-lease.d.mts +23 -0
- package/foundation/release/lock-lease.mjs +80 -0
- package/foundation/release/notices.d.mts +253 -0
- package/foundation/release/notices.mjs +596 -0
- package/foundation/release/notices.test.mjs +565 -0
- package/foundation/release/npm-guard.d.mts +4 -0
- package/foundation/release/npm-guard.mjs +126 -0
- package/foundation/release/project.d.mts +141 -0
- package/foundation/release/project.mjs +466 -0
- package/foundation/release/project.test.mjs +288 -0
- package/foundation/release/refresh-worker.d.mts +4 -0
- package/foundation/release/refresh-worker.mjs +27 -0
- package/foundation/release/run-context.d.mts +47 -0
- package/foundation/release/run-context.mjs +149 -0
- package/foundation/release/update-notice-env.d.mts +28 -0
- package/foundation/release/update-notice-env.mjs +39 -0
- package/foundation/response/base.d.ts +26 -3
- package/foundation/response/error-codes.d.mts +3 -1
- package/foundation/response/error-codes.doc.mjs +12 -0
- package/foundation/response/error-codes.mjs +17 -0
- package/foundation/response/error-codes.test.mjs +2 -0
- package/foundation/response/json-contract.test.mjs +7 -2
- package/foundation/response/json.mjs +4 -1
- package/foundation/response/response-types.doc.mjs +22 -11
- package/foundation/response/response-types.doc.test.mjs +46 -0
- package/foundation/response/response.doc.mjs +3 -3
- package/foundation/xle/browser-imports.test.mjs +74 -0
- package/foundation/xle/registry.mjs +4 -0
- package/foundation/xle/registry.test.mjs +28 -0
- package/package.json +11 -11
- package/scripts/postinstall.mjs +24 -5
package/api/search/search.mjs
CHANGED
|
@@ -57,9 +57,10 @@
|
|
|
57
57
|
* Between domains, ranking adds one rule ahead of the score: domain priority
|
|
58
58
|
* (see {@link domainPriority}). A component, hook, template, or theme whose
|
|
59
59
|
* name or keyword a query word hits ranks ahead of every doc the reader did not
|
|
60
|
-
* ask for by name or
|
|
61
|
-
*
|
|
62
|
-
* query holds,
|
|
60
|
+
* ask for by name, title, or a declared phrase, so `font size` finds `Text`
|
|
61
|
+
* before a token page whose heading holds the phrase. A doc the query names,
|
|
62
|
+
* whose title the query holds, or that declares the whole query as a keyword
|
|
63
|
+
* keeps its place.
|
|
63
64
|
*
|
|
64
65
|
* Description and guidance are separate tiers on purpose. A component's own
|
|
65
66
|
* one-line description saying "notification" is a claim about what it IS; the
|
|
@@ -81,11 +82,19 @@
|
|
|
81
82
|
|
|
82
83
|
import {withListingCache} from '../../foundation/fs/listing-cache.mjs';
|
|
83
84
|
import {readDocView} from '../../foundation/doc-compiler/read.mjs';
|
|
84
|
-
import {
|
|
85
|
+
import {discoverExternalPackages} from '../../foundation/fs/paths.mjs';
|
|
86
|
+
import {requireSearchCoreDir} from './_adapter.mjs';
|
|
87
|
+
import {isOutsideProject} from '../../foundation/env/project-scope.mjs';
|
|
85
88
|
import {
|
|
86
89
|
discoverComponents,
|
|
87
90
|
discoverValidIntegrationComponents,
|
|
91
|
+
docStem,
|
|
92
|
+
familyMember,
|
|
88
93
|
findComponentReadme,
|
|
94
|
+
forEachIntegrationDoc,
|
|
95
|
+
integrationMemberEntry,
|
|
96
|
+
integrationRecordHold,
|
|
97
|
+
isIntegrationMember,
|
|
89
98
|
resolveImportPath,
|
|
90
99
|
resolveIntegrationImportPath,
|
|
91
100
|
CORE_PACKAGE,
|
|
@@ -97,6 +106,7 @@ import {
|
|
|
97
106
|
import {
|
|
98
107
|
loadComponentReplacements,
|
|
99
108
|
loadIntegrationsSafely,
|
|
109
|
+
resolveLegacyExternalDoc,
|
|
100
110
|
} from '../component/_adapter.mjs';
|
|
101
111
|
import {levenshteinDistance} from '../../foundation/text/string-utils.mjs';
|
|
102
112
|
import {discoverTemplates, extractComponents} from '../template/template.mjs';
|
|
@@ -115,9 +125,10 @@ import {
|
|
|
115
125
|
sectionKey,
|
|
116
126
|
sectionSummary,
|
|
117
127
|
} from '../../foundation/discovery/docs-section-key.mjs';
|
|
118
|
-
import {AstryxError} from '../error.mjs';
|
|
128
|
+
import {AstryxError, coreDirOrThrow} from '../error.mjs';
|
|
119
129
|
import {ERROR_CODES} from '../../foundation/response/error-codes.mjs';
|
|
120
130
|
import {setResultCoverage} from './coverage.mjs';
|
|
131
|
+
import {linkSearchResponse} from '../component/_adapter.mjs';
|
|
121
132
|
|
|
122
133
|
/**
|
|
123
134
|
* A search candidate gathered from one content domain. Extra underscore-
|
|
@@ -128,6 +139,9 @@ import {setResultCoverage} from './coverage.mjs';
|
|
|
128
139
|
* @property {string[]} [aliases] - Other names the candidate answers to, scored
|
|
129
140
|
* with the same name signals: a theme's display name.
|
|
130
141
|
* @property {string[]} [keywords]
|
|
142
|
+
* @property {string[]} [_declared] - A doc's own declared keywords: what its
|
|
143
|
+
* author lists in `keywords`, without the code terms and table cells its
|
|
144
|
+
* other keywords add.
|
|
131
145
|
* @property {string[]} [weakKeywords]
|
|
132
146
|
* @property {string} [description]
|
|
133
147
|
* @property {string[]} [prose]
|
|
@@ -149,6 +163,9 @@ import {setResultCoverage} from './coverage.mjs';
|
|
|
149
163
|
* @property {'page'|'block'} [_kind]
|
|
150
164
|
* @property {string} [_resultName]
|
|
151
165
|
* @property {string} [_commandName]
|
|
166
|
+
* @property {'file'|'doc'} [_hold] - How an integration component holds its
|
|
167
|
+
* name, for the owner rule.
|
|
168
|
+
* @property {string} [_docPath] - An integration component's doc.
|
|
152
169
|
*/
|
|
153
170
|
|
|
154
171
|
/**
|
|
@@ -242,9 +259,34 @@ function wordForms(w) {
|
|
|
242
259
|
if (w.length > 3 && w.endsWith('s')) forms.add(w.slice(0, -1));
|
|
243
260
|
if (w.length > 4 && w.endsWith('es')) forms.add(w.slice(0, -2));
|
|
244
261
|
if (w.length > 4 && w.endsWith('ies')) forms.add(w.slice(0, -3) + 'y');
|
|
262
|
+
// A gerund keeps its verb: "spacing" lost a final e ("space"), and
|
|
263
|
+
// "dragging" doubled a consonant ("drag"). A past participle does not: it
|
|
264
|
+
// usually describes a state ("archived notes"), not the action a catalog
|
|
265
|
+
// word names.
|
|
266
|
+
if (w.length >= 6 && w.endsWith('ing')) {
|
|
267
|
+
const base = w.slice(0, -3);
|
|
268
|
+
forms.add(base);
|
|
269
|
+
forms.add(`${base}e`);
|
|
270
|
+
if (/([b-df-hj-np-tv-z])\1$/.test(base)) forms.add(base.slice(0, -1));
|
|
271
|
+
}
|
|
245
272
|
return forms;
|
|
246
273
|
}
|
|
247
274
|
|
|
275
|
+
/**
|
|
276
|
+
* The singulars a lowercase word may have when it ends in a plural suffix:
|
|
277
|
+
* "tokens" is "token", "boxes" is "box", "entries" is "entry".
|
|
278
|
+
* @param {string} w
|
|
279
|
+
* @returns {string[]}
|
|
280
|
+
*/
|
|
281
|
+
function singulars(w) {
|
|
282
|
+
/** @type {string[]} */
|
|
283
|
+
const out = [];
|
|
284
|
+
if (w.length > 3 && w.endsWith('s')) out.push(w.slice(0, -1));
|
|
285
|
+
if (w.length > 4 && w.endsWith('es')) out.push(w.slice(0, -2));
|
|
286
|
+
if (w.length > 4 && w.endsWith('ies')) out.push(w.slice(0, -3) + 'y');
|
|
287
|
+
return out;
|
|
288
|
+
}
|
|
289
|
+
|
|
248
290
|
/**
|
|
249
291
|
* Whether two lowercase words are the same word, up to plural and stem form.
|
|
250
292
|
* @param {string} a
|
|
@@ -285,9 +327,201 @@ function wordsOf(text) {
|
|
|
285
327
|
function startsAWordOf(term, text) {
|
|
286
328
|
if (term.length < 4) return false;
|
|
287
329
|
if (term.length / String(text).length < 0.5) return false;
|
|
288
|
-
|
|
330
|
+
// The term or its singular may start the word: "tokens" starts
|
|
331
|
+
// "tokenizer" through "token". A singular needs five letters, so "stats"
|
|
332
|
+
// does not start "status".
|
|
333
|
+
const starts = [term, ...singulars(term).filter(f => f.length >= 5)];
|
|
334
|
+
return wordsOf(text).some(
|
|
335
|
+
w => starts.some(f => w.startsWith(f)) || sameWord(term, w),
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* A query with its camelCase and PascalCase identifiers spelled as words, so
|
|
341
|
+
* `SidebarToggle` reads "sidebar toggle" and `useTablePaginationState` "use
|
|
342
|
+
* table pagination state". Plain words, and identifiers joined with `_` or `-`, are
|
|
343
|
+
* unchanged: a joined token that matches nothing stays unmatched.
|
|
344
|
+
* @param {string} query - The query as typed, before lowercasing.
|
|
345
|
+
* @returns {string} Lowercased.
|
|
346
|
+
*/
|
|
347
|
+
export function spellIdentifiers(query) {
|
|
348
|
+
return String(query)
|
|
349
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
350
|
+
.replace(/([A-Z])([A-Z][a-z])/g, '$1 $2')
|
|
351
|
+
.toLowerCase();
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* A name or keyword with its separators removed, lowercased: `Side Nav`,
|
|
356
|
+
* `side-nav`, and `SideNav` all join to `sidenav`.
|
|
357
|
+
* @param {string} text
|
|
358
|
+
* @returns {string}
|
|
359
|
+
*/
|
|
360
|
+
function joined(text) {
|
|
361
|
+
return String(text)
|
|
362
|
+
.toLowerCase()
|
|
363
|
+
.replace(/[^a-z0-9]+/g, '');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* The strongest hit a run of two or more adjacent query words has when it is
|
|
368
|
+
* read as one joined word, and the query words it covers. Readers type a
|
|
369
|
+
* compound name as separate words (`table header cell` is TableHeaderCell,
|
|
370
|
+
* `number field` the keyword `numberfield`), and no single word of the run
|
|
371
|
+
* matches on its own.
|
|
372
|
+
*
|
|
373
|
+
* - 100: the run joins to a component, hook, or template name, or its plural
|
|
374
|
+
* (`table header cells` is TableHeaderCell), or a hook name without its
|
|
375
|
+
* `use` prefix (`table sticky columns` is useTableStickyColumns).
|
|
376
|
+
* - 90: the run joins to a one-word keyword (`number field` is
|
|
377
|
+
* `numberfield`).
|
|
378
|
+
* - 95: the run joins to most of a name, cut short (`dropdown menu radio` is
|
|
379
|
+
* DropdownMenuRadioItem); 92: it is a name's leading words, covering at least half
|
|
380
|
+
* of it (`top nav mega` is TopNavMegaMenu); 70: it is two or more adjacent
|
|
381
|
+
* words of a name that cover at least half of it (`header cell` in
|
|
382
|
+
* TableHeaderCell), the same coverage a word prefix needs. These three
|
|
383
|
+
* are for components and hooks only.
|
|
384
|
+
*
|
|
385
|
+
* A doc's name is a route or key, so docs are left out.
|
|
386
|
+
* @param {string[]} words - The query's words, stopwords included.
|
|
387
|
+
* @param {Candidate} candidate
|
|
388
|
+
* @returns {{score: number, reason: string, covered: Set<string>, alias?: 'partial' | 'keyword', aliasWords?: number} | null}
|
|
389
|
+
* `covered` holds the run's words and every stretch of them joined. `alias`
|
|
390
|
+
* marks a hit on part of a name or on a keyword, not on the whole name.
|
|
391
|
+
*/
|
|
392
|
+
export function compoundHit(words, candidate) {
|
|
393
|
+
if (candidate.domain === 'doc' || words.length < 2) return null;
|
|
394
|
+
const names = [candidate.name, ...(candidate.aliases ?? [])]
|
|
395
|
+
.filter(Boolean)
|
|
396
|
+
.map(name => ({name, nameJoined: joined(name), nameWords: wordsOf(name)}));
|
|
397
|
+
// Part of a name counts only for a component or hook. A template's name
|
|
398
|
+
// starts with the component it shows (`ProgressBarShowcase`), so part of it
|
|
399
|
+
// names the component, not the template.
|
|
400
|
+
const partial =
|
|
401
|
+
candidate.domain === 'component' || candidate.domain === 'hook';
|
|
402
|
+
// One-word lowercase keywords: a keyword an author declares for a reader
|
|
403
|
+
// to type, joined (`numberfield`). A capitalized one names another entry (a
|
|
404
|
+
// block's `componentsUsed`), which matches by its own name, and a keyword of
|
|
405
|
+
// several words is a phrase, matched as one.
|
|
406
|
+
const keywords = (candidate.keywords ?? []).filter(k =>
|
|
407
|
+
/^[a-z0-9]+$/.test(String(k)),
|
|
408
|
+
);
|
|
409
|
+
/** @type {{score: number, reason: string, covered: Set<string>, alias?: 'partial' | 'keyword', aliasWords?: number} | null} */
|
|
410
|
+
let best = null;
|
|
411
|
+
for (const {run, word} of queryRuns(words)) {
|
|
412
|
+
let score = 0;
|
|
413
|
+
let reason = '';
|
|
414
|
+
/** @type {'partial' | 'keyword' | null} */
|
|
415
|
+
let kind = null;
|
|
416
|
+
for (const {name, nameJoined, nameWords} of names) {
|
|
417
|
+
if (
|
|
418
|
+
nameJoined === word ||
|
|
419
|
+
pluralOf(word, nameJoined) ||
|
|
420
|
+
(candidate.domain === 'hook' && nameJoined === `use${word}`)
|
|
421
|
+
) {
|
|
422
|
+
if (score < 100)
|
|
423
|
+
[score, reason, kind] = [100, `name ${name} spelled`, null];
|
|
424
|
+
} else if (!partial) {
|
|
425
|
+
continue;
|
|
426
|
+
} else if (
|
|
427
|
+
nameJoined.startsWith(word) &&
|
|
428
|
+
word.length / nameJoined.length >= 0.75
|
|
429
|
+
) {
|
|
430
|
+
// Most of the name, cut short: `dropdown menu radio` is
|
|
431
|
+
// DropdownMenuRadioItem.
|
|
432
|
+
if (score < 95)
|
|
433
|
+
[score, reason, kind] = [95, `name ${name} spelled`, 'partial'];
|
|
434
|
+
} else if (
|
|
435
|
+
word.length / nameJoined.length >= 0.5 &&
|
|
436
|
+
run.every(
|
|
437
|
+
(w, j) => nameWords[j + (nameWords[0] === 'use' ? 1 : 0)] === w,
|
|
438
|
+
)
|
|
439
|
+
) {
|
|
440
|
+
// The name's leading words: `top nav mega` is TopNavMegaMenu.
|
|
441
|
+
if (score < 92)
|
|
442
|
+
[score, reason, kind] = [
|
|
443
|
+
92,
|
|
444
|
+
`name ${name} starts "${run.join(' ')}"`,
|
|
445
|
+
'partial',
|
|
446
|
+
];
|
|
447
|
+
} else if (
|
|
448
|
+
word.length / nameJoined.length >= 0.5 &&
|
|
449
|
+
nameWords.some((_, k) => run.every((w, j) => nameWords[k + j] === w))
|
|
450
|
+
) {
|
|
451
|
+
if (score < 70)
|
|
452
|
+
[score, reason, kind] = [
|
|
453
|
+
70,
|
|
454
|
+
`name ${name} holds "${run.join(' ')}"`,
|
|
455
|
+
'partial',
|
|
456
|
+
];
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
if (score < 90 && keywords.includes(word)) {
|
|
460
|
+
[score, reason, kind] = [
|
|
461
|
+
90,
|
|
462
|
+
`keyword "${run.join(' ')}" spelled`,
|
|
463
|
+
'keyword',
|
|
464
|
+
];
|
|
465
|
+
}
|
|
466
|
+
if (score > (best?.score ?? 0)) {
|
|
467
|
+
// Each word of the run, and each identifier typed as several of its
|
|
468
|
+
// words joined (`dropdownMenu` in `dropdownMenu checkbox`).
|
|
469
|
+
const covered = new Set();
|
|
470
|
+
for (let a = 0; a < run.length; a++) {
|
|
471
|
+
for (let b = a + 1; b <= run.length; b++) {
|
|
472
|
+
covered.add(run.slice(a, b).join(''));
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
best = {
|
|
476
|
+
score,
|
|
477
|
+
reason,
|
|
478
|
+
covered,
|
|
479
|
+
...(kind ? {alias: kind, aliasWords: run.length} : {}),
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return best;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* The longest run of adjacent query words read as one name. Core's longest
|
|
488
|
+
* names run to six words; a longer run is a sentence, not a name, and reading
|
|
489
|
+
* every run of a long build idea costs time on every candidate.
|
|
490
|
+
*/
|
|
491
|
+
const MAX_RUN_WORDS = 6;
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Each run of two to {@link MAX_RUN_WORDS} adjacent query words, longest
|
|
495
|
+
* first, with the run joined into one word. Computed once per query.
|
|
496
|
+
* @type {WeakMap<string[], {run: string[], word: string}[]>}
|
|
497
|
+
*/
|
|
498
|
+
const runsOf = new WeakMap();
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* @param {string[]} words
|
|
502
|
+
* @returns {{run: string[], word: string}[]}
|
|
503
|
+
*/
|
|
504
|
+
function queryRuns(words) {
|
|
505
|
+
let runs = runsOf.get(words);
|
|
506
|
+
if (runs) return runs;
|
|
507
|
+
runs = [];
|
|
508
|
+
for (let len = Math.min(words.length, MAX_RUN_WORDS); len >= 2; len--) {
|
|
509
|
+
for (let i = 0; i + len <= words.length; i++) {
|
|
510
|
+
const run = words.slice(i, i + len);
|
|
511
|
+
runs.push({run, word: run.join('')});
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
runsOf.set(words, runs);
|
|
515
|
+
return runs;
|
|
289
516
|
}
|
|
290
517
|
|
|
518
|
+
/**
|
|
519
|
+
* Each candidate's compound hit from its last scoreQuery, so domainPriority
|
|
520
|
+
* reads it without walking the query's runs again.
|
|
521
|
+
* @type {WeakMap<object, ReturnType<typeof compoundHit>>}
|
|
522
|
+
*/
|
|
523
|
+
const compoundOf = new WeakMap();
|
|
524
|
+
|
|
291
525
|
/**
|
|
292
526
|
* The fewest letters both words need before an edit distance counts as a
|
|
293
527
|
* typo, by distance. Below them, one edit usually makes a different word.
|
|
@@ -500,19 +734,20 @@ const STRONG_TOKEN_SCORE = 70;
|
|
|
500
734
|
* typography/font-setup, `side panel` is "Side panels");
|
|
501
735
|
* - 1: a word of the query is the topic's name (`illustration` in a longer
|
|
502
736
|
* question is the illustrations guide);
|
|
737
|
+
* - 1: a query of two or more words is one of the topic's own declared
|
|
738
|
+
* keywords, whatever the plural (`code review` on a guide that declares it);
|
|
503
739
|
* - 1: the query holds one of the doc's titles whole (`resizable side panels`
|
|
504
740
|
* names "Side panels"; `light dark mode button` names "Light/Dark Mode");
|
|
505
741
|
* - 1: the doc matches every word of the query and one of them is a word of
|
|
506
742
|
* its title (`switch to a dark theme` and "Use a theme").
|
|
507
743
|
* A section answers to its topic's name.
|
|
508
744
|
*
|
|
509
|
-
* A doc that matched only by
|
|
745
|
+
* A doc that matched only by a heading holding the query, by one word, or by
|
|
510
746
|
* words spread through its text has no priority. Docs are split into many
|
|
511
|
-
* small topics,
|
|
512
|
-
*
|
|
513
|
-
*
|
|
514
|
-
*
|
|
515
|
-
* above the component. Broad reference pages match every word of many queries
|
|
747
|
+
* small topics, each with its own headings, so a common phrase such as
|
|
748
|
+
* `font size` hits a heading exactly (170) while the component the reader is
|
|
749
|
+
* after matches one word by keyword (`Text`, 98). Ranked on text alone, every
|
|
750
|
+
* split adds another doc above the component. Broad reference pages match every word of many queries
|
|
516
751
|
* in their text the same way. The score stays the text-match strength the
|
|
517
752
|
* result reports, so callers that gate on it (`build`) see the same numbers.
|
|
518
753
|
*
|
|
@@ -523,7 +758,13 @@ const STRONG_TOKEN_SCORE = 70;
|
|
|
523
758
|
* candidate's scoreQuery result.
|
|
524
759
|
* @returns {0 | 1 | 2}
|
|
525
760
|
*/
|
|
526
|
-
export function domainPriority(
|
|
761
|
+
export function domainPriority(
|
|
762
|
+
term,
|
|
763
|
+
tokens,
|
|
764
|
+
candidate,
|
|
765
|
+
hit,
|
|
766
|
+
queryWords = phraseWords(term),
|
|
767
|
+
) {
|
|
527
768
|
const words = [term, ...tokens];
|
|
528
769
|
if (candidate.domain === 'doc') {
|
|
529
770
|
const topic = String(candidate._topic ?? candidate.name).toLowerCase();
|
|
@@ -538,6 +779,20 @@ export function domainPriority(term, tokens, candidate, hit) {
|
|
|
538
779
|
if (named.some(n => samePhrase(term, n))) {
|
|
539
780
|
return 2;
|
|
540
781
|
}
|
|
782
|
+
// A query of two or more words that is one of the doc's own declared
|
|
783
|
+
// keywords, read as words whatever the plural, is the author naming the
|
|
784
|
+
// doc for that phrase: `code review` on a guide that declares it. Only
|
|
785
|
+
// what the author declared counts: the code terms and table cells a
|
|
786
|
+
// doc's other keywords add (`missing_field`) name what it mentions, not
|
|
787
|
+
// what it is for. One word is left out too, and a section declares
|
|
788
|
+
// nothing.
|
|
789
|
+
if (
|
|
790
|
+
phraseWords(term).length >= 2 &&
|
|
791
|
+
candidate._section == null &&
|
|
792
|
+
(candidate._declared ?? []).some(k => samePhrase(term, String(k)))
|
|
793
|
+
) {
|
|
794
|
+
return 1;
|
|
795
|
+
}
|
|
541
796
|
if (
|
|
542
797
|
words.some(
|
|
543
798
|
w =>
|
|
@@ -568,6 +823,12 @@ export function domainPriority(term, tokens, candidate, hit) {
|
|
|
568
823
|
return 0;
|
|
569
824
|
}
|
|
570
825
|
if (hit.score >= FULL_COVERAGE_SCORE) return 1;
|
|
826
|
+
const compound = compoundOf.has(candidate)
|
|
827
|
+
? compoundOf.get(candidate)
|
|
828
|
+
: compoundHit(queryWords, candidate);
|
|
829
|
+
if ((compound?.score ?? 0) >= STRONG_TOKEN_SCORE) {
|
|
830
|
+
return 1;
|
|
831
|
+
}
|
|
571
832
|
const fuzzy = tokens.length <= 1;
|
|
572
833
|
return words.some(
|
|
573
834
|
w =>
|
|
@@ -683,14 +944,24 @@ export function titleInQuery(term, titles) {
|
|
|
683
944
|
* @param {string} term - Lowercased full query.
|
|
684
945
|
* @param {string[]} tokens - Content tokens from tokenizeQuery(term).
|
|
685
946
|
* @param {Candidate} candidate
|
|
686
|
-
* @
|
|
947
|
+
* @param {string[]} [words] - The query's words for compound names (see
|
|
948
|
+
* compoundHit); empty when the query names an entry outright.
|
|
949
|
+
* @param {{plain?: boolean}} [opts] - `plain`: score without matches on part
|
|
950
|
+
* of a name or a joined keyword.
|
|
951
|
+
* @returns {{score: number, reason: string, matched: number, total: number, alias?: 'partial' | 'keyword', aliasWords?: number} | null}
|
|
687
952
|
* `matched`/`total` are the query concepts this candidate answered, out of
|
|
688
953
|
* the concepts the query had. Callers that must distinguish "matched one word
|
|
689
954
|
* of three" from "matched all three" — `build`, which gates its pages group
|
|
690
955
|
* on coverage — cannot recover that from the score, because a single strong
|
|
691
956
|
* hit and a broad weak one land on the same number.
|
|
692
957
|
*/
|
|
693
|
-
export function scoreQuery(
|
|
958
|
+
export function scoreQuery(
|
|
959
|
+
term,
|
|
960
|
+
tokens,
|
|
961
|
+
candidate,
|
|
962
|
+
words = phraseWords(term),
|
|
963
|
+
{plain = false} = {},
|
|
964
|
+
) {
|
|
694
965
|
const total = Math.max(tokens.length, 1);
|
|
695
966
|
// A whole-phrase hit answered the whole query by definition.
|
|
696
967
|
const asFull = (/** @type {{score: number, reason: string}} */ hit) => ({
|
|
@@ -699,14 +970,24 @@ export function scoreQuery(term, tokens, candidate) {
|
|
|
699
970
|
total,
|
|
700
971
|
});
|
|
701
972
|
// Typo tolerance is for one-word lookups. In a multi-word query a near miss
|
|
702
|
-
// is usually a different word, not a typo.
|
|
703
|
-
|
|
704
|
-
const
|
|
973
|
+
// is usually a different word, not a typo. One typed identifier is one
|
|
974
|
+
// word, whatever words it spells.
|
|
975
|
+
const fuzzy = tokens.length <= 1 || !/\s/.test(term);
|
|
976
|
+
const full = scoreCandidate(term, candidate, {
|
|
977
|
+
fuzzy,
|
|
978
|
+
joins: !plain && words.length > 0,
|
|
979
|
+
});
|
|
705
980
|
// A query of several words keeps its phrase tiers below even when stopwords
|
|
706
981
|
// leave one content word: "make an integration" is still the phrase an
|
|
707
982
|
// author declares as a keyword, and "build an integration" still names a
|
|
708
983
|
// title outright, though each tokenizes to `integration` alone.
|
|
709
|
-
const phrase = phraseWords(term).length >= 2;
|
|
984
|
+
const phrase = phraseWords(term).length >= 2 || words.length >= 2;
|
|
985
|
+
let compound = compoundHit(words, candidate);
|
|
986
|
+
if (plain) {
|
|
987
|
+
if (compound?.alias) compound = null;
|
|
988
|
+
} else {
|
|
989
|
+
compoundOf.set(candidate, compound);
|
|
990
|
+
}
|
|
710
991
|
|
|
711
992
|
/** 0–1 content tokens: whole-phrase fuzzy matching (typo tolerance for
|
|
712
993
|
* single words), but if stopwords left exactly one DIFFERENT token (e.g.
|
|
@@ -731,7 +1012,11 @@ export function scoreQuery(term, tokens, candidate) {
|
|
|
731
1012
|
// of Table-related templates each match "table" and "contents" separately
|
|
732
1013
|
// and accumulate a higher raw score (#5239).
|
|
733
1014
|
if (full && full.score >= 90) {
|
|
734
|
-
return asFull({
|
|
1015
|
+
return asFull({
|
|
1016
|
+
score: full.score + 100,
|
|
1017
|
+
reason: full.reason,
|
|
1018
|
+
...(full.alias ? {alias: full.alias, aliasWords: words.length} : {}),
|
|
1019
|
+
});
|
|
735
1020
|
}
|
|
736
1021
|
|
|
737
1022
|
// The whole query standing as a phrase in a doc's title or one of its
|
|
@@ -757,8 +1042,25 @@ export function scoreQuery(term, tokens, candidate) {
|
|
|
757
1042
|
let tokenSum = 0;
|
|
758
1043
|
/** @type {string[]} */
|
|
759
1044
|
const hitTerms = [];
|
|
1045
|
+
// Adjacent words that join into a name or keyword answer each word they
|
|
1046
|
+
// join, as strongly as the joined word matches (see compoundHit).
|
|
1047
|
+
// Whether a counted word matched only through part of a name or a keyword
|
|
1048
|
+
// read joined, not through the entry's own name or a word of it.
|
|
1049
|
+
/** @type {'partial' | 'keyword' | false} */
|
|
1050
|
+
let alias = false;
|
|
1051
|
+
let aliasWords = 0;
|
|
760
1052
|
for (const tok of tokens) {
|
|
761
|
-
|
|
1053
|
+
let h = bestForToken(tok, candidate, {fuzzy});
|
|
1054
|
+
if (
|
|
1055
|
+
compound?.covered.has(joined(tok)) &&
|
|
1056
|
+
compound.score > (h?.score ?? 0)
|
|
1057
|
+
) {
|
|
1058
|
+
h = compound;
|
|
1059
|
+
if (compound.alias && compound.score >= MIN_TOKEN_SCORE) {
|
|
1060
|
+
alias = alias === 'partial' ? alias : compound.alias;
|
|
1061
|
+
aliasWords = compound.aliasWords ?? 0;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
762
1064
|
if (h && h.score >= MIN_TOKEN_SCORE) {
|
|
763
1065
|
if (h.score > strongest) strongest = h.score;
|
|
764
1066
|
matched++;
|
|
@@ -808,6 +1110,7 @@ export function scoreQuery(term, tokens, candidate) {
|
|
|
808
1110
|
reason,
|
|
809
1111
|
matched,
|
|
810
1112
|
total,
|
|
1113
|
+
...(alias ? {alias, aliasWords} : {}),
|
|
811
1114
|
};
|
|
812
1115
|
}
|
|
813
1116
|
|
|
@@ -833,7 +1136,13 @@ export function scoreQuery(term, tokens, candidate) {
|
|
|
833
1136
|
strongest + Math.min(matched - 1, 3) * 12 + coverage * 15,
|
|
834
1137
|
);
|
|
835
1138
|
if (full && full.score >= tokenScore) return asFull(full);
|
|
836
|
-
return {
|
|
1139
|
+
return {
|
|
1140
|
+
score: tokenScore,
|
|
1141
|
+
reason,
|
|
1142
|
+
matched,
|
|
1143
|
+
total,
|
|
1144
|
+
...(alias ? {alias, aliasWords} : {}),
|
|
1145
|
+
};
|
|
837
1146
|
}
|
|
838
1147
|
|
|
839
1148
|
/**
|
|
@@ -852,8 +1161,9 @@ export function scoreQuery(term, tokens, candidate) {
|
|
|
852
1161
|
* @param {string} [candidate.description]
|
|
853
1162
|
* @param {string[]} [candidate.prose] - Extra free-text blobs (doc section text, best practices).
|
|
854
1163
|
* @param {string[]} [candidate.guidance] - Usage guidance (features, best practices) — scored a tier below description.
|
|
855
|
-
* @param {{fuzzy?: boolean}} [opts] - `fuzzy`: allow edit-distance (typo) matches. Default true; multi-word queries pass false.
|
|
856
|
-
* @returns {{score: number, reason: string} | null}
|
|
1164
|
+
* @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.
|
|
1165
|
+
* @returns {{score: number, reason: string, alias?: 'partial' | 'keyword'} | null}
|
|
1166
|
+
* `alias` marks a hit that read the query's words joined.
|
|
857
1167
|
*/
|
|
858
1168
|
export function scoreCandidate(
|
|
859
1169
|
term,
|
|
@@ -867,18 +1177,23 @@ export function scoreCandidate(
|
|
|
867
1177
|
prose = [],
|
|
868
1178
|
guidance = [],
|
|
869
1179
|
},
|
|
870
|
-
{fuzzy = true} = {},
|
|
1180
|
+
{fuzzy = true, joins = true} = {},
|
|
871
1181
|
) {
|
|
872
1182
|
let best = 0;
|
|
873
1183
|
let reason = '';
|
|
1184
|
+
/** @type {'partial' | 'keyword' | false} */
|
|
1185
|
+
let alias = false;
|
|
874
1186
|
/**
|
|
875
1187
|
* @param {number} score
|
|
876
1188
|
* @param {string} why
|
|
1189
|
+
* @param {'partial' | 'keyword' | false} [byAlias] - the hit reads the
|
|
1190
|
+
* query's words joined, into part of a name or into a keyword
|
|
877
1191
|
*/
|
|
878
|
-
const consider = (score, why) => {
|
|
1192
|
+
const consider = (score, why, byAlias = false) => {
|
|
879
1193
|
if (score > best) {
|
|
880
1194
|
best = score;
|
|
881
1195
|
reason = why;
|
|
1196
|
+
alias = byAlias;
|
|
882
1197
|
}
|
|
883
1198
|
};
|
|
884
1199
|
|
|
@@ -902,6 +1217,16 @@ export function scoreCandidate(
|
|
|
902
1217
|
nameLower === term.replace(/\s+/g, '');
|
|
903
1218
|
if (nameLower === term || leafLower === term || spelled) {
|
|
904
1219
|
consider(100, 'exact name');
|
|
1220
|
+
} else if (
|
|
1221
|
+
joins &&
|
|
1222
|
+
(domain === 'component' || domain === 'hook') &&
|
|
1223
|
+
/\s/.test(term) &&
|
|
1224
|
+
nameLower.startsWith(term.replace(/\s+/g, '')) &&
|
|
1225
|
+
term.replace(/\s+/g, '').length / nameLower.length >= 0.75
|
|
1226
|
+
) {
|
|
1227
|
+
// Most of the name typed as words, cut short: `complex select` is
|
|
1228
|
+
// ComplexSelector, ahead of a one-word keyword elsewhere.
|
|
1229
|
+
consider(95, `name "${candidateName}" spelled`, 'partial');
|
|
905
1230
|
} else if (pluralOf(nameLower, term) || pluralOf(term, nameLower)) {
|
|
906
1231
|
// One point under the exact spelling, so the doc named `tokens` still
|
|
907
1232
|
// outranks the Token component for `tokens`.
|
|
@@ -925,12 +1250,27 @@ export function scoreCandidate(
|
|
|
925
1250
|
}
|
|
926
1251
|
|
|
927
1252
|
// ── Keyword signals ─────────────────────────────────────────────
|
|
1253
|
+
const termJoined = joined(term);
|
|
928
1254
|
for (const kw of keywords) {
|
|
929
1255
|
const kwLower = String(kw).toLowerCase();
|
|
1256
|
+
// A one-word keyword typed as separate words is the keyword: `number
|
|
1257
|
+
// field` is `numberfield`. Only a
|
|
1258
|
+
// lowercase keyword, one an author declares for a reader to type: a
|
|
1259
|
+
// capitalized one names another entry (a block's `componentsUsed`). A
|
|
1260
|
+
// doc's keywords include code terms, matched as written.
|
|
930
1261
|
if (kwLower === term) {
|
|
931
1262
|
consider(90, `keyword "${kw}"`);
|
|
932
1263
|
continue;
|
|
933
1264
|
}
|
|
1265
|
+
if (
|
|
1266
|
+
joins &&
|
|
1267
|
+
domain !== 'doc' &&
|
|
1268
|
+
/\s/.test(term) &&
|
|
1269
|
+
String(kw) === termJoined
|
|
1270
|
+
) {
|
|
1271
|
+
consider(90, `keyword "${kw}"`, 'keyword');
|
|
1272
|
+
continue;
|
|
1273
|
+
}
|
|
934
1274
|
if (sameWord(term, kwLower)) {
|
|
935
1275
|
consider(88, `keyword "${kw}"`);
|
|
936
1276
|
continue;
|
|
@@ -962,9 +1302,12 @@ export function scoreCandidate(
|
|
|
962
1302
|
// Match the term's stem as a whole word, tolerating plural/gerund suffixes
|
|
963
1303
|
// so "chart" matches "charts" and "filter" matches "filtering".
|
|
964
1304
|
if (term.length >= 3) {
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
const
|
|
1305
|
+
// Any form of the term, with any common suffix: `spacing` finds
|
|
1306
|
+
// "space" and "spaces", `tables` finds "table".
|
|
1307
|
+
const roots = [...new Set([stem(term), ...wordForms(term)])]
|
|
1308
|
+
.filter(f => f.length >= 3)
|
|
1309
|
+
.map(f => f.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
1310
|
+
const re = new RegExp(`\\b(?:${roots.join('|')})(s|es|ing|ed|ies)?\\b`);
|
|
968
1311
|
if (description && re.test(description.toLowerCase())) {
|
|
969
1312
|
consider(50, `description mentions "${term}"`);
|
|
970
1313
|
} else {
|
|
@@ -990,7 +1333,7 @@ export function scoreCandidate(
|
|
|
990
1333
|
}
|
|
991
1334
|
}
|
|
992
1335
|
|
|
993
|
-
return best > 0 ? {score: best, reason} : null;
|
|
1336
|
+
return best > 0 ? {score: best, reason, ...(alias ? {alias} : {})} : null;
|
|
994
1337
|
}
|
|
995
1338
|
|
|
996
1339
|
/**
|
|
@@ -1079,7 +1422,11 @@ async function gatherCoreComponents(coreDir) {
|
|
|
1079
1422
|
/** @type {string[]} */
|
|
1080
1423
|
let guidance = [];
|
|
1081
1424
|
if (readme && readme.endsWith('.doc.mjs')) {
|
|
1082
|
-
const
|
|
1425
|
+
const loaded = await loadModuleDoc(readme);
|
|
1426
|
+
// A member documented in its family's doc (VStack in Stack's doc) is
|
|
1427
|
+
// indexed from its own entry: the family's keywords and guidance
|
|
1428
|
+
// describe the family, not the member.
|
|
1429
|
+
const doc = familyMember(loaded, comp) ?? loaded;
|
|
1083
1430
|
if (doc) {
|
|
1084
1431
|
keywords = Array.isArray(doc.keywords) ? doc.keywords : [];
|
|
1085
1432
|
description = doc.usage?.description || doc.description || '';
|
|
@@ -1105,20 +1452,57 @@ async function gatherCoreComponents(coreDir) {
|
|
|
1105
1452
|
* usage/description from each component's .doc.mjs, same as core. Without
|
|
1106
1453
|
* this, an integration component is invisible to `search`/`build` even
|
|
1107
1454
|
* though `component --list`/`component <Name>` already resolve it — the two
|
|
1108
|
-
* discovery paths silently disagreed.
|
|
1455
|
+
* discovery paths silently disagreed. Also returns, per name, the docs that
|
|
1456
|
+
* hold it by their file name and the loadable docs that only record it.
|
|
1109
1457
|
* @param {import('../../foundation/integrations/integrations.mjs').LoadedIntegration[]} loadedIntegrations
|
|
1110
|
-
* @returns {Promise<Candidate[]>}
|
|
1458
|
+
* @returns {Promise<{candidates: Candidate[], fileNamed: Map<string, Set<string>>, documented: Map<string, Set<string>>}>}
|
|
1111
1459
|
*/
|
|
1112
1460
|
async function gatherIntegrationComponents(loadedIntegrations) {
|
|
1113
1461
|
/** @type {Candidate[]} */
|
|
1114
1462
|
const candidates = [];
|
|
1463
|
+
// What the owner rule reads: the docs that hold a name by their file name,
|
|
1464
|
+
// loadable or not and hidden ones too, and the loadable docs that only
|
|
1465
|
+
// record it.
|
|
1466
|
+
/** @type {Map<string, Set<string>>} */
|
|
1467
|
+
const fileNamed = new Map();
|
|
1468
|
+
/** @type {Map<string, Set<string>>} */
|
|
1469
|
+
const documented = new Map();
|
|
1470
|
+
/** @param {Map<string, Set<string>>} map @param {string} name @param {string} docPath */
|
|
1471
|
+
const hold = (map, name, docPath) =>
|
|
1472
|
+
map.set(name, (map.get(name) ?? new Set()).add(docPath));
|
|
1115
1473
|
for (const integration of loadedIntegrations) {
|
|
1116
|
-
const {components} =
|
|
1474
|
+
const {components, discovered} =
|
|
1475
|
+
await discoverValidIntegrationComponents(integration);
|
|
1476
|
+
const renamed = new Set(
|
|
1477
|
+
discovered
|
|
1478
|
+
.filter(rec => integrationRecordHold(rec) === 'doc')
|
|
1479
|
+
.filter(rec => !isIntegrationMember(rec))
|
|
1480
|
+
.map(rec => rec.docPath),
|
|
1481
|
+
);
|
|
1482
|
+
forEachIntegrationDoc(integration.components, (docPath, stem) => {
|
|
1483
|
+
if (!renamed.has(docPath)) hold(fileNamed, stem, docPath);
|
|
1484
|
+
});
|
|
1117
1485
|
for (const rec of components) {
|
|
1118
|
-
const
|
|
1486
|
+
const loaded = await loadModuleDoc(rec.docPath);
|
|
1487
|
+
// A family member is indexed from its own entry: the family's keywords
|
|
1488
|
+
// and guidance describe the family.
|
|
1489
|
+
const member = isIntegrationMember(rec);
|
|
1490
|
+
const doc = member ? integrationMemberEntry(loaded, rec) : loaded;
|
|
1491
|
+
// A doc that names another export of its file still answers to the
|
|
1492
|
+
// file's name, as `component <file name>` does.
|
|
1493
|
+
const fileName = docStem(rec.docPath);
|
|
1494
|
+
if (integrationRecordHold(rec) === 'doc') {
|
|
1495
|
+
hold(documented, rec.name, rec.docPath);
|
|
1496
|
+
}
|
|
1497
|
+
if (renamed.has(rec.docPath) && !member) {
|
|
1498
|
+
hold(documented, fileName, rec.docPath);
|
|
1499
|
+
}
|
|
1119
1500
|
candidates.push({
|
|
1120
1501
|
domain: 'component',
|
|
1121
1502
|
name: rec.name,
|
|
1503
|
+
aliases: !member && fileName !== rec.name ? [fileName] : [],
|
|
1504
|
+
_hold: integrationRecordHold(rec),
|
|
1505
|
+
_docPath: rec.docPath,
|
|
1122
1506
|
keywords: doc && Array.isArray(doc.keywords) ? doc.keywords : [],
|
|
1123
1507
|
description: doc ? doc.usage?.description || doc.description || '' : '',
|
|
1124
1508
|
guidance: guidanceFrom(doc),
|
|
@@ -1138,12 +1522,12 @@ async function gatherIntegrationComponents(loadedIntegrations) {
|
|
|
1138
1522
|
packageName: rec.package,
|
|
1139
1523
|
},
|
|
1140
1524
|
rec.name,
|
|
1141
|
-
|
|
1525
|
+
loaded?.import,
|
|
1142
1526
|
),
|
|
1143
1527
|
});
|
|
1144
1528
|
}
|
|
1145
1529
|
}
|
|
1146
|
-
return candidates;
|
|
1530
|
+
return {candidates, fileNamed, documented};
|
|
1147
1531
|
}
|
|
1148
1532
|
|
|
1149
1533
|
/**
|
|
@@ -1155,11 +1539,12 @@ async function gatherIntegrationComponents(loadedIntegrations) {
|
|
|
1155
1539
|
*/
|
|
1156
1540
|
async function gatherComponents(coreDir, cwd) {
|
|
1157
1541
|
const loadedIntegrations = await loadIntegrationsSafely(cwd);
|
|
1158
|
-
const [core,
|
|
1542
|
+
const [core, gathered, replacements] = await Promise.all([
|
|
1159
1543
|
gatherCoreComponents(coreDir),
|
|
1160
1544
|
gatherIntegrationComponents(loadedIntegrations),
|
|
1161
1545
|
loadComponentReplacements(coreDir, loadedIntegrations),
|
|
1162
1546
|
]);
|
|
1547
|
+
const {candidates: integrations, fileNamed, documented} = gathered;
|
|
1163
1548
|
// An active replacement answers to the Core name it replaces, so a search
|
|
1164
1549
|
// for that name finds the replacement and not the Core original
|
|
1165
1550
|
// (spec:AST-035 FR11).
|
|
@@ -1170,9 +1555,43 @@ async function gatherComponents(coreDir, cwd) {
|
|
|
1170
1555
|
active.target,
|
|
1171
1556
|
]),
|
|
1172
1557
|
);
|
|
1558
|
+
// The owner rule, as component detail applies it: a candidate `component
|
|
1559
|
+
// <name>` does not open names its package and follows the owner, and a
|
|
1560
|
+
// file-name alias that opens another owner is dropped.
|
|
1561
|
+
const coreNames = new Set(core.map(candidate => candidate.name));
|
|
1562
|
+
/** @param {string} name @param {Candidate} candidate */
|
|
1563
|
+
const opens = (name, candidate) =>
|
|
1564
|
+
!coreNames.has(name) &&
|
|
1565
|
+
findComponentReadme(coreDir, name) == null &&
|
|
1566
|
+
[...(fileNamed.get(name) ?? [])].every(doc => doc === candidate._docPath) &&
|
|
1567
|
+
[...(documented.get(name) ?? [])].every(doc => doc === candidate._docPath);
|
|
1568
|
+
// A legacy docs package keeps an exact name over an export name a doc records.
|
|
1569
|
+
/** @type {ReturnType<typeof discoverExternalPackages> | undefined} */
|
|
1570
|
+
let externals;
|
|
1571
|
+
/** @param {string} name */
|
|
1572
|
+
const legacyDocuments = name =>
|
|
1573
|
+
(externals ??= discoverExternalPackages(cwd)).some(
|
|
1574
|
+
ext => resolveLegacyExternalDoc(ext, name) != null,
|
|
1575
|
+
);
|
|
1576
|
+
const ruled = integrations.map(candidate => {
|
|
1577
|
+
const aliases = (candidate.aliases ?? []).filter(alias =>
|
|
1578
|
+
opens(alias, candidate),
|
|
1579
|
+
);
|
|
1580
|
+
return candidate._hold === 'doc' &&
|
|
1581
|
+
(!opens(candidate.name, candidate) || legacyDocuments(candidate.name))
|
|
1582
|
+
? {
|
|
1583
|
+
...candidate,
|
|
1584
|
+
aliases,
|
|
1585
|
+
_command: `astryx component ${candidate.name} --package ${candidate._package}`,
|
|
1586
|
+
}
|
|
1587
|
+
: {...candidate, aliases};
|
|
1588
|
+
});
|
|
1173
1589
|
return [
|
|
1174
1590
|
...core.filter(candidate => !replacements.forTarget(candidate.name)),
|
|
1175
|
-
...
|
|
1591
|
+
...[
|
|
1592
|
+
...ruled.filter(candidate => !candidate._command),
|
|
1593
|
+
...ruled.filter(candidate => candidate._command),
|
|
1594
|
+
].map(candidate => {
|
|
1176
1595
|
const target = targetOf.get(`${candidate._package}\0${candidate.name}`);
|
|
1177
1596
|
if (target != null) {
|
|
1178
1597
|
return {...candidate, aliases: [...(candidate.aliases ?? []), target]};
|
|
@@ -1225,11 +1644,11 @@ export async function componentKeywords(coreDir, cwd) {
|
|
|
1225
1644
|
.flat()
|
|
1226
1645
|
.map(async name => {
|
|
1227
1646
|
const readme = findComponentReadme(coreDir, name);
|
|
1228
|
-
const
|
|
1647
|
+
const loaded =
|
|
1229
1648
|
readme && readme.endsWith('.doc.mjs')
|
|
1230
1649
|
? await loadModuleDoc(readme)
|
|
1231
1650
|
: null;
|
|
1232
|
-
return {name, keywords: keywordsOf(
|
|
1651
|
+
return {name, keywords: keywordsOf(familyMember(loaded, name) ?? loaded)};
|
|
1233
1652
|
});
|
|
1234
1653
|
const loadedIntegrations = await loadIntegrationsSafely(cwd);
|
|
1235
1654
|
const replacements = await loadComponentReplacements(
|
|
@@ -1239,10 +1658,13 @@ export async function componentKeywords(coreDir, cwd) {
|
|
|
1239
1658
|
const integrations = loadedIntegrations.map(async integration => {
|
|
1240
1659
|
const {components} = await discoverValidIntegrationComponents(integration);
|
|
1241
1660
|
return Promise.all(
|
|
1242
|
-
components.map(async rec =>
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1661
|
+
components.map(async rec => {
|
|
1662
|
+
const loaded = await loadModuleDoc(rec.docPath);
|
|
1663
|
+
return {
|
|
1664
|
+
name: rec.name,
|
|
1665
|
+
keywords: keywordsOf(integrationMemberEntry(loaded, rec) ?? loaded),
|
|
1666
|
+
};
|
|
1667
|
+
}),
|
|
1246
1668
|
);
|
|
1247
1669
|
});
|
|
1248
1670
|
return [
|
|
@@ -1416,6 +1838,10 @@ async function gatherDocs(cwd) {
|
|
|
1416
1838
|
...defined,
|
|
1417
1839
|
...codeTerms({content}),
|
|
1418
1840
|
],
|
|
1841
|
+
_declared: [
|
|
1842
|
+
...(Array.isArray(selfDoc?.keywords) ? selfDoc.keywords : []),
|
|
1843
|
+
...(Array.isArray(node.keywords) ? node.keywords : []),
|
|
1844
|
+
],
|
|
1419
1845
|
description: node.summary || '',
|
|
1420
1846
|
prose: sectionProse({title: node.title, content}),
|
|
1421
1847
|
titles: [node.title],
|
|
@@ -1552,6 +1978,7 @@ function topicCandidates(
|
|
|
1552
1978
|
...(doc?.title || title ? [doc?.title || title] : []),
|
|
1553
1979
|
...(Array.isArray(doc?.keywords) ? doc.keywords : []),
|
|
1554
1980
|
],
|
|
1981
|
+
_declared: Array.isArray(doc?.keywords) ? doc.keywords : [],
|
|
1555
1982
|
description: doc?.description || summary,
|
|
1556
1983
|
prose: split
|
|
1557
1984
|
? sections.map(section => section.title).filter(Boolean)
|
|
@@ -1700,8 +2127,11 @@ async function gatherThemes(cwd) {
|
|
|
1700
2127
|
* @param {string} reason
|
|
1701
2128
|
* @param {number} matchedTerms - Query concepts this result answered.
|
|
1702
2129
|
* @param {number} queryTerms - Query concepts there were to answer.
|
|
2130
|
+
* @param {boolean} [outside] - The search runs outside a project, where a
|
|
2131
|
+
* theme cannot be added to an app, so its command forks the source instead
|
|
2132
|
+
* (spec AST-081 FR3).
|
|
1703
2133
|
*/
|
|
1704
|
-
function toResult(c, score, reason, matchedTerms, queryTerms) {
|
|
2134
|
+
function toResult(c, score, reason, matchedTerms, queryTerms, outside = false) {
|
|
1705
2135
|
const base = {
|
|
1706
2136
|
domain: c.domain,
|
|
1707
2137
|
name: c._resultName ?? c.name,
|
|
@@ -1750,7 +2180,9 @@ function toResult(c, score, reason, matchedTerms, queryTerms) {
|
|
|
1750
2180
|
result = {
|
|
1751
2181
|
...base,
|
|
1752
2182
|
displayName: c._displayName,
|
|
1753
|
-
command:
|
|
2183
|
+
command: outside
|
|
2184
|
+
? `astryx theme eject ${c.name}`
|
|
2185
|
+
: `astryx theme add ${c.name}`,
|
|
1754
2186
|
};
|
|
1755
2187
|
break;
|
|
1756
2188
|
default:
|
|
@@ -1811,21 +2243,29 @@ async function searchOnce(query, options) {
|
|
|
1811
2243
|
}
|
|
1812
2244
|
|
|
1813
2245
|
const term = String(query).trim().toLowerCase();
|
|
1814
|
-
const tokens = tokenizeQuery(term);
|
|
1815
|
-
|
|
1816
2246
|
// `astryx docs` reads docs without @astryxdesign/core, and `astryx theme
|
|
1817
2247
|
// list` reads themes without it (bundled themes need no project), so a
|
|
1818
2248
|
// search of either must too. Every other domain reads core: asked for by
|
|
1819
2249
|
// name, it is an error without core; an open search then covers the docs
|
|
1820
2250
|
// and themes alone.
|
|
1821
2251
|
const needsCore = !type || !CORELESS_DOMAINS.includes(type);
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
2252
|
+
// A domain that reads Core gets it as `component` does: the project's, else
|
|
2253
|
+
// the answering release's own, installed into the per-user cache on first
|
|
2254
|
+
// need (spec AST-081 FR3). Asked for by name, a Core that cannot be had is
|
|
2255
|
+
// the error; an open search answers from the rest and says what it skipped
|
|
2256
|
+
// and why, never silently.
|
|
2257
|
+
/** @type {string | null} */
|
|
2258
|
+
let coreDir = null;
|
|
2259
|
+
/** @type {string | null} */
|
|
2260
|
+
let coreSkipped = null;
|
|
2261
|
+
if (needsCore && type) {
|
|
2262
|
+
coreDir = coreDirOrThrow(requireSearchCoreDir, cwd);
|
|
2263
|
+
} else if (needsCore) {
|
|
2264
|
+
try {
|
|
2265
|
+
coreDir = requireSearchCoreDir(cwd);
|
|
2266
|
+
} catch (error) {
|
|
2267
|
+
coreSkipped = /** @type {Error} */ (error).message;
|
|
2268
|
+
}
|
|
1829
2269
|
}
|
|
1830
2270
|
|
|
1831
2271
|
// Gather candidates from each requested domain in parallel.
|
|
@@ -1844,14 +2284,101 @@ async function searchOnce(query, options) {
|
|
|
1844
2284
|
|
|
1845
2285
|
const all = [...components, ...hooks, ...docTopics, ...templates, ...themes];
|
|
1846
2286
|
|
|
2287
|
+
// A query that is one identifier counts as the words it spells
|
|
2288
|
+
// (`SidebarToggle`), and still matches whole as typed. In a query of several
|
|
2289
|
+
// words, each identifier stays one term (`ChatComposer ChatLayout` names two
|
|
2290
|
+
// components), and its words still join into names (see compoundHit). Both
|
|
2291
|
+
// only help a query that names no entry outright: when the query is an
|
|
2292
|
+
// entry's name, typed whole or as its words, that entry answers it, and
|
|
2293
|
+
// neither reading may add near siblings beside it.
|
|
2294
|
+
const spelled = spellIdentifiers(String(query).trim());
|
|
2295
|
+
const exact = all.some(candidate =>
|
|
2296
|
+
[candidate.name, ...(candidate.aliases ?? [])].some(name => {
|
|
2297
|
+
const lower = String(name).toLowerCase();
|
|
2298
|
+
return (
|
|
2299
|
+
lower === term ||
|
|
2300
|
+
(candidate.domain !== 'doc' &&
|
|
2301
|
+
!/[\s_-]/.test(lower) &&
|
|
2302
|
+
lower === term.replace(/\s+/g, ''))
|
|
2303
|
+
);
|
|
2304
|
+
}),
|
|
2305
|
+
);
|
|
2306
|
+
// One identifier is read as its words only when, read whole, it matches
|
|
2307
|
+
// nothing: a prop or hook name a doc already mentions keeps the
|
|
2308
|
+
// entries that mention it.
|
|
2309
|
+
const split =
|
|
2310
|
+
!exact &&
|
|
2311
|
+
!/\s/.test(term) &&
|
|
2312
|
+
spelled !== term &&
|
|
2313
|
+
!all.some(candidate => scoreCandidate(term, candidate));
|
|
2314
|
+
const tokens = tokenizeQuery(split ? spelled : term);
|
|
2315
|
+
/** @type {string[]} */
|
|
2316
|
+
const words = exact
|
|
2317
|
+
? []
|
|
2318
|
+
: phraseWords(split || /\s/.test(term) ? spelled : term);
|
|
2319
|
+
// The entries the query names: a component, hook, or template whose whole
|
|
2320
|
+
// name is adjacent query words joined (`icon button in a card header` names
|
|
2321
|
+
// IconButton), or one identifier typed as such. A name inside a longer named
|
|
2322
|
+
// run does not count (`icon` there is part of IconButton).
|
|
2323
|
+
const queryWords = phraseWords(spelled);
|
|
2324
|
+
const typedIdentifiers = new Set(
|
|
2325
|
+
(String(query).match(/[A-Za-z0-9]+/g) ?? [])
|
|
2326
|
+
.filter(w => /[A-Z]/.test(w))
|
|
2327
|
+
.map(w => w.toLowerCase()),
|
|
2328
|
+
);
|
|
2329
|
+
/** @type {Map<string, [number, number]>} each run joined, longest first */
|
|
2330
|
+
const spans = new Map();
|
|
2331
|
+
for (let len = Math.min(queryWords.length, MAX_RUN_WORDS); len >= 1; len--) {
|
|
2332
|
+
for (let a = 0; a + len <= queryWords.length; a++) {
|
|
2333
|
+
// One plain word names too much (`list`, `field`); a named entry is a
|
|
2334
|
+
// run of joined words, or an identifier typed as one.
|
|
2335
|
+
if (len === 1 && !typedIdentifiers.has(queryWords[a])) continue;
|
|
2336
|
+
const run = queryWords.slice(a, a + len).join('');
|
|
2337
|
+
if (!spans.has(run)) spans.set(run, [a, a + len]);
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
/**
|
|
2341
|
+
* Where a query word is the candidate's whole name, or null: such an entry
|
|
2342
|
+
* keeps the rank its name earns, whatever else it matched.
|
|
2343
|
+
* @param {Candidate} candidate
|
|
2344
|
+
* @returns {[number, number] | null}
|
|
2345
|
+
*/
|
|
2346
|
+
const wordNamed = candidate => {
|
|
2347
|
+
if (candidate.domain === 'doc') return null;
|
|
2348
|
+
for (const name of [candidate.name, ...(candidate.aliases ?? [])]) {
|
|
2349
|
+
const n = joined(name);
|
|
2350
|
+
const at = queryWords.findIndex(w => w === n || w === `${n}s`);
|
|
2351
|
+
if (at >= 0) return [at, at + 1];
|
|
2352
|
+
}
|
|
2353
|
+
return null;
|
|
2354
|
+
};
|
|
2355
|
+
/** @param {Candidate} candidate @returns {[number, number] | null} */
|
|
2356
|
+
const namedSpan = candidate => {
|
|
2357
|
+
if (candidate.domain === 'doc') return null;
|
|
2358
|
+
/** @type {[number, number] | null} */
|
|
2359
|
+
let best = null;
|
|
2360
|
+
for (const name of [candidate.name, ...(candidate.aliases ?? [])]) {
|
|
2361
|
+
const n = joined(name);
|
|
2362
|
+
const span =
|
|
2363
|
+
spans.get(n) ??
|
|
2364
|
+
spans.get(`${n}s`) ??
|
|
2365
|
+
(/(?:s|x|z|ch|sh)$/.test(n) ? spans.get(`${n}es`) : undefined);
|
|
2366
|
+
if (span && (!best || span[1] - span[0] > best[1] - best[0])) {
|
|
2367
|
+
best = span;
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
return best;
|
|
2371
|
+
};
|
|
2372
|
+
|
|
1847
2373
|
// Score every candidate on its own merits. The consumer groups results by
|
|
1848
2374
|
// role (page / block / component) and takes the top of each, so there's no
|
|
1849
2375
|
// cross-role competition to engineer — a target page only needs to be the
|
|
1850
2376
|
// strongest PAGE, not outrank every component.
|
|
1851
|
-
/** @type {{result: any, priority: 0 | 1 | 2}[]} */
|
|
2377
|
+
/** @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}[]} */
|
|
1852
2378
|
const ranked = [];
|
|
2379
|
+
const outside = themes.length > 0 && isOutsideProject(cwd);
|
|
1853
2380
|
for (const candidate of all) {
|
|
1854
|
-
const hit = scoreQuery(term, tokens, candidate);
|
|
2381
|
+
const hit = scoreQuery(term, tokens, candidate, words);
|
|
1855
2382
|
if (hit)
|
|
1856
2383
|
ranked.push({
|
|
1857
2384
|
result: toResult(
|
|
@@ -1860,10 +2387,56 @@ async function searchOnce(query, options) {
|
|
|
1860
2387
|
hit.reason,
|
|
1861
2388
|
hit.matched,
|
|
1862
2389
|
hit.total,
|
|
2390
|
+
outside,
|
|
1863
2391
|
),
|
|
1864
|
-
priority: domainPriority(term, tokens, candidate, hit),
|
|
2392
|
+
priority: domainPriority(term, tokens, candidate, hit, words),
|
|
2393
|
+
key: hit.score,
|
|
2394
|
+
alias: hit.alias ?? false,
|
|
2395
|
+
aliasWords: hit.aliasWords ?? 0,
|
|
2396
|
+
candidate,
|
|
2397
|
+
span: namedSpan(candidate),
|
|
2398
|
+
word: wordNamed(candidate),
|
|
1865
2399
|
});
|
|
1866
2400
|
}
|
|
2401
|
+
// A match on a keyword read joined never outranks an entry the query names,
|
|
2402
|
+
// and neither does a match on part of another name unless it spans more
|
|
2403
|
+
// query words than any named run: `table header with sticky columns` keeps
|
|
2404
|
+
// TableHeader above useTableStickyColumns, which only holds the words
|
|
2405
|
+
// `sticky columns`. Only the order changes; each result keeps the score its
|
|
2406
|
+
// text earned.
|
|
2407
|
+
/** @param {[number, number]} inner @param {[number, number]} outer */
|
|
2408
|
+
const inside = (inner, outer) =>
|
|
2409
|
+
outer[0] <= inner[0] &&
|
|
2410
|
+
outer[1] >= inner[1] &&
|
|
2411
|
+
outer[1] - outer[0] > inner[1] - inner[0];
|
|
2412
|
+
const named = ranked.filter(r => {
|
|
2413
|
+
const span = r.span;
|
|
2414
|
+
return (
|
|
2415
|
+
span != null &&
|
|
2416
|
+
!ranked.some(o => o !== r && o.span != null && inside(span, o.span))
|
|
2417
|
+
);
|
|
2418
|
+
});
|
|
2419
|
+
/** @param {{key: number}[]} rows */
|
|
2420
|
+
const floorOf = rows =>
|
|
2421
|
+
rows.length ? Math.min(...rows.map(r => r.key)) : Infinity;
|
|
2422
|
+
const namedFloor = floorOf(named);
|
|
2423
|
+
const longestNamed = Math.max(
|
|
2424
|
+
0,
|
|
2425
|
+
...named.map(r => (r.span ? r.span[1] - r.span[0] : 0)),
|
|
2426
|
+
);
|
|
2427
|
+
for (const r of ranked) {
|
|
2428
|
+
if (named.includes(r)) continue;
|
|
2429
|
+
if (!r.alias || r.word) continue;
|
|
2430
|
+
// A match on part of a longer name that spans more words than any named
|
|
2431
|
+
// run reads more of the query than the named entry does (`dropdown menu
|
|
2432
|
+
// radio` and DropdownMenuRadioItem, against DropdownMenu), so it is not
|
|
2433
|
+
// held back. A joined keyword always is.
|
|
2434
|
+
if (r.alias === 'partial' && r.aliasWords > longestNamed) continue;
|
|
2435
|
+
// It keeps what it earns without those matches.
|
|
2436
|
+
const own =
|
|
2437
|
+
scoreQuery(term, tokens, r.candidate, words, {plain: true})?.score ?? 0;
|
|
2438
|
+
r.key = Math.max(own, Math.min(r.key, namedFloor - 0.5));
|
|
2439
|
+
}
|
|
1867
2440
|
|
|
1868
2441
|
// Docs and everything else are each sorted by score desc, then domain
|
|
1869
2442
|
// (stable order), then name. The two are merged: at each step the stronger
|
|
@@ -1872,11 +2445,11 @@ async function searchOnce(query, options) {
|
|
|
1872
2445
|
/** @type {Record<string, number>} */
|
|
1873
2446
|
const domainOrder = {component: 0, hook: 1, doc: 2, template: 3, theme: 4};
|
|
1874
2447
|
/**
|
|
1875
|
-
* @param {{result: any}} x
|
|
1876
|
-
* @param {{result: any}} y
|
|
2448
|
+
* @param {{result: any, key: number}} x
|
|
2449
|
+
* @param {{result: any, key: number}} y
|
|
1877
2450
|
*/
|
|
1878
|
-
const byScore = ({result: a}, {result: b}) =>
|
|
1879
|
-
|
|
2451
|
+
const byScore = ({result: a, key: ka}, {result: b, key: kb}) =>
|
|
2452
|
+
kb - ka ||
|
|
1880
2453
|
(domainOrder[a.domain] ?? 9) - (domainOrder[b.domain] ?? 9) ||
|
|
1881
2454
|
a.name.localeCompare(b.name);
|
|
1882
2455
|
const others = ranked.filter(r => r.result.domain !== 'doc').sort(byScore);
|
|
@@ -1923,11 +2496,19 @@ async function searchOnce(query, options) {
|
|
|
1923
2496
|
),
|
|
1924
2497
|
},
|
|
1925
2498
|
};
|
|
2499
|
+
if (coreSkipped) {
|
|
2500
|
+
response.meta = {
|
|
2501
|
+
partial: {
|
|
2502
|
+
skipped: ['component', 'hook', 'template'],
|
|
2503
|
+
reason: coreSkipped,
|
|
2504
|
+
},
|
|
2505
|
+
};
|
|
2506
|
+
}
|
|
1926
2507
|
if (wants('component')) {
|
|
1927
2508
|
searchedComponentsOf.set(
|
|
1928
2509
|
response,
|
|
1929
2510
|
components.map(c => ({name: c.name, keywords: c.keywords ?? []})),
|
|
1930
2511
|
);
|
|
1931
2512
|
}
|
|
1932
|
-
return response;
|
|
2513
|
+
return linkSearchResponse(response, cwd);
|
|
1933
2514
|
}
|