@astryxdesign/cli 0.6.4-canary.06c8fa3 → 0.6.4-canary.0e1fbdb
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 +49 -40
- package/api/build/build.doc.mjs +6 -1
- package/api/build/build.test.mjs +22 -0
- package/api/build/kit/kit.mjs +44 -5
- package/api/component/component.doc.mjs +14 -7
- package/api/docs/_adapter.d.mts +8 -3
- package/api/docs/_adapter.mjs +14 -6
- package/api/docs/docOverlays.test.mjs +27 -1
- package/api/docs/docs.doc.mjs +2 -2
- package/api/doctor/doctor.doc.mjs +17 -8
- package/api/doctor/doctor.type.d.mts +1 -1
- package/api/doctor/doctor.type.mjs +1 -1
- package/api/gap-report/gap-report.doc.mjs +19 -10
- package/api/hook/hook.doc.mjs +6 -3
- package/api/index.d.mts +2 -0
- package/api/index.mjs +3 -1
- package/api/init/init.doc.mjs +17 -12
- package/api/integration/add-theme.mjs +22 -1
- package/api/integration/add-theme.test.mjs +34 -0
- package/api/integration/authoring-checks.mjs +2 -2
- package/api/integration/integrationPackCheck.doc.mjs +3 -3
- package/api/integration/pack-check.lifecycle-output.test.mjs +2 -0
- package/api/integration/pack-check.mjs +54 -6
- package/api/integration/pack-check.test.mjs +90 -0
- package/api/integration/pack-check.type.mjs +1 -1
- package/api/json/assertResponse.doc.mjs +1 -1
- package/api/json/index.ts +1 -0
- package/api/json/isError.doc.mjs +1 -1
- package/api/layout/_adapter.d.mts +34 -0
- package/api/layout/_adapter.mjs +148 -0
- package/api/layout/check/check.d.mts +16 -0
- package/api/layout/check/check.mjs +40 -0
- package/api/layout/expand/expand.d.mts +22 -0
- package/api/layout/expand/expand.mjs +155 -0
- package/api/layout/expand/expand.path-safety.test.mjs +53 -0
- package/api/layout/grammar/grammar.d.mts +13 -0
- package/api/layout/grammar/grammar.mjs +87 -0
- package/api/layout/layout.d.mts +6 -0
- package/api/layout/layout.mjs +17 -0
- package/api/layout/layout.test.mjs +297 -0
- package/api/layout/layout.type.d.mts +89 -0
- package/api/layout/layout.type.mjs +103 -0
- package/api/layout/layoutCheck.doc.d.mts +11 -0
- package/api/layout/layoutCheck.doc.mjs +85 -0
- package/api/layout/layoutExpand.doc.d.mts +11 -0
- package/api/layout/layoutExpand.doc.mjs +107 -0
- package/api/layout/layoutGrammar.doc.d.mts +11 -0
- package/api/layout/layoutGrammar.doc.mjs +57 -0
- package/api/search/search.d.mts +27 -1
- package/api/search/search.doc.mjs +2 -2
- package/api/search/search.mjs +228 -16
- package/api/swizzle/swizzle.doc.mjs +7 -5
- package/api/template/copy/copy.mjs +1 -1
- package/api/template/copy/copy.test.mjs +9 -0
- package/api/template/template-integration.test.mjs +65 -1
- package/api/template/template.doc.mjs +2 -1
- package/api/template/template.mjs +1 -1
- package/api/theme/generateTonalPalette.doc.mjs +1 -2
- package/api/theme/listThemes.doc.mjs +1 -1
- package/api/theme/themeAdd.doc.mjs +9 -10
- package/api/theme/themeBuild.doc.mjs +13 -13
- package/api/theme/themeList.doc.mjs +1 -1
- package/api/theme/themeListAvailable.doc.mjs +2 -1
- package/api/theme/themePaletteGenerate.doc.mjs +15 -8
- package/api/theme/themeTargets.doc.mjs +3 -2
- package/api/theme/themeTemplate.doc.mjs +2 -1
- package/api/upgrade/run/run.mjs +1 -1
- package/api/upgrade/upgrade.doc.mjs +24 -22
- package/assets/docs/README.md +4 -2
- package/assets/docs/browser-support.doc.mjs +11 -11
- package/assets/docs/color.doc.mjs +8 -2
- package/assets/docs/elevation.doc.mjs +6 -4
- package/assets/docs/getting-started.doc.mjs +5 -16
- package/assets/docs/icons.doc.mjs +2 -21
- package/assets/docs/illustrations.doc.mjs +7 -15
- package/assets/docs/layout.doc.dense.mjs +130 -82
- package/assets/docs/layout.doc.mjs +133 -77
- package/assets/docs/migration.doc.mjs +19 -21
- package/assets/docs/motion.doc.mjs +16 -3
- package/assets/docs/principles.doc.dense.mjs +5 -5
- package/assets/docs/principles.doc.mjs +8 -0
- package/assets/docs/principles.doc.zh.mjs +6 -6
- package/assets/docs/shape.doc.mjs +8 -3
- package/assets/docs/spacing.doc.mjs +7 -2
- package/assets/docs/styling-libraries.doc.mjs +6 -2
- package/assets/docs/styling.doc.mjs +19 -23
- package/assets/docs/theme.doc.dense.mjs +58 -18
- package/assets/docs/theme.doc.mjs +56 -46
- package/assets/docs/theme.doc.zh.mjs +9 -8
- package/assets/docs/tokens.doc.dense.mjs +2 -2
- package/assets/docs/tokens.doc.mjs +389 -8
- package/assets/docs/tokens.doc.zh.mjs +2 -2
- package/assets/docs/tree/add-a-component.doc.mjs +75 -0
- package/assets/docs/tree/add-a-theme.doc.mjs +85 -0
- package/assets/docs/tree/add-a-topic.doc.mjs +144 -0
- package/assets/docs/tree/agent-guidance.doc.mjs +138 -0
- package/assets/docs/tree/block-template.doc.mjs +130 -0
- package/assets/docs/tree/build-the-template.doc.mjs +28 -0
- package/assets/docs/tree/building-blocks.doc.mjs +46 -0
- package/assets/docs/tree/check-your-docs.doc.mjs +137 -0
- package/assets/docs/tree/checks.doc.mjs +119 -0
- package/assets/docs/tree/codemods.doc.mjs +147 -0
- package/assets/docs/tree/component-family.doc.mjs +113 -0
- package/assets/docs/tree/component-imports.doc.mjs +69 -0
- package/assets/docs/tree/components.doc.mjs +23 -0
- package/assets/docs/tree/configuration.doc.mjs +23 -0
- package/assets/docs/tree/debug-and-gap-reports.doc.mjs +182 -0
- package/assets/docs/tree/define-the-theme.doc.mjs +118 -0
- package/assets/docs/tree/describe-the-component.doc.mjs +57 -0
- package/assets/docs/tree/docs.doc.mjs +21 -0
- package/assets/docs/tree/document-the-template.doc.mjs +28 -0
- package/assets/docs/tree/document-the-theme.doc.mjs +68 -0
- package/assets/docs/tree/export-template-assets.doc.mjs +147 -0
- package/assets/docs/tree/extend-or-replace.doc.mjs +103 -0
- package/assets/docs/tree/fonts-and-assets.doc.mjs +106 -0
- package/assets/docs/tree/generate-a-palette.doc.mjs +66 -0
- package/assets/docs/tree/grade-template-with-agent.doc.mjs +105 -0
- package/assets/docs/tree/help.doc.mjs +16 -0
- package/assets/docs/tree/integrations.doc.mjs +25 -470
- package/assets/docs/tree/links.doc.mjs +98 -0
- package/assets/docs/tree/package-and-test.doc.mjs +32 -0
- package/assets/docs/tree/page-template.doc.mjs +71 -0
- package/assets/docs/tree/publishing.doc.mjs +111 -0
- package/assets/docs/tree/quick-start.doc.mjs +272 -0
- package/assets/docs/tree/replace-a-core-component.doc.mjs +104 -0
- package/assets/docs/tree/replace-a-core-template.doc.mjs +172 -0
- package/assets/docs/tree/sections-and-placement.doc.mjs +108 -0
- package/assets/docs/tree/see-it-in-an-app.doc.mjs +59 -0
- package/assets/docs/tree/ship.doc.mjs +16 -0
- package/assets/docs/tree/short-and-findable.doc.mjs +108 -0
- package/assets/docs/tree/single-component.doc.mjs +165 -0
- package/assets/docs/tree/start-a-template.doc.mjs +143 -0
- package/assets/docs/tree/subcomponent.doc.mjs +115 -0
- package/assets/docs/tree/template-assets.doc.mjs +64 -0
- package/assets/docs/tree/template-doc-overview.doc.mjs +109 -0
- package/assets/docs/tree/template-fonts.doc.mjs +102 -0
- package/assets/docs/tree/template-grading-rubric.doc.mjs +452 -0
- package/assets/docs/tree/template-icons.doc.mjs +97 -0
- package/assets/docs/tree/template-images-media.doc.mjs +127 -0
- package/assets/docs/tree/template-styles.doc.mjs +93 -0
- package/assets/docs/tree/templates.doc.mjs +34 -0
- package/assets/docs/tree/test-in-an-app.doc.mjs +115 -0
- package/assets/docs/tree/test-template-in-app.doc.mjs +128 -0
- package/assets/docs/tree/themes.doc.mjs +39 -0
- package/assets/docs/tree/troubleshooting.doc.mjs +149 -0
- package/assets/docs/tree/upgrading.doc.mjs +103 -0
- package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +51 -0
- package/assets/docs/tree/verify-packed-template.doc.mjs +77 -0
- package/assets/docs/tree/versioning.doc.mjs +161 -0
- package/assets/docs/tree/write-good-templates.doc.mjs +64 -0
- package/assets/docs/tree/write-the-template-file.doc.mjs +154 -0
- package/assets/docs/typography.doc.mjs +24 -4
- package/assets/docs/working-with-ai.doc.mjs +30 -22
- package/authoring/config/config.doc.mjs +2 -2
- package/authoring/config/type.ts +2 -2
- package/authoring/doctypes/_schema.d.mts +3 -2
- package/authoring/doctypes/_schema.mjs +6 -0
- package/authoring/doctypes/base/graph-fields.doc.mjs +3 -3
- package/authoring/doctypes/base/type.ts +4 -2
- package/authoring/doctypes/command/command.doc.mjs +1 -1
- package/authoring/doctypes/command/type.ts +1 -1
- package/authoring/doctypes/component/component.doc.mjs +6 -0
- package/authoring/doctypes/component/type.ts +8 -0
- package/authoring/doctypes/reference/reference.doc.mjs +7 -0
- package/authoring/doctypes/reference/type.ts +5 -0
- package/authoring/doctypes/schema/schema.doc.mjs +2 -2
- package/authoring/doctypes/template/template.doc.mjs +1 -1
- package/authoring/doctypes/template/type.ts +2 -2
- package/authoring/integration/integration.doc.mjs +12 -10
- package/clients/cli/command-result-coverage.test.mjs +7 -7
- package/clients/cli/commands/component.doc.mjs +4 -3
- package/clients/cli/commands/debug-result-summary.test.mjs +2 -2
- package/clients/cli/commands/docs.doc.mjs +1 -1
- package/clients/cli/commands/docs.mjs +60 -17
- package/clients/cli/commands/doctor-integration-docs.doc.mjs +3 -2
- package/clients/cli/commands/doctor-integration.test.mjs +53 -0
- package/clients/cli/commands/doctor.doc.mjs +3 -1
- package/clients/cli/commands/doctor.mjs +49 -5
- package/clients/cli/commands/gap-report.doc.mjs +10 -9
- package/clients/cli/commands/init.doc.mjs +9 -6
- package/clients/cli/commands/integration-add.doc.mjs +9 -9
- package/clients/cli/commands/integration-authoring.test.mjs +61 -10
- package/clients/cli/commands/integration-pack.doc.mjs +5 -9
- package/clients/cli/commands/integration-real-world.test.mjs +1 -1
- package/clients/cli/commands/integration-verify.doc.mjs +22 -0
- package/clients/cli/commands/integration.doc.mjs +4 -4
- package/clients/cli/commands/integration.mjs +74 -43
- package/clients/cli/commands/layout-check.doc.mjs +65 -0
- package/clients/cli/commands/layout-expand.doc.mjs +83 -0
- package/clients/cli/commands/layout-grammar.doc.mjs +30 -0
- package/clients/cli/commands/layout.doc.mjs +34 -0
- package/clients/cli/commands/layout.error-codes.test.mjs +66 -0
- package/clients/cli/commands/layout.exit-parity.test.mjs +41 -0
- package/clients/cli/commands/layout.mjs +275 -0
- package/clients/cli/commands/layout.path-help.test.mjs +33 -0
- package/clients/cli/commands/layout.stdin-cap.test.mjs +47 -0
- package/clients/cli/commands/layout.text-fields.test.mjs +39 -0
- package/clients/cli/commands/manifest.doc.mjs +1 -1
- package/clients/cli/commands/search.doc.mjs +10 -3
- package/clients/cli/commands/search.mjs +21 -2
- package/clients/cli/commands/search.test.mjs +21 -4
- package/clients/cli/commands/swizzle.doc.mjs +1 -1
- package/clients/cli/commands/template.doc.mjs +1 -1
- package/clients/cli/commands/text-json-parity.test.mjs +24 -1
- package/clients/cli/commands/theme-add.doc.mjs +1 -1
- package/clients/cli/commands/theme-palette-generate.doc.mjs +3 -2
- package/clients/cli/commands/theme-palette.doc.mjs +1 -2
- package/clients/cli/commands/theme-targets.doc.mjs +2 -2
- package/clients/cli/commands/theme.doc.mjs +2 -1
- package/clients/cli/commands/upgrade.doc.mjs +62 -3
- package/clients/cli/index.mjs +32 -6
- package/clients/cli/lib/define-command.mjs +28 -4
- package/clients/cli/lib/define-command.test.mjs +54 -0
- package/clients/cli/lib/exit-codes.test.mjs +25 -2
- package/clients/cli/lib/json-shim.test.mjs +20 -6
- package/clients/cli/lib/manifest.mjs +23 -5
- package/foundation/agent-docs/agent-docs.mjs +1 -1
- package/foundation/discovery/authoring-self-docs.test.mjs +6 -2
- package/foundation/discovery/cli-self-docs.mjs +16 -2
- package/foundation/discovery/cli-self-docs.test.mjs +20 -0
- package/foundation/discovery/docs-discovery.mjs +5 -1
- package/foundation/discovery/docs-discovery.test.mjs +21 -0
- package/foundation/discovery/docs-section-key.d.mts +1 -1
- package/foundation/discovery/docs-section-key.mjs +1 -1
- package/foundation/discovery/template-adapter.mjs +1 -1
- package/foundation/doc-compiler/doc-loads.test.mjs +15 -2
- package/foundation/doc-compiler/inputs.test.mjs +0 -1
- package/foundation/doc-compiler/tree.d.mts +4 -0
- package/foundation/doc-compiler/tree.mjs +6 -1
- package/foundation/integrations/cli-requirement.d.mts +26 -6
- package/foundation/integrations/cli-requirement.mjs +46 -11
- package/foundation/integrations/cli-requirement.test.mjs +7 -2
- package/foundation/integrations/contribution-inventory.mjs +1 -1
- package/foundation/response/error-codes.doc.mjs +6 -8
- package/foundation/response/error-codes.test.mjs +30 -5
- package/foundation/response/response-types.doc.d.mts +4 -3
- package/foundation/response/response-types.doc.mjs +42 -6
- package/foundation/response/response.doc.mjs +11 -10
- package/foundation/xle/browser.d.mts +3 -3
- package/foundation/xle/browser.mjs +3 -3
- package/foundation/xle/expand.mjs +2 -2
- package/foundation/xle/parse.mjs +1 -1
- package/foundation/xle/print.mjs +2 -2
- package/foundation/xle/splice.mjs +1 -1
- package/package.json +9 -9
- package/api/docs/docs.test.mjs +0 -245
- package/api/docs/integration-tree.test.mjs +0 -555
- package/api/docs/integrationDocs.test.mjs +0 -314
- package/api/search/search.test.mjs +0 -530
- package/assets/docs/tree/integrations.test.mjs +0 -62
- package/assets/docs/tree/writing-docs.doc.mjs +0 -286
- package/clients/cli/commands/docs.test.mjs +0 -323
- package/foundation/agent-docs/agent-docs.test.mjs +0 -1159
- package/foundation/doc-compiler/tree.test.mjs +0 -606
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file FunctionDoc for `layoutExpand()` / `astryx layout expand`. Colocated
|
|
5
|
+
* with the API function it documents; the response-shape source of truth stays
|
|
6
|
+
* in `layout.type.mjs`.
|
|
7
|
+
* @position packages/cli/api/layout — function documentation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** @type {import('@astryxdesign/cli/authoring').FunctionDoc} */
|
|
11
|
+
export const doc = {
|
|
12
|
+
type: 'function',
|
|
13
|
+
kind: 'api',
|
|
14
|
+
name: 'layoutExpand',
|
|
15
|
+
namespace: 'cli/api',
|
|
16
|
+
displayName: 'layoutExpand()',
|
|
17
|
+
summary: 'Expand a validated layout expression into XDS TSX.',
|
|
18
|
+
description:
|
|
19
|
+
'The generator behind `astryx layout expand`. Parses and validates a compressed XLE/XLO ' +
|
|
20
|
+
'expression, then expands it into ready-to-use XDS TSX, auto-routing structural children ' +
|
|
21
|
+
'into the right slots, scaffolding typed useState for interactive controls, and splicing or ' +
|
|
22
|
+
'importing any referenced template blocks. Returns the code (and metadata) in a layout.expand ' +
|
|
23
|
+
'envelope, optionally writing it to a path within cwd.',
|
|
24
|
+
importPath: '@astryxdesign/cli/api',
|
|
25
|
+
signature:
|
|
26
|
+
'layoutExpand(expression: string, options?: LayoutExpandOptions): Promise<LayoutExpandResponse>',
|
|
27
|
+
keywords: ['layout', 'expand', 'xle', 'xlo', 'tsx', 'scaffold', 'generate'],
|
|
28
|
+
params: [
|
|
29
|
+
{
|
|
30
|
+
name: 'expression',
|
|
31
|
+
type: 'string',
|
|
32
|
+
description:
|
|
33
|
+
'The layout expression to expand (XLE compact or XLO outline form).',
|
|
34
|
+
required: true,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'options.targetPath',
|
|
38
|
+
type: 'string',
|
|
39
|
+
description:
|
|
40
|
+
'Write the generated TSX here (validated to stay within cwd). A path that ends in .tsx, .ts, .jsx, .js, .mjs, .cjs, .css, .scss, .json, .md or .html is used as-is; any other path is a directory that gets <name>.tsx. An existing file is replaced. Omit to return the code without writing.',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'options.form',
|
|
44
|
+
type: "'compact' | 'outline' | 'auto'",
|
|
45
|
+
description:
|
|
46
|
+
'Force which input surface the expression is parsed as, or auto-detect it.',
|
|
47
|
+
default: "'auto'",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'options.loose',
|
|
51
|
+
type: 'boolean',
|
|
52
|
+
description:
|
|
53
|
+
'Downgrade unknown {hint} references to TODO warnings instead of hard errors.',
|
|
54
|
+
default: 'false',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'options.name',
|
|
58
|
+
type: 'string',
|
|
59
|
+
description: 'PascalCase name for the generated component.',
|
|
60
|
+
default: "'GeneratedLayout'",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'options.cwd',
|
|
64
|
+
type: 'string',
|
|
65
|
+
description:
|
|
66
|
+
'Directory the block catalog, registry, and target path resolve against.',
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
returns: [
|
|
70
|
+
{
|
|
71
|
+
type: 'layout.expand',
|
|
72
|
+
description:
|
|
73
|
+
'The expansion: the parsed form, the generated TSX code, componentsUsed, states (count of useState hooks scaffolded), todos, blocksReferenced (each {name, mode}), warnings, and written (the relative output path, or null when nothing was written).',
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
throws: [
|
|
77
|
+
{
|
|
78
|
+
code: 'ERR_INVALID_ARGUMENT',
|
|
79
|
+
when: 'the expression is empty, or name is not a PascalCase identifier',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
code: 'ERR_INVALID_OPTION',
|
|
83
|
+
when: 'form is not one of "compact", "outline", or "auto"',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
code: 'ERR_LAYOUT_PARSE',
|
|
87
|
+
when: 'the expression has a syntax error (reported with line/col)',
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
code: 'ERR_LAYOUT_INVALID',
|
|
91
|
+
when: 'the expression parses but fails validation (unknown component/prop/enum/block)',
|
|
92
|
+
},
|
|
93
|
+
{code: 'ERR_PATH_TRAVERSAL', when: 'the target path escapes cwd'},
|
|
94
|
+
],
|
|
95
|
+
examples: [
|
|
96
|
+
{
|
|
97
|
+
label: 'Expand to TSX',
|
|
98
|
+
code: 'const r = await layoutExpand(\'VStack[g4] > Heading"Title" + Text"Body"\');',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
label: 'Write to a file',
|
|
102
|
+
code: "await layoutExpand('Card > Text\"Hi\"', {targetPath: 'src/Generated.tsx', name: 'Generated'});",
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
command: 'layout expand',
|
|
106
|
+
related: ['layoutCheck', 'layoutGrammar'],
|
|
107
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
|
|
2
|
+
// DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @file FunctionDoc for `layoutGrammar()` / `astryx layout grammar`. Colocated
|
|
6
|
+
* with the API function it documents; the response-shape source of truth stays
|
|
7
|
+
* in `layout.type.mjs`.
|
|
8
|
+
* @position packages/cli/api/layout — function documentation
|
|
9
|
+
*/
|
|
10
|
+
/** @type {import('@astryxdesign/cli/authoring').FunctionDoc} */
|
|
11
|
+
export const doc: import("@astryxdesign/cli/authoring").FunctionDoc;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file FunctionDoc for `layoutGrammar()` / `astryx layout grammar`. Colocated
|
|
5
|
+
* with the API function it documents; the response-shape source of truth stays
|
|
6
|
+
* in `layout.type.mjs`.
|
|
7
|
+
* @position packages/cli/api/layout — function documentation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** @type {import('@astryxdesign/cli/authoring').FunctionDoc} */
|
|
11
|
+
export const doc = {
|
|
12
|
+
type: 'function',
|
|
13
|
+
kind: 'api',
|
|
14
|
+
name: 'layoutGrammar',
|
|
15
|
+
namespace: 'cli/api',
|
|
16
|
+
displayName: 'layoutGrammar()',
|
|
17
|
+
summary: 'Return the XLE/XLO grammar cheatsheet for this install.',
|
|
18
|
+
description:
|
|
19
|
+
'The reference behind `astryx layout grammar`: the agent cheatsheet for writing XLE/XLO ' +
|
|
20
|
+
"layout expressions, with the alias table generated from this branch's registry rather than " +
|
|
21
|
+
'hand-maintained, so short names always reflect the components actually installed.',
|
|
22
|
+
importPath: '@astryxdesign/cli/api',
|
|
23
|
+
signature:
|
|
24
|
+
'layoutGrammar(options?: LayoutGrammarOptions): Promise<LayoutGrammarResponse>',
|
|
25
|
+
keywords: [
|
|
26
|
+
'layout',
|
|
27
|
+
'grammar',
|
|
28
|
+
'cheatsheet',
|
|
29
|
+
'xle',
|
|
30
|
+
'xlo',
|
|
31
|
+
'aliases',
|
|
32
|
+
'reference',
|
|
33
|
+
],
|
|
34
|
+
params: [
|
|
35
|
+
{
|
|
36
|
+
name: 'options.cwd',
|
|
37
|
+
type: 'string',
|
|
38
|
+
description:
|
|
39
|
+
'Directory the component registry (and its alias table) resolves against.',
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
returns: [
|
|
43
|
+
{
|
|
44
|
+
type: 'layout.grammar',
|
|
45
|
+
description:
|
|
46
|
+
"The cheatsheet: a text field with the full grammar reference, plus an aliases map (short name → canonical component) generated from this install's registry.",
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
examples: [
|
|
50
|
+
{
|
|
51
|
+
label: 'Get the cheatsheet',
|
|
52
|
+
code: 'const {data} = await layoutGrammar();',
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
command: 'layout grammar',
|
|
56
|
+
related: ['layoutExpand', 'layoutCheck'],
|
|
57
|
+
};
|
package/api/search/search.d.mts
CHANGED
|
@@ -24,6 +24,23 @@ export function sameWord(a: string, b: string): boolean;
|
|
|
24
24
|
* @returns {string[]}
|
|
25
25
|
*/
|
|
26
26
|
export function tokenizeQuery(term: string): string[];
|
|
27
|
+
/**
|
|
28
|
+
* The first title or heading that holds every word of the query, in order and
|
|
29
|
+
* side by side, or null.
|
|
30
|
+
* @param {string} term - Lowercased full query.
|
|
31
|
+
* @param {string[] | undefined} titles
|
|
32
|
+
* @returns {string | null}
|
|
33
|
+
*/
|
|
34
|
+
export function headingWithPhrase(term: string, titles: string[] | undefined): string | null;
|
|
35
|
+
/**
|
|
36
|
+
* The first title or heading of two words or more that the query holds whole,
|
|
37
|
+
* in order and side by side, or null. A question such as "how do I add dark
|
|
38
|
+
* mode" names the "Dark mode" section outright, around words no title has.
|
|
39
|
+
* @param {string} term - Lowercased full query.
|
|
40
|
+
* @param {string[] | undefined} titles
|
|
41
|
+
* @returns {string | null}
|
|
42
|
+
*/
|
|
43
|
+
export function titleInQuery(term: string, titles: string[] | undefined): string | null;
|
|
27
44
|
/**
|
|
28
45
|
* @param {string} term - Lowercased full query.
|
|
29
46
|
* @param {string[]} tokens - Content tokens from tokenizeQuery(term).
|
|
@@ -49,6 +66,8 @@ export function scoreQuery(term: string, tokens: string[], candidate: Candidate)
|
|
|
49
66
|
* @param {string} term - Lowercased search term.
|
|
50
67
|
* @param {object} candidate
|
|
51
68
|
* @param {string} candidate.name - Primary identifier (component/hook name, topic, template name).
|
|
69
|
+
* @param {string} [candidate.domain] - A component, hook, or template name
|
|
70
|
+
* also matches typed as words: `command palette` is CommandPalette.
|
|
52
71
|
* @param {string[]} [candidate.keywords] - Authored intent (componentsUsed, category words).
|
|
53
72
|
* @param {string[]} [candidate.weakKeywords] - Derived signal (components a page renders).
|
|
54
73
|
* @param {string} [candidate.description]
|
|
@@ -57,8 +76,9 @@ export function scoreQuery(term: string, tokens: string[], candidate: Candidate)
|
|
|
57
76
|
* @param {{fuzzy?: boolean}} [opts] - `fuzzy`: allow edit-distance (typo) matches. Default true; multi-word queries pass false.
|
|
58
77
|
* @returns {{score: number, reason: string} | null}
|
|
59
78
|
*/
|
|
60
|
-
export function scoreCandidate(term: string, { name, keywords, weakKeywords, description, prose, guidance, }: {
|
|
79
|
+
export function scoreCandidate(term: string, { name, domain, keywords, weakKeywords, description, prose, guidance, }: {
|
|
61
80
|
name: string;
|
|
81
|
+
domain?: string | undefined;
|
|
62
82
|
keywords?: string[] | undefined;
|
|
63
83
|
weakKeywords?: string[] | undefined;
|
|
64
84
|
description?: string | undefined;
|
|
@@ -122,6 +142,12 @@ export type Candidate = {
|
|
|
122
142
|
description?: string | undefined;
|
|
123
143
|
prose?: string[] | undefined;
|
|
124
144
|
guidance?: string[] | undefined;
|
|
145
|
+
/**
|
|
146
|
+
* - A doc's title and the headings inside it:
|
|
147
|
+
* the lines a reader scans to pick it. The whole query standing in one of
|
|
148
|
+
* them, or one of them standing whole in the query, is a top-tier match.
|
|
149
|
+
*/
|
|
150
|
+
titles?: string[] | undefined;
|
|
125
151
|
_import?: string | undefined;
|
|
126
152
|
_title?: string | undefined;
|
|
127
153
|
/**
|
|
@@ -48,7 +48,7 @@ export const doc = {
|
|
|
48
48
|
name: 'options.cwd',
|
|
49
49
|
type: 'string',
|
|
50
50
|
description:
|
|
51
|
-
"Directory to resolve @astryxdesign/core from. A docs-only search (`type: 'doc'`) does not need it.",
|
|
51
|
+
"Directory to resolve @astryxdesign/core from. A docs-only search (`type: 'doc'`) does not need it, and a search with no `type` covers the docs alone when core is missing.",
|
|
52
52
|
},
|
|
53
53
|
],
|
|
54
54
|
returns: [
|
|
@@ -65,7 +65,7 @@ export const doc = {
|
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
67
|
code: 'ERR_CORE_NOT_FOUND',
|
|
68
|
-
when: '@astryxdesign/core cannot be found from the cwd, and
|
|
68
|
+
when: '@astryxdesign/core cannot be found from the cwd, and `type` names a domain that reads it: `component`, `hook`, or `template`',
|
|
69
69
|
},
|
|
70
70
|
],
|
|
71
71
|
examples: [
|
package/api/search/search.mjs
CHANGED
|
@@ -42,6 +42,18 @@
|
|
|
42
42
|
* sentence, a near miss is usually a different word: "site" is not "side",
|
|
43
43
|
* "cable" is not "table".
|
|
44
44
|
*
|
|
45
|
+
* A multi-word query has a reserved top tier (see {@link scoreQuery}): the
|
|
46
|
+
* whole query as a candidate's name or keyword (190-200), then the whole query
|
|
47
|
+
* as a phrase inside a doc's title or one of its headings (170), then a whole
|
|
48
|
+
* title of two words or more inside the query (160-169), then a candidate that
|
|
49
|
+
* matches every word of the query, at least one of them by name or keyword
|
|
50
|
+
* (151-159). Below those sits everything else: a partial match, or every word
|
|
51
|
+
* matched only in prose or through the components a page renders. A section titled "Light/Dark Mode" answers `dark
|
|
52
|
+
* mode` better than any doc that merely names `mode` in code, however exactly;
|
|
53
|
+
* "Dark mode" answers `how do I add dark mode`; and a guide whose title and
|
|
54
|
+
* description hold both words of `troubleshoot integration` answers it better
|
|
55
|
+
* than a doc named `integration`.
|
|
56
|
+
*
|
|
45
57
|
* Description and guidance are separate tiers on purpose. A component's own
|
|
46
58
|
* one-line description saying "notification" is a claim about what it IS; the
|
|
47
59
|
* same word inside another component's best-practice advice is a passing
|
|
@@ -105,6 +117,9 @@ import {setResultCoverage} from './coverage.mjs';
|
|
|
105
117
|
* @property {string} [description]
|
|
106
118
|
* @property {string[]} [prose]
|
|
107
119
|
* @property {string[]} [guidance]
|
|
120
|
+
* @property {string[]} [titles] - A doc's title and the headings inside it:
|
|
121
|
+
* the lines a reader scans to pick it. The whole query standing in one of
|
|
122
|
+
* them, or one of them standing whole in the query, is a top-tier match.
|
|
108
123
|
* @property {string} [_import]
|
|
109
124
|
* @property {string} [_title]
|
|
110
125
|
* @property {string} [_topic] - A doc result's topic or docs-tree route.
|
|
@@ -414,6 +429,120 @@ function bestForToken(tok, candidate, opts = {}) {
|
|
|
414
429
|
return best;
|
|
415
430
|
}
|
|
416
431
|
|
|
432
|
+
/**
|
|
433
|
+
* The score of a whole-query phrase inside a doc's title or heading: a keyword
|
|
434
|
+
* substring hit (70) promoted by the same 100 as the exact tier. Below an
|
|
435
|
+
* exact name or keyword (190-200), above the token-sum path (~151 at most).
|
|
436
|
+
*/
|
|
437
|
+
const TITLE_PHRASE_SCORE = 170;
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* The score of a whole title inside a longer query, before its coverage bonus:
|
|
441
|
+
* one step below {@link TITLE_PHRASE_SCORE}. The bonus (one per query term the
|
|
442
|
+
* candidate matches, at most 9) orders the sections that share a common title,
|
|
443
|
+
* so "best practices for spacing" puts Spacing's Best Practices first.
|
|
444
|
+
*/
|
|
445
|
+
const TITLE_IN_QUERY_SCORE = 160;
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* The score of a candidate that matches every content word of a multi-word
|
|
449
|
+
* query, before a bonus of up to 8 for how strong its strongest match is: just
|
|
450
|
+
* above anything that matches only some of the words. The token-sum path tops
|
|
451
|
+
* out near 150 for a partial match (a 100 on one word, the per-word bonus, and
|
|
452
|
+
* the coverage term), so an AND-match with one keyword-strength hit (see
|
|
453
|
+
* {@link STRONG_TOKEN_SCORE}) always outranks an OR-match, and stays below the
|
|
454
|
+
* title tiers.
|
|
455
|
+
*/
|
|
456
|
+
const FULL_COVERAGE_SCORE = 151;
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* The strongest single-word hit an every-word match needs to take that tier: a
|
|
460
|
+
* keyword substring. Two passing mentions in prose, or the components a page
|
|
461
|
+
* happens to render, are breadth, not relevance; they stay on the token sum,
|
|
462
|
+
* below an exact name or keyword hit on one of the words.
|
|
463
|
+
*/
|
|
464
|
+
const STRONG_TOKEN_SCORE = 70;
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* The words of a title or query, lowercased, without punctuation or code ticks.
|
|
468
|
+
* @param {string} text
|
|
469
|
+
* @returns {string[]}
|
|
470
|
+
*/
|
|
471
|
+
function phraseWords(text) {
|
|
472
|
+
return unlinkText(text).toLowerCase().match(/[a-z0-9]+/g) ?? [];
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Whether two words are the same word, allowing a plural on either side, so
|
|
477
|
+
* `data attributes selector` still reads "Data attribute selectors".
|
|
478
|
+
* @param {string} a
|
|
479
|
+
* @param {string} b
|
|
480
|
+
*/
|
|
481
|
+
function samePhraseWord(a, b) {
|
|
482
|
+
return (
|
|
483
|
+
a === b ||
|
|
484
|
+
`${a}s` === b ||
|
|
485
|
+
`${b}s` === a ||
|
|
486
|
+
`${a}es` === b ||
|
|
487
|
+
`${b}es` === a
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Whether `plural` is the plural of `word`: `integrations` of `integration`,
|
|
493
|
+
* `boxes` of `box`. `es` only follows s, x, z, ch, or sh, so `notes` is not a
|
|
494
|
+
* plural of `not`.
|
|
495
|
+
* @param {string} plural
|
|
496
|
+
* @param {string} word
|
|
497
|
+
*/
|
|
498
|
+
function pluralOf(plural, word) {
|
|
499
|
+
if (word.length < 3) return false;
|
|
500
|
+
if (plural === `${word}s`) return true;
|
|
501
|
+
return /(?:s|x|z|ch|sh)$/.test(word) && plural === `${word}es`;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* The first title or heading that holds every word of the query, in order and
|
|
506
|
+
* side by side, or null.
|
|
507
|
+
* @param {string} term - Lowercased full query.
|
|
508
|
+
* @param {string[] | undefined} titles
|
|
509
|
+
* @returns {string | null}
|
|
510
|
+
*/
|
|
511
|
+
export function headingWithPhrase(term, titles) {
|
|
512
|
+
const query = phraseWords(term);
|
|
513
|
+
if (query.length < 2 || !titles) return null;
|
|
514
|
+
for (const title of titles) {
|
|
515
|
+
const words = phraseWords(String(title ?? ''));
|
|
516
|
+
for (let i = 0; i + query.length <= words.length; i++) {
|
|
517
|
+
if (query.every((word, j) => samePhraseWord(words[i + j], word)))
|
|
518
|
+
return title;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return null;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* The first title or heading of two words or more that the query holds whole,
|
|
526
|
+
* in order and side by side, or null. A question such as "how do I add dark
|
|
527
|
+
* mode" names the "Dark mode" section outright, around words no title has.
|
|
528
|
+
* @param {string} term - Lowercased full query.
|
|
529
|
+
* @param {string[] | undefined} titles
|
|
530
|
+
* @returns {string | null}
|
|
531
|
+
*/
|
|
532
|
+
export function titleInQuery(term, titles) {
|
|
533
|
+
const query = phraseWords(term);
|
|
534
|
+
if (!titles) return null;
|
|
535
|
+
for (const title of titles) {
|
|
536
|
+
const words = phraseWords(String(title ?? ''));
|
|
537
|
+
if (words.length < 2 || words.length > query.length) continue;
|
|
538
|
+
for (let i = 0; i + words.length <= query.length; i++) {
|
|
539
|
+
if (words.every((word, j) => samePhraseWord(query[i + j], word)))
|
|
540
|
+
return title;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return null;
|
|
544
|
+
}
|
|
545
|
+
|
|
417
546
|
/**
|
|
418
547
|
* @param {string} term - Lowercased full query.
|
|
419
548
|
* @param {string[]} tokens - Content tokens from tokenizeQuery(term).
|
|
@@ -437,16 +566,22 @@ export function scoreQuery(term, tokens, candidate) {
|
|
|
437
566
|
// is usually a different word, not a typo.
|
|
438
567
|
const fuzzy = tokens.length <= 1;
|
|
439
568
|
const full = scoreCandidate(term, candidate, {fuzzy});
|
|
569
|
+
// A query of several words keeps its phrase tiers below even when stopwords
|
|
570
|
+
// leave one content word: "make an integration" is still the phrase an
|
|
571
|
+
// author declares as a keyword, and "build an integration" still names a
|
|
572
|
+
// title outright, though each tokenizes to `integration` alone.
|
|
573
|
+
const phrase = phraseWords(term).length >= 2;
|
|
440
574
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
575
|
+
/** 0–1 content tokens: whole-phrase fuzzy matching (typo tolerance for
|
|
576
|
+
* single words), but if stopwords left exactly one DIFFERENT token (e.g.
|
|
577
|
+
* "pricing page" → "pricing"), score that token too and take the stronger. */
|
|
578
|
+
const fewTokens = () => {
|
|
445
579
|
const single =
|
|
446
580
|
tokens.length === 1 ? bestForToken(tokens[0], candidate, {fuzzy}) : null;
|
|
447
581
|
if (full && (!single || full.score >= single.score)) return asFull(full);
|
|
448
582
|
return single ? asFull(single) : null;
|
|
449
|
-
}
|
|
583
|
+
};
|
|
584
|
+
if (tokens.length <= 1 && !phrase) return fewTokens();
|
|
450
585
|
|
|
451
586
|
// The full (untokenized) query matching a candidate's name or a declared
|
|
452
587
|
// keyword VERBATIM — full.score 90 or 100, the only two scoreCandidate
|
|
@@ -463,6 +598,22 @@ export function scoreQuery(term, tokens, candidate) {
|
|
|
463
598
|
return asFull({score: full.score + 100, reason: full.reason});
|
|
464
599
|
}
|
|
465
600
|
|
|
601
|
+
// The whole query standing as a phrase in a doc's title or one of its
|
|
602
|
+
// headings is the next tier down, and still above the token-sum path. The
|
|
603
|
+
// reader named what the section is about, in order: `dark mode` is the
|
|
604
|
+
// "Light/Dark Mode" section. Without this, the title scores a keyword
|
|
605
|
+
// substring (70) and loses to a doc that happens to name `mode` exactly in
|
|
606
|
+
// a code tick (90 on one token, 98 with coverage), so API enum docs outrank
|
|
607
|
+
// the guide section.
|
|
608
|
+
const heading = headingWithPhrase(term, candidate.titles);
|
|
609
|
+
if (heading != null) {
|
|
610
|
+
return asFull({
|
|
611
|
+
score: TITLE_PHRASE_SCORE,
|
|
612
|
+
reason: `title "${heading}" holds the whole query`,
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
if (tokens.length <= 1) return fewTokens();
|
|
616
|
+
|
|
466
617
|
// Multi-word natural language: score each content token, counting only
|
|
467
618
|
// strong hits, then reward coverage so candidates matching more terms win.
|
|
468
619
|
let strongest = 0;
|
|
@@ -477,8 +628,42 @@ export function scoreQuery(term, tokens, candidate) {
|
|
|
477
628
|
hitTerms.push(tok);
|
|
478
629
|
}
|
|
479
630
|
}
|
|
631
|
+
// The reverse of the title tier, a step lower: the query holds a whole title
|
|
632
|
+
// of two words or more, so the reader asked a question around the section's
|
|
633
|
+
// name ("how do I add dark mode"). Coverage breaks ties between sections
|
|
634
|
+
// that share a title such as "Best Practices".
|
|
635
|
+
const named = titleInQuery(term, candidate.titles);
|
|
636
|
+
if (named != null) {
|
|
637
|
+
return {
|
|
638
|
+
score: TITLE_IN_QUERY_SCORE + Math.min(matched, 9),
|
|
639
|
+
reason: `the query names the title "${named}"`,
|
|
640
|
+
matched,
|
|
641
|
+
total,
|
|
642
|
+
};
|
|
643
|
+
}
|
|
480
644
|
if (matched === 0) return full ? asFull(full) : null;
|
|
481
645
|
|
|
646
|
+
const reason = `matches ${matched}/${tokens.length} terms: ${hitTerms.join(', ')}`;
|
|
647
|
+
|
|
648
|
+
// Every word matched is its own tier. Summed per word, a doc that matches
|
|
649
|
+
// both words of `troubleshoot integration` in its title and description
|
|
650
|
+
// (50 + bonus + coverage = 77) lost to thirty docs that each match
|
|
651
|
+
// `integration` alone, by name or in a code tick (98-108). The reader asked for
|
|
652
|
+
// both; a candidate that has both comes first, ordered among its peers by
|
|
653
|
+
// how strong its strongest match is. It needs one keyword-strength hit:
|
|
654
|
+
// every word mentioned in prose, or rendered by a page, is breadth, and
|
|
655
|
+
// stays on the token sum below an exact hit on one word.
|
|
656
|
+
if (matched === tokens.length && strongest >= STRONG_TOKEN_SCORE) {
|
|
657
|
+
return {
|
|
658
|
+
score:
|
|
659
|
+
FULL_COVERAGE_SCORE +
|
|
660
|
+
Math.floor((strongest - MIN_TOKEN_SCORE) / 6.25),
|
|
661
|
+
reason,
|
|
662
|
+
matched,
|
|
663
|
+
total,
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
|
|
482
667
|
// Base the score on the STRONGEST concept that matched, plus a bonus per
|
|
483
668
|
// additional matched concept and a coverage term.
|
|
484
669
|
//
|
|
@@ -500,14 +685,8 @@ export function scoreQuery(term, tokens, candidate) {
|
|
|
500
685
|
const tokenScore = Math.round(
|
|
501
686
|
strongest + Math.min(matched - 1, 3) * 12 + coverage * 15,
|
|
502
687
|
);
|
|
503
|
-
|
|
504
688
|
if (full && full.score >= tokenScore) return asFull(full);
|
|
505
|
-
return {
|
|
506
|
-
score: tokenScore,
|
|
507
|
-
reason: `matches ${matched}/${tokens.length} terms: ${hitTerms.join(', ')}`,
|
|
508
|
-
matched,
|
|
509
|
-
total,
|
|
510
|
-
};
|
|
689
|
+
return {score: tokenScore, reason, matched, total};
|
|
511
690
|
}
|
|
512
691
|
|
|
513
692
|
/**
|
|
@@ -518,6 +697,8 @@ export function scoreQuery(term, tokens, candidate) {
|
|
|
518
697
|
* @param {string} term - Lowercased search term.
|
|
519
698
|
* @param {object} candidate
|
|
520
699
|
* @param {string} candidate.name - Primary identifier (component/hook name, topic, template name).
|
|
700
|
+
* @param {string} [candidate.domain] - A component, hook, or template name
|
|
701
|
+
* also matches typed as words: `command palette` is CommandPalette.
|
|
521
702
|
* @param {string[]} [candidate.keywords] - Authored intent (componentsUsed, category words).
|
|
522
703
|
* @param {string[]} [candidate.weakKeywords] - Derived signal (components a page renders).
|
|
523
704
|
* @param {string} [candidate.description]
|
|
@@ -530,6 +711,7 @@ export function scoreCandidate(
|
|
|
530
711
|
term,
|
|
531
712
|
{
|
|
532
713
|
name,
|
|
714
|
+
domain,
|
|
533
715
|
keywords = [],
|
|
534
716
|
weakKeywords = [],
|
|
535
717
|
description = '',
|
|
@@ -552,10 +734,26 @@ export function scoreCandidate(
|
|
|
552
734
|
};
|
|
553
735
|
|
|
554
736
|
const nameLower = name.toLowerCase();
|
|
737
|
+
// A placed guide's name is its route, and the route's last segment is its
|
|
738
|
+
// name too, as a flat topic's is: `codemods` is cli/integrations/codemods.
|
|
739
|
+
const leafLower = nameLower.slice(nameLower.lastIndexOf('/') + 1);
|
|
555
740
|
|
|
556
741
|
// ── Name signals ────────────────────────────────────────────────
|
|
557
|
-
|
|
742
|
+
// A plural of the name is the name: `integration` is the `integrations`
|
|
743
|
+
// guides, `tab` the `tabs` doc.
|
|
744
|
+
// A component, hook, or template name typed as words is its name:
|
|
745
|
+
// `command palette` is CommandPalette. A doc's name is a route or key,
|
|
746
|
+
// matched as written.
|
|
747
|
+
const spelled =
|
|
748
|
+
domain !== 'doc' &&
|
|
749
|
+
!/[\s_-]/.test(nameLower) &&
|
|
750
|
+
nameLower === term.replace(/\s+/g, '');
|
|
751
|
+
if (nameLower === term || leafLower === term || spelled) {
|
|
558
752
|
consider(100, 'exact name');
|
|
753
|
+
} else if (pluralOf(nameLower, term) || pluralOf(term, nameLower)) {
|
|
754
|
+
// One point under the exact spelling, so the doc named `tokens` still
|
|
755
|
+
// outranks the Token component for `tokens`.
|
|
756
|
+
consider(99, 'plural of the name');
|
|
559
757
|
} else {
|
|
560
758
|
if (sameWord(term, nameLower)) consider(95, `name "${name}"`);
|
|
561
759
|
// The term is a word of the name, or starts one: "input" in TextInput.
|
|
@@ -962,11 +1160,14 @@ async function gatherDocs(cwd) {
|
|
|
962
1160
|
keywords: [
|
|
963
1161
|
node.route.slice(node.route.lastIndexOf('/') + 1),
|
|
964
1162
|
...(Array.isArray(selfDoc?.keywords) ? selfDoc.keywords : []),
|
|
1163
|
+
// A namespace doc's own keywords, which it declares for search.
|
|
1164
|
+
...(Array.isArray(node.keywords) ? node.keywords : []),
|
|
965
1165
|
...defined,
|
|
966
1166
|
...codeTerms({content}),
|
|
967
1167
|
],
|
|
968
1168
|
description: node.summary || '',
|
|
969
1169
|
prose: sectionProse({title: node.title, content}),
|
|
1170
|
+
titles: [node.title],
|
|
970
1171
|
_topic: node.route,
|
|
971
1172
|
_title: path.join(' › '),
|
|
972
1173
|
_command: `astryx docs ${node.route}`,
|
|
@@ -1086,12 +1287,16 @@ function topicCandidates(
|
|
|
1086
1287
|
const sections = doc?.sections ?? [];
|
|
1087
1288
|
const docTitle = path || doc?.title || title || name;
|
|
1088
1289
|
const split = sections.length > 1;
|
|
1290
|
+
// A placed guide also answers to its last route segment's words:
|
|
1291
|
+
// `quick start` is cli/integrations/quick-start.
|
|
1292
|
+
const leaf = name.slice(name.lastIndexOf('/') + 1);
|
|
1089
1293
|
/** @type {Candidate[]} */
|
|
1090
1294
|
const out = [
|
|
1091
1295
|
{
|
|
1092
1296
|
domain: 'doc',
|
|
1093
1297
|
name,
|
|
1094
1298
|
keywords: [
|
|
1299
|
+
...(leaf !== name ? [leaf.replaceAll('-', ' ')] : []),
|
|
1095
1300
|
...(doc?.title || title ? [doc?.title || title] : []),
|
|
1096
1301
|
...(Array.isArray(doc?.keywords) ? doc.keywords : []),
|
|
1097
1302
|
],
|
|
@@ -1099,6 +1304,11 @@ function topicCandidates(
|
|
|
1099
1304
|
prose: split
|
|
1100
1305
|
? sections.map(section => section.title).filter(Boolean)
|
|
1101
1306
|
: sections.flatMap(sectionProse),
|
|
1307
|
+
titles: [
|
|
1308
|
+
doc?.title || title || name,
|
|
1309
|
+
// A topic read whole answers for the headings inside it.
|
|
1310
|
+
...(split ? [] : sections.flatMap(s => [s.title, ...headings(s)])),
|
|
1311
|
+
].filter(Boolean),
|
|
1102
1312
|
_topic: name,
|
|
1103
1313
|
_title: docTitle,
|
|
1104
1314
|
_command: split ? `astryx docs ${name} --index` : `astryx docs ${name}`,
|
|
@@ -1119,6 +1329,7 @@ function topicCandidates(
|
|
|
1119
1329
|
],
|
|
1120
1330
|
description: sectionSummary(section),
|
|
1121
1331
|
prose: sectionProse(section),
|
|
1332
|
+
titles: [section.title, ...headings(section)].filter(Boolean),
|
|
1122
1333
|
_topic: name,
|
|
1123
1334
|
_section: key,
|
|
1124
1335
|
_title: `${docTitle} › ${section.title}`,
|
|
@@ -1296,10 +1507,11 @@ export async function search(query, options = {}) {
|
|
|
1296
1507
|
const tokens = tokenizeQuery(term);
|
|
1297
1508
|
|
|
1298
1509
|
// `astryx docs` reads docs without @astryxdesign/core, so a docs-only
|
|
1299
|
-
// search must too. Every other domain reads core
|
|
1510
|
+
// search must too. Every other domain reads core: asked for by name, it is
|
|
1511
|
+
// an error without core; an open search then covers the docs alone.
|
|
1300
1512
|
const docsOnly = type === 'doc';
|
|
1301
1513
|
const coreDir = docsOnly ? null : findCoreDir(cwd);
|
|
1302
|
-
if (!docsOnly && !coreDir) {
|
|
1514
|
+
if (type && !docsOnly && !coreDir) {
|
|
1303
1515
|
throw new AstryxError(
|
|
1304
1516
|
'Could not find @astryxdesign/core package',
|
|
1305
1517
|
undefined,
|
|
@@ -1309,7 +1521,7 @@ export async function search(query, options = {}) {
|
|
|
1309
1521
|
|
|
1310
1522
|
// Gather candidates from each requested domain in parallel.
|
|
1311
1523
|
/** @param {string} d */
|
|
1312
|
-
const wants = d => !type || type === d;
|
|
1524
|
+
const wants = d => (!type && (coreDir != null || d === 'doc')) || type === d;
|
|
1313
1525
|
const [components, hooks, docTopics, templates] = await Promise.all([
|
|
1314
1526
|
wants('component')
|
|
1315
1527
|
? gatherComponents(/** @type {string} */ (coreDir), cwd)
|
|
@@ -29,17 +29,19 @@ export const doc = {
|
|
|
29
29
|
name: 'component',
|
|
30
30
|
type: 'string',
|
|
31
31
|
description:
|
|
32
|
-
|
|
32
|
+
"Component name to copy (e.g. 'Button'). Omit to list the swizzlable components.",
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
name: 'options.cwd',
|
|
36
36
|
type: 'string',
|
|
37
37
|
description: 'Directory to resolve @astryxdesign/core from.',
|
|
38
|
+
default: 'process.cwd()',
|
|
38
39
|
},
|
|
39
40
|
{
|
|
40
41
|
name: 'options.output',
|
|
41
42
|
type: 'string',
|
|
42
|
-
description:
|
|
43
|
+
description:
|
|
44
|
+
'Output directory, relative to cwd. An absolute path, or one that resolves outside cwd, throws ERR_PATH_TRAVERSAL.',
|
|
43
45
|
default: "'./components/astryx'",
|
|
44
46
|
},
|
|
45
47
|
{
|
|
@@ -71,7 +73,7 @@ export const doc = {
|
|
|
71
73
|
{
|
|
72
74
|
type: 'swizzle.copy',
|
|
73
75
|
description:
|
|
74
|
-
'A receipt after copying the component into the project: the component name, owning package, output directory, files-copied count, the written file names, whether any file uses StyleX, and an
|
|
76
|
+
'A receipt after copying the component into the project: the component name, owning package, output directory, files-copied count, the written file names, whether any file uses StyleX, and, when the owner has an issues URL, feedback ({issuesUrl, ghCommand?}): where to report the gap that led to swizzling.',
|
|
75
77
|
},
|
|
76
78
|
],
|
|
77
79
|
throws: [
|
|
@@ -81,7 +83,7 @@ export const doc = {
|
|
|
81
83
|
},
|
|
82
84
|
{
|
|
83
85
|
code: 'ERR_PATH_TRAVERSAL',
|
|
84
|
-
when: 'the component name contains a path separator or traversal, output resolves outside cwd, or an existing output file or directory is a symlink that resolves outside cwd',
|
|
86
|
+
when: 'the component name contains a path separator or traversal, output is absolute or resolves outside cwd, or an existing output file or directory is a symlink that resolves outside cwd',
|
|
85
87
|
},
|
|
86
88
|
{
|
|
87
89
|
code: 'ERR_UNKNOWN_COMPONENT',
|
|
@@ -108,7 +110,7 @@ export const doc = {
|
|
|
108
110
|
{label: 'Eject a component', code: "await swizzle('Button');"},
|
|
109
111
|
{
|
|
110
112
|
label: 'Disambiguate by package',
|
|
111
|
-
code: "await swizzle('Button', {package: '@astryxdesign/core'});",
|
|
113
|
+
code: "await swizzle('Button', {package: '@astryxdesign/core', overwrite: true});",
|
|
112
114
|
},
|
|
113
115
|
{
|
|
114
116
|
label: 'Custom output directory',
|