@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,202 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file The words build's page ranker reads: a template's terms and families,
|
|
5
|
+
* an idea's modifiers, containers and phrase pairs, and search's synonyms.
|
|
6
|
+
*
|
|
7
|
+
* Family words come from the templates' own ids — a word in the ids of two or
|
|
8
|
+
* more templates of one family, plus the family's name when a template carries
|
|
9
|
+
* it — never from a list kept here or from synonyms, so an integration's
|
|
10
|
+
* templates join the vocabulary by being named like their family, and "a
|
|
11
|
+
* landing page" is not a hero because "landing" is a synonym of "hero".
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {stem, STOPWORDS, SYNONYMS} from '../../search/search.mjs';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Words search drops that name layout: "side navigation", "side by side".
|
|
18
|
+
*/
|
|
19
|
+
const LAYOUT_WORDS = new Set(['side']);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Where an idea's head ends: its first clause, before the parts it lists.
|
|
23
|
+
*/
|
|
24
|
+
export const HEAD_END =
|
|
25
|
+
/:|,|;|\(|\s[-\u2013\u2014]\s|\b(?:with|showing|listing|for|that|where|plus|including|containing|featuring|which|to|from|of)\b/i;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Frames a page can live in, above whatever is underneath. A container phrase
|
|
29
|
+
* counts only when it names one of these.
|
|
30
|
+
*/
|
|
31
|
+
const OVERLAY_FRAMES = new Set([
|
|
32
|
+
'modal',
|
|
33
|
+
'dialog',
|
|
34
|
+
'drawer',
|
|
35
|
+
'sheet',
|
|
36
|
+
'popup',
|
|
37
|
+
'overlay',
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The family whose templates are app chrome rather than page content: the
|
|
42
|
+
* explicit `Shell -` category (architecture:template-authoring/INV7).
|
|
43
|
+
*/
|
|
44
|
+
export const SHELL_FAMILY = 'Shell';
|
|
45
|
+
|
|
46
|
+
/** A container phrase: "in a modal", "inside the side panel". */
|
|
47
|
+
export const CONTAINER_PHRASE =
|
|
48
|
+
/\b(?:in|inside|within)\s+(?:a|an|the)\s+([a-z-]+)(?:\s+([a-z-]+))?/gi;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Where a phrase ends, for finding the word that heads it: punctuation and
|
|
52
|
+
* the words that join phrases.
|
|
53
|
+
*/
|
|
54
|
+
export const PHRASE_END =
|
|
55
|
+
/[^a-z0-9\s-]+|\s(?:and|or|with|for|of|to|in|on|at|by|from|the|a|an|plus)\s/;
|
|
56
|
+
|
|
57
|
+
/** @param {string} t */
|
|
58
|
+
const isContentWord = t =>
|
|
59
|
+
t.length >= 2 && (LAYOUT_WORDS.has(t) || !STOPWORDS.has(t));
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Content terms of a text: lowercase alphanumeric words, stopwords removed,
|
|
63
|
+
* stemmed.
|
|
64
|
+
* @param {string} text
|
|
65
|
+
* @returns {string[]}
|
|
66
|
+
*/
|
|
67
|
+
export function terms(text) {
|
|
68
|
+
return (
|
|
69
|
+
String(text)
|
|
70
|
+
.toLowerCase()
|
|
71
|
+
.match(/[a-z0-9]+/g) ?? []
|
|
72
|
+
)
|
|
73
|
+
.filter(isContentWord)
|
|
74
|
+
.map(stem);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The terms of an idea that only ever modify another word, each with the words
|
|
79
|
+
* it modifies: in each phrase the last content word is its head, and the ones
|
|
80
|
+
* before it describe the next.
|
|
81
|
+
* @param {string} query
|
|
82
|
+
* @returns {Map<string, Set<string>>}
|
|
83
|
+
*/
|
|
84
|
+
export function modifiersOf(query) {
|
|
85
|
+
const heads = new Set();
|
|
86
|
+
/** @type {Map<string, Set<string>>} */
|
|
87
|
+
const modifiers = new Map();
|
|
88
|
+
for (const phrase of String(query).toLowerCase().split(PHRASE_END)) {
|
|
89
|
+
const words = terms(phrase);
|
|
90
|
+
words.forEach((w, i) => {
|
|
91
|
+
if (i === words.length - 1) heads.add(w);
|
|
92
|
+
else modifiers.set(w, (modifiers.get(w) ?? new Set()).add(words[i + 1]));
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
for (const h of heads) modifiers.delete(h);
|
|
96
|
+
return modifiers;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Adjacent term pairs a text names, "a b", for telling a template that names
|
|
101
|
+
* a whole compound ("executive summary") from one that names only its parts.
|
|
102
|
+
* @param {string[]} texts
|
|
103
|
+
* @returns {Set<string>}
|
|
104
|
+
*/
|
|
105
|
+
export function pairsOf(texts) {
|
|
106
|
+
const pairs = new Set();
|
|
107
|
+
for (const text of texts) {
|
|
108
|
+
for (const phrase of String(text).toLowerCase().split(PHRASE_END)) {
|
|
109
|
+
const words = terms(phrase);
|
|
110
|
+
for (let i = 1; i < words.length; i++)
|
|
111
|
+
pairs.add(`${words[i - 1]} ${words[i]}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return pairs;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The terms of an idea that name its frame: "modal" in "in a modal". A card, a
|
|
119
|
+
* table or an existing page after "in a" is where a part goes, not a frame.
|
|
120
|
+
* @param {string} query
|
|
121
|
+
* @returns {Set<string>}
|
|
122
|
+
*/
|
|
123
|
+
export function containersOf(query) {
|
|
124
|
+
const found = new Set();
|
|
125
|
+
for (const m of String(query).matchAll(CONTAINER_PHRASE)) {
|
|
126
|
+
for (const t of terms(`${m[1]} ${m[2] ?? ''}`)) {
|
|
127
|
+
if (OVERLAY_FRAMES.has(t)) found.add(t);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return found;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** @param {string} text */
|
|
134
|
+
export const normalized = text =>
|
|
135
|
+
(
|
|
136
|
+
String(text)
|
|
137
|
+
.toLowerCase()
|
|
138
|
+
.match(/[a-z0-9]+/g) ?? []
|
|
139
|
+
).join(' ');
|
|
140
|
+
|
|
141
|
+
/** Stemmed synonym lookup built from search's vocabulary, both directions. */
|
|
142
|
+
export const SYNONYMS_OF = (() => {
|
|
143
|
+
/** @type {Map<string, Set<string>>} */
|
|
144
|
+
const index = new Map();
|
|
145
|
+
/** @param {string} a @param {string} b */
|
|
146
|
+
const add = (a, b) => {
|
|
147
|
+
const set = index.get(a) ?? new Set();
|
|
148
|
+
set.add(b);
|
|
149
|
+
index.set(a, set);
|
|
150
|
+
};
|
|
151
|
+
for (const [key, values] of Object.entries(SYNONYMS)) {
|
|
152
|
+
const k = stem(key);
|
|
153
|
+
for (const value of values) {
|
|
154
|
+
const v = stem(value);
|
|
155
|
+
add(k, v);
|
|
156
|
+
add(v, k);
|
|
157
|
+
for (const other of values) if (other !== value) add(v, stem(other));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return index;
|
|
161
|
+
})();
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* @typedef {import('../_adapter.mjs').PageTemplate} PageTemplate
|
|
165
|
+
* @typedef {import('../_adapter.mjs').ComponentWords} ComponentWords
|
|
166
|
+
* @typedef {{name: string, score: number, hits: number, familyNamed: boolean, containerMatched: boolean, family: string, base: boolean, matched: Set<string>}} RankedPage
|
|
167
|
+
* @typedef {'page' | 'part' | 'edit'} IdeaKind
|
|
168
|
+
*/
|
|
169
|
+
|
|
170
|
+
/** @param {PageTemplate} page */
|
|
171
|
+
const familyOf = page => (page.category.split(' - ')[0] ?? '').trim();
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Family words, from the templates' own ids: a word in the ids of two or more
|
|
175
|
+
* templates of one family, plus the family's name when a template carries it.
|
|
176
|
+
* Each word maps to its family.
|
|
177
|
+
* @param {PageTemplate[]} pages
|
|
178
|
+
* @returns {Map<string, string>}
|
|
179
|
+
*/
|
|
180
|
+
export function familyWordsOf(pages) {
|
|
181
|
+
/** @type {Map<string, Map<string, number>>} */
|
|
182
|
+
const idWords = new Map();
|
|
183
|
+
for (const page of pages) {
|
|
184
|
+
const family = familyOf(page);
|
|
185
|
+
const counts = idWords.get(family) ?? new Map();
|
|
186
|
+
for (const w of new Set(page.name.split('-'))) {
|
|
187
|
+
if (w.length >= 3) counts.set(stem(w), (counts.get(stem(w)) ?? 0) + 1);
|
|
188
|
+
}
|
|
189
|
+
idWords.set(family, counts);
|
|
190
|
+
}
|
|
191
|
+
/** @type {Map<string, string>} */
|
|
192
|
+
const familyOfWord = new Map();
|
|
193
|
+
for (const [family, counts] of idWords) {
|
|
194
|
+
const heads = [...counts].filter(([, c]) => c >= 2).map(([w]) => w);
|
|
195
|
+
const name = normalized(family).split(' ').pop() ?? '';
|
|
196
|
+
if (name.length >= 3 && counts.has(stem(name))) heads.push(stem(name));
|
|
197
|
+
for (const head of heads) {
|
|
198
|
+
if (!familyOfWord.has(head)) familyOfWord.set(head, family);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return familyOfWord;
|
|
202
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
export const BUILD_TREE: import("../../foundation/route/route.mjs").Tree<BuildRequest, BuildContext, import("./build.type.mjs").BuildKitResponse>;
|
|
5
|
+
export type BuildRequest = {
|
|
6
|
+
query: string;
|
|
7
|
+
options: NonNullable<Parameters<typeof kitSignals>[1]>;
|
|
8
|
+
};
|
|
9
|
+
export type BuildContext = {
|
|
10
|
+
/**
|
|
11
|
+
* the root's weights
|
|
12
|
+
*/
|
|
13
|
+
weights: import("../../foundation/route/route.mjs").ClassifierWeights;
|
|
14
|
+
/**
|
|
15
|
+
* the block picker's weights; null: build names its own blocks
|
|
16
|
+
*/
|
|
17
|
+
blocks: import("./blocks/blocks.mjs").BlockPickerWeights | null;
|
|
18
|
+
};
|
|
19
|
+
export type BuildAnswer = import("./build.type.mjs").BuildKitResponse;
|
|
20
|
+
import { kitSignals } from './kit/signals.mjs';
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file How build answers: a root classifier sends each request to the
|
|
5
|
+
* searcher that should answer it. Its weights (tree.weights.json) have one
|
|
6
|
+
* class today, so every request goes to the main searcher: the kit's signals
|
|
7
|
+
* (kit/signals.mjs), the block picker's blocks (blocks/blocks.mjs), and the
|
|
8
|
+
* kit around them (kit/answer.mjs).
|
|
9
|
+
* @input {query, options} and the data build's adapter gathered: the root's
|
|
10
|
+
* weights and the block picker's
|
|
11
|
+
* @output the build.kit response
|
|
12
|
+
* @position api/build; build.mjs runs it with the shared route helper
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {routeKit} from '../../foundation/route/route.mjs';
|
|
16
|
+
import {kitAnswer} from './kit/answer.mjs';
|
|
17
|
+
import {kitSignals} from './kit/signals.mjs';
|
|
18
|
+
import {pickBlocks} from './blocks/blocks.mjs';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {{query: string, options: NonNullable<Parameters<typeof kitSignals>[1]>}} BuildRequest
|
|
22
|
+
* @typedef {object} BuildContext
|
|
23
|
+
* @property {import('../../foundation/route/route.mjs').ClassifierWeights} weights the root's weights
|
|
24
|
+
* @property {import('./blocks/blocks.mjs').BlockPickerWeights | null} blocks the block picker's weights; null: build names its own blocks
|
|
25
|
+
* @typedef {import('./build.type.mjs').BuildKitResponse} BuildAnswer
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** @type {import('../../foundation/route/route.mjs').RouteKit<BuildRequest, BuildContext, BuildAnswer>} */
|
|
29
|
+
const {defineSearcher, defineClassifier, defineTree} = routeKit;
|
|
30
|
+
|
|
31
|
+
const main = defineSearcher({
|
|
32
|
+
id: 'main',
|
|
33
|
+
summary:
|
|
34
|
+
'Ranks page templates, blocks, and components by the words of the request, starts from the best page template, and lists its blocks in the order the block picker ranks them.',
|
|
35
|
+
run: async (request, ctx) => {
|
|
36
|
+
const signals = await kitSignals(request.query, request.options);
|
|
37
|
+
if (!ctx.blocks) return kitAnswer(signals);
|
|
38
|
+
const picked = pickBlocks(request.query, signals, ctx.blocks);
|
|
39
|
+
return kitAnswer(signals, picked.names, picked.entries);
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const root = defineClassifier({
|
|
44
|
+
id: 'root',
|
|
45
|
+
summary: 'Classifies the request and hands it to the searcher for its class.',
|
|
46
|
+
searchers: {},
|
|
47
|
+
fallback: 'main',
|
|
48
|
+
floor: 1,
|
|
49
|
+
features: () => ['#bias'],
|
|
50
|
+
weights: ctx => ctx.weights,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export const BUILD_TREE = defineTree('root', [root, main]);
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file build's tree end to end, offline: the network and subprocesses throw,
|
|
5
|
+
* and build still answers. The block picker changes only the kit's blocks:
|
|
6
|
+
* the start, its alternatives, and every other field stay build's own, the
|
|
7
|
+
* block count is build's own, and the blocks it names are pinned per phrase.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as path from 'node:path';
|
|
11
|
+
import {fileURLToPath} from 'node:url';
|
|
12
|
+
import {afterAll, beforeAll, describe, expect, it, vi} from 'vitest';
|
|
13
|
+
|
|
14
|
+
// vi.mock calls are hoisted above the imports; the stub maker is hoisted with them.
|
|
15
|
+
const {blocked} = vi.hoisted(() => ({
|
|
16
|
+
/** @param {string} name */
|
|
17
|
+
blocked: name => async (/** @type {() => Promise<any>} */ load) => {
|
|
18
|
+
const real = await load();
|
|
19
|
+
/** @type {Record<string, unknown>} */
|
|
20
|
+
const stub = {};
|
|
21
|
+
for (const [key, value] of Object.entries(real)) {
|
|
22
|
+
stub[key] =
|
|
23
|
+
typeof value === 'function'
|
|
24
|
+
? () => {
|
|
25
|
+
throw new Error(`build reached ${name}.${key}`);
|
|
26
|
+
}
|
|
27
|
+
: value;
|
|
28
|
+
}
|
|
29
|
+
return {...stub, default: stub};
|
|
30
|
+
},
|
|
31
|
+
}));
|
|
32
|
+
vi.mock('node:http', blocked('http'));
|
|
33
|
+
vi.mock('node:https', blocked('https'));
|
|
34
|
+
vi.mock('node:net', blocked('net'));
|
|
35
|
+
vi.mock('node:dns', blocked('dns'));
|
|
36
|
+
vi.mock('node:child_process', blocked('child_process'));
|
|
37
|
+
vi.mock('http', blocked('http'));
|
|
38
|
+
vi.mock('https', blocked('https'));
|
|
39
|
+
vi.mock('net', blocked('net'));
|
|
40
|
+
vi.mock('dns', blocked('dns'));
|
|
41
|
+
vi.mock('child_process', blocked('child_process'));
|
|
42
|
+
|
|
43
|
+
const {build} = await import('./build.mjs');
|
|
44
|
+
const {buildKit} = await import('./kit/kit.mjs');
|
|
45
|
+
const {loadBlockPicker, withListingCache} = await import('./_adapter.mjs');
|
|
46
|
+
|
|
47
|
+
const REPO = path.resolve(
|
|
48
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
49
|
+
'../../../..',
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Hand-written phrases and the blocks the picker names for each. The rest of
|
|
54
|
+
* the answer carries search results, which change whenever the docs do, so it
|
|
55
|
+
* is compared with build's own kit rather than pinned.
|
|
56
|
+
* @type {[string, string[]][]}
|
|
57
|
+
*/
|
|
58
|
+
const PINNED = [
|
|
59
|
+
[
|
|
60
|
+
'analytics dashboard with a KPI row and a sortable table',
|
|
61
|
+
[
|
|
62
|
+
'TableSortableTable',
|
|
63
|
+
'TableGridDividersTable',
|
|
64
|
+
'TableShowcase',
|
|
65
|
+
'TableGroupedRowsTable',
|
|
66
|
+
'TableStripedTable',
|
|
67
|
+
],
|
|
68
|
+
],
|
|
69
|
+
[
|
|
70
|
+
'settings page with notification toggles and a save bar',
|
|
71
|
+
[
|
|
72
|
+
'SwitchShowcase',
|
|
73
|
+
'SwitchSettingsPanel',
|
|
74
|
+
'ToggleButtonShowcase',
|
|
75
|
+
'ToggleButtonStates',
|
|
76
|
+
'SwitchWithDescription',
|
|
77
|
+
],
|
|
78
|
+
],
|
|
79
|
+
[
|
|
80
|
+
'empty state for a question bank with an import button',
|
|
81
|
+
[
|
|
82
|
+
'EmptyStateContainer',
|
|
83
|
+
'EmptyStateShowcase',
|
|
84
|
+
'EmptyStateCompact',
|
|
85
|
+
'EmptyStateActions',
|
|
86
|
+
'CommandPaletteEmptyShowcase',
|
|
87
|
+
],
|
|
88
|
+
],
|
|
89
|
+
[
|
|
90
|
+
'login form with single sign-on buttons',
|
|
91
|
+
[
|
|
92
|
+
'ButtonWithIcon',
|
|
93
|
+
'CardWithInnerLayout',
|
|
94
|
+
'ButtonVariants',
|
|
95
|
+
'FormLayoutShowcase',
|
|
96
|
+
'FormLayoutMixedControls',
|
|
97
|
+
],
|
|
98
|
+
],
|
|
99
|
+
[
|
|
100
|
+
'calendar for booking appointments',
|
|
101
|
+
[
|
|
102
|
+
'CalendarSingle',
|
|
103
|
+
'CalendarShowcase',
|
|
104
|
+
'CalendarConstraints',
|
|
105
|
+
'CalendarRangeWithValue',
|
|
106
|
+
'CalendarTwoMonths',
|
|
107
|
+
],
|
|
108
|
+
],
|
|
109
|
+
[
|
|
110
|
+
'kanban board for sprint tasks with a filter bar',
|
|
111
|
+
['ToolbarTableFilter', 'PowerSearchShowcase'],
|
|
112
|
+
],
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
/** @type {typeof globalThis.fetch} */
|
|
116
|
+
let realFetch;
|
|
117
|
+
beforeAll(() => {
|
|
118
|
+
realFetch = globalThis.fetch;
|
|
119
|
+
globalThis.fetch = () => {
|
|
120
|
+
throw new Error('build reached fetch');
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
afterAll(() => {
|
|
124
|
+
globalThis.fetch = realFetch;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('build tree', () => {
|
|
128
|
+
it('keeps a block the request names by its exact name in build’s slot', async () => {
|
|
129
|
+
const phrase = 'calendar for booking appointments with CalendarTwoMonths';
|
|
130
|
+
const answer = await build(phrase, {cwd: REPO});
|
|
131
|
+
const own = await withListingCache(() => buildKit(phrase, {cwd: REPO}));
|
|
132
|
+
if (answer?.type !== 'build.kit' || own.type !== 'build.kit')
|
|
133
|
+
throw new Error('expected a kit');
|
|
134
|
+
const k = own.data.blocks.findIndex(b => b.name === 'CalendarTwoMonths');
|
|
135
|
+
expect(k).toBeGreaterThanOrEqual(0);
|
|
136
|
+
expect(answer.data.blocks[k].name).toBe('CalendarTwoMonths');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('runs with the network and subprocesses blocked', async () => {
|
|
140
|
+
const cp = await import('node:child_process');
|
|
141
|
+
const https = await import('node:https');
|
|
142
|
+
expect(() => cp.execFileSync('true')).toThrow(/build reached/);
|
|
143
|
+
expect(() => https.request('https://example.com')).toThrow(/build reached/);
|
|
144
|
+
expect(() => globalThis.fetch('https://example.com')).toThrow(
|
|
145
|
+
/build reached/,
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('loads the block picker weights as plain numbers', () => {
|
|
150
|
+
expect(loadBlockPicker()).not.toBeNull();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
for (const [phrase, blocks] of PINNED) {
|
|
154
|
+
it(`answers "${phrase}" offline with the picker's blocks`, async () => {
|
|
155
|
+
const answer = await build(phrase, {cwd: REPO});
|
|
156
|
+
const own = await withListingCache(() => buildKit(phrase, {cwd: REPO}));
|
|
157
|
+
if (answer?.type !== 'build.kit' || own.type !== 'build.kit')
|
|
158
|
+
throw new Error('expected a kit');
|
|
159
|
+
// Only the blocks move: the start, its alternatives, and every other
|
|
160
|
+
// field are build's own.
|
|
161
|
+
expect({...answer.data, blocks: null}).toEqual({
|
|
162
|
+
...own.data,
|
|
163
|
+
blocks: null,
|
|
164
|
+
});
|
|
165
|
+
expect(answer.data.start?.alternatives).toEqual(
|
|
166
|
+
own.data.start?.alternatives,
|
|
167
|
+
);
|
|
168
|
+
// Every block is build's own entry, one search returned, or the picker's
|
|
169
|
+
// own, each with its real score and reason; never more blocks than
|
|
170
|
+
// build's own.
|
|
171
|
+
expect(answer.data.blocks.length).toBe(own.data.blocks.length);
|
|
172
|
+
for (const b of answer.data.blocks) {
|
|
173
|
+
expect(b.score).toBeGreaterThan(0);
|
|
174
|
+
expect(typeof b.reason).toBe('string');
|
|
175
|
+
const mine = own.data.blocks.find(o => o.name === b.name);
|
|
176
|
+
if (mine) expect(mine).toEqual(b);
|
|
177
|
+
}
|
|
178
|
+
expect(answer.data.blocks.map(b => b.name)).toEqual(blocks);
|
|
179
|
+
// Never longer than build's own answer for the same request.
|
|
180
|
+
expect(Buffer.byteLength(JSON.stringify(answer))).toBeLessThanOrEqual(
|
|
181
|
+
Buffer.byteLength(JSON.stringify(own)),
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"classes": ["main"], "tokens": {"#bias": [0]}}
|
|
@@ -44,6 +44,10 @@ import {Project} from '../../foundation/config/project.mjs';
|
|
|
44
44
|
import {loadComponentDoc as loadValidatedComponentDoc} from '../../foundation/discovery/component-loader.mjs';
|
|
45
45
|
import {resolveComponentReplacements} from '../../foundation/discovery/component-replacement.mjs';
|
|
46
46
|
import {searchComponents} from '../../foundation/text/string-utils.mjs';
|
|
47
|
+
import {
|
|
48
|
+
rankComponentSuggestions,
|
|
49
|
+
synonymMatches,
|
|
50
|
+
} from '../../foundation/text/suggest-order.mjs';
|
|
47
51
|
import {AstryxError, coreDirOrThrow} from '../error.mjs';
|
|
48
52
|
import {
|
|
49
53
|
componentLinkProvider,
|
|
@@ -51,6 +55,28 @@ import {
|
|
|
51
55
|
loadDocsCatalog,
|
|
52
56
|
} from '../docs/_adapter.mjs';
|
|
53
57
|
|
|
58
|
+
/** @type {Record<string, {synonyms?: string[]}> | undefined} */
|
|
59
|
+
let cards;
|
|
60
|
+
/**
|
|
61
|
+
* Search's component cards (each component's other names), read once.
|
|
62
|
+
* @returns {Record<string, {synonyms?: string[]}>}
|
|
63
|
+
*/
|
|
64
|
+
function searchCards() {
|
|
65
|
+
if (!cards) {
|
|
66
|
+
try {
|
|
67
|
+
cards = JSON.parse(
|
|
68
|
+
fs.readFileSync(
|
|
69
|
+
new URL('../search/_cards.json', import.meta.url),
|
|
70
|
+
'utf8',
|
|
71
|
+
),
|
|
72
|
+
);
|
|
73
|
+
} catch {
|
|
74
|
+
cards = {};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return /** @type {Record<string, {synonyms?: string[]}>} */ (cards);
|
|
78
|
+
}
|
|
79
|
+
|
|
54
80
|
export {CORE_PACKAGE, unscopedComponentOwners};
|
|
55
81
|
|
|
56
82
|
/**
|
|
@@ -463,12 +489,20 @@ export async function resolveUnscopedDoc(dirName, {coreDir, cwd, name}) {
|
|
|
463
489
|
resolvedOwnerPackage = CORE_PACKAGE;
|
|
464
490
|
resolvedSourcePath = findComponentSource(coreDir, resolvedName);
|
|
465
491
|
} else {
|
|
492
|
+
// How many to suggest: the near-top results, at most five and at
|
|
493
|
+
// least two. Which ones, and in what order, is the suggestion
|
|
494
|
+
// ranking's call.
|
|
466
495
|
const threshold = Math.max(topScore - 20, 1);
|
|
467
|
-
const
|
|
468
|
-
.filter(r => r.score >= threshold)
|
|
469
|
-
.
|
|
470
|
-
|
|
471
|
-
|
|
496
|
+
const count = Math.max(
|
|
497
|
+
results.filter(r => r.score >= threshold).slice(0, 5).length,
|
|
498
|
+
Math.min(2, results.length),
|
|
499
|
+
);
|
|
500
|
+
const candidates = rankComponentSuggestions(
|
|
501
|
+
dirName,
|
|
502
|
+
results,
|
|
503
|
+
Object.values(components).flat(),
|
|
504
|
+
synonymMatches(dirName, searchCards()),
|
|
505
|
+
).slice(0, count);
|
|
472
506
|
throw new AstryxError(
|
|
473
507
|
`No component named "${name}"`,
|
|
474
508
|
candidates.map(c => ({name: c.name, reason: c.reason})),
|
|
@@ -64,6 +64,7 @@ function writeIntegration(name, components, peers) {
|
|
|
64
64
|
type: 'component',
|
|
65
65
|
name: componentName,
|
|
66
66
|
displayName: componentName,
|
|
67
|
+
import: `${name}/components/${componentName}`,
|
|
67
68
|
...(replaces ? {replaces} : {}),
|
|
68
69
|
keywords: ['acme'],
|
|
69
70
|
usage: {description: `${componentName} for Acme apps.`},
|
|
@@ -293,7 +293,7 @@ async function componentRead(name, options = {}) {
|
|
|
293
293
|
const selectorList = Array.isArray(name) ? name : null;
|
|
294
294
|
const noName = selectorList == null && !name;
|
|
295
295
|
|
|
296
|
-
// Default detail level mirrors the CLI (see commands/component
|
|
296
|
+
// Default detail level mirrors the CLI (see commands/component.mjs):
|
|
297
297
|
// single-component views default to 'full', list-style views (--list,
|
|
298
298
|
// --category, or no name) default to 'brief' (scannable name lists).
|
|
299
299
|
// An explicit array remains a batch even when it is empty or a caller also
|
|
@@ -461,8 +461,22 @@ async function componentRead(name, options = {}) {
|
|
|
461
461
|
return componentDetailBlocks(dirName, cwd, owner.package);
|
|
462
462
|
}
|
|
463
463
|
const loaded = await loadComponentDoc(owner.docPath, docOpts);
|
|
464
|
-
// A family member
|
|
465
|
-
// member
|
|
464
|
+
// A family member reads as that member, scoped the way the unscoped
|
|
465
|
+
// lookup scopes it: an integration member by its own entry, a Core
|
|
466
|
+
// sub-component (HStack in the Stack doc) by scopeSubComponent.
|
|
467
|
+
const sub =
|
|
468
|
+
scoped.kind === 'core'
|
|
469
|
+
? scopeSubComponent(loaded, dirName, coreDir)
|
|
470
|
+
: null;
|
|
471
|
+
if (sub) {
|
|
472
|
+
if (props) {
|
|
473
|
+
return componentDetailProps(
|
|
474
|
+
{props: sub.matchingComponent.props},
|
|
475
|
+
owner.package,
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
return componentDetail(sub.scoped, owner, dirName, coreDir);
|
|
479
|
+
}
|
|
466
480
|
const docs =
|
|
467
481
|
scoped.kind === 'integration'
|
|
468
482
|
? scopeIntegrationMember(loaded, owner, dirName)
|
|
@@ -135,6 +135,21 @@ describe('component list — components whose docs live in a family folder', ()
|
|
|
135
135
|
});
|
|
136
136
|
}, SLOW);
|
|
137
137
|
|
|
138
|
+
it('answers a Core-scoped lookup of a member with that member, as the unscoped lookup does', async () => {
|
|
139
|
+
for (const name of ['HStack', 'VStack']) {
|
|
140
|
+
const unscoped = await component(name, {cwd});
|
|
141
|
+
const scoped = await component(name, {
|
|
142
|
+
cwd,
|
|
143
|
+
package: '@astryxdesign/core',
|
|
144
|
+
});
|
|
145
|
+
expect(scoped.data.name).toBe(name);
|
|
146
|
+
expect(scoped.data.parentDoc).toBe('Stack');
|
|
147
|
+
expect(scoped.data.import).toBe(`@astryxdesign/core/${name}`);
|
|
148
|
+
expect(scoped.data.props).toEqual(unscoped.data.props);
|
|
149
|
+
expect(scoped.data.props.length).toBeGreaterThan(0);
|
|
150
|
+
}
|
|
151
|
+
}, SLOW);
|
|
152
|
+
|
|
138
153
|
it('prints each one\'s own import path in the list, the detail, and search', async () => {
|
|
139
154
|
// The path `component <Name>` prints is the one docs and examples teach:
|
|
140
155
|
// the component's own entry point, never its family's (/Stack, /Text) or
|
|
@@ -250,14 +250,14 @@ describe("a link in a guide's description, as the docs tree shows it", () => {
|
|
|
250
250
|
'Document a theme by extending the core theme topic, so an app reads it in `astryx docs theme`.';
|
|
251
251
|
const guide = /** @type {any} */ (
|
|
252
252
|
await docs(
|
|
253
|
-
'
|
|
253
|
+
'authoring/building-blocks/themes/document-the-theme',
|
|
254
254
|
undefined,
|
|
255
255
|
{cwd: tmpDir, full: true},
|
|
256
256
|
)
|
|
257
257
|
);
|
|
258
258
|
expect(guide.data.description).toBe(expected);
|
|
259
259
|
const namespace = /** @type {any} */ (
|
|
260
|
-
await docs('
|
|
260
|
+
await docs('authoring/building-blocks/themes', undefined, {
|
|
261
261
|
cwd: tmpDir,
|
|
262
262
|
})
|
|
263
263
|
);
|
|
@@ -43,12 +43,11 @@ export function hasSources(project: Project | undefined): boolean;
|
|
|
43
43
|
* @param {Project | undefined} project
|
|
44
44
|
* @param {Array<{name: string}>} installed
|
|
45
45
|
* @param {Set<string>} declared
|
|
46
|
-
* @param {(name: string, version?: string) => string} add
|
|
47
46
|
* @returns {Promise<import('./discover.type.mjs').DiscoverAvailableEntry[]>}
|
|
48
47
|
*/
|
|
49
48
|
export function computerAvailableEntries(project: Project | undefined, installed: Array<{
|
|
50
49
|
name: string;
|
|
51
|
-
}>, declared: Set<string
|
|
50
|
+
}>, declared: Set<string>): Promise<import("./discover.type.mjs").DiscoverAvailableEntry[]>;
|
|
52
51
|
/**
|
|
53
52
|
* Package names the project's package.json declares in any dependency field.
|
|
54
53
|
* @param {Project | undefined} project
|