@astryxdesign/cli 0.1.8-canary.4a7ce02 → 0.1.8-canary.4cc4d7a
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/bin/astryx.mjs +2 -0
- package/docs/elevation.doc.mjs +79 -1
- package/docs/internationalization.doc.mjs +6 -2
- package/package.json +11 -10
- package/scripts/postinstall.mjs +3 -3
- package/src/api/blog.mjs +39 -3
- package/src/api/component.mjs +40 -17
- package/src/api/discover.mjs +49 -12
- package/src/api/docs.mjs +43 -17
- package/src/api/doctor.mjs +6 -3
- package/src/api/error.mjs +2 -2
- package/src/api/hook.mjs +5 -5
- package/src/api/layout.mjs +56 -7
- package/src/api/search.mjs +65 -6
- package/src/api/template.mjs +89 -12
- package/src/api/theme-add.mjs +31 -3
- package/src/api/validate-integration.mjs +15 -10
- package/src/codemods/ensure-jscodeshift.mjs +1 -1
- package/src/codemods/integration-discovery.mjs +5 -2
- package/src/codemods/integration-runner.mjs +8 -3
- package/src/codemods/registry.mjs +3 -1
- package/src/codemods/run-codemod.mjs +17 -10
- package/src/codemods/runner.mjs +22 -12
- package/src/codemods/transforms/v0.0.10/remove-size-props.mjs +9 -4
- package/src/codemods/transforms/v0.0.12/add-is-icon-only.mjs +27 -22
- package/src/codemods/transforms/v0.0.13/icon-name-deprecations.mjs +12 -5
- package/src/codemods/transforms/v0.0.13/rename-attachments-to-drawer.mjs +15 -8
- package/src/codemods/transforms/v0.0.13/toolbar-density-to-size.mjs +10 -4
- package/src/codemods/transforms/v0.0.14/rename-action-props.mjs +11 -5
- package/src/codemods/transforms/v0.0.14/rename-section-wash-to-muted.mjs +11 -6
- package/src/codemods/transforms/v0.0.14/rename-status-variants.mjs +15 -10
- package/src/codemods/transforms/v0.0.15/migrate-item-children-to-endcontent.mjs +13 -8
- package/src/codemods/transforms/v0.0.15/migrate-selector-children-to-render-option.mjs +18 -13
- package/src/codemods/transforms/v0.0.15/migrate-theme-selectors-to-data-attrs.mjs +8 -4
- package/src/codemods/transforms/v0.0.15/rename-date-picker-to-input.mjs +11 -6
- package/src/codemods/transforms/v0.0.15/rename-imperative-ref-to-handleRef.mjs +6 -1
- package/src/codemods/transforms/v0.0.15/rename-isStreaming-to-isStopShown.mjs +6 -1
- package/src/codemods/transforms/v0.0.15/rename-stack-element-to-as.mjs +8 -3
- package/src/codemods/transforms/v0.0.2/migrate-badge-dot-to-statusdot.mjs +17 -10
- package/src/codemods/transforms/v0.0.2/migrate-gap-to-numeric.mjs +7 -1
- package/src/codemods/transforms/v0.0.2/migrate-isFullBleed-to-padding.mjs +9 -4
- package/src/codemods/transforms/v0.0.2/migrate-useXDSIcon-to-getIcon.mjs +12 -7
- package/src/codemods/transforms/v0.0.2/rename-banner-endButton-to-endContent.mjs +7 -2
- package/src/codemods/transforms/v0.0.2/rename-form-tooltip-startIcon.mjs +8 -2
- package/src/codemods/transforms/v0.0.2/rename-isShown-to-isOpen.mjs +7 -2
- package/src/codemods/transforms/v0.0.2/rename-selector-items-to-options.mjs +7 -2
- package/src/codemods/transforms/v0.0.2/rename-sidenav-header-to-heading.mjs +11 -4
- package/src/codemods/transforms/v0.0.2/rename-topnav-title-to-heading.mjs +10 -4
- package/src/codemods/transforms/v0.0.2/unify-uncontrolled-to-defaultX.mjs +7 -2
- package/src/codemods/transforms/v0.0.2/unify-visibility-to-onOpenChange.mjs +10 -5
- package/src/codemods/transforms/v0.0.6/migrate-badge-children-to-label.mjs +7 -2
- package/src/codemods/transforms/v0.0.6/migrate-collapse-to-collapsible.mjs +9 -3
- package/src/codemods/transforms/v0.0.6/migrate-radius-tokens.mjs +11 -5
- package/src/codemods/transforms/v0.0.6/migrate-shadow-tokens.mjs +13 -6
- package/src/codemods/transforms/v0.0.6/migrate-skeleton-radius.mjs +7 -1
- package/src/codemods/transforms/v0.0.6/migrate-token-names.mjs +11 -5
- package/src/codemods/transforms/v0.0.7/rename-banner-variant-to-container.mjs +8 -3
- package/src/codemods/transforms/v0.0.8/migrate-token-renames.mjs +15 -8
- package/src/codemods/transforms/v0.0.8/rename-endslot-to-endcontent.mjs +7 -2
- package/src/codemods/transforms/v0.1.0/drop-xds-prefix-imports.mjs +19 -14
- package/src/codemods/transforms/v0.1.0/migrate-xds-css-surfaces.mjs +21 -17
- package/src/codemods/transforms/v0.1.0/migrate-xds-declare-module.mjs +7 -2
- package/src/codemods/transforms/v0.1.0/migrate-xds-module-specifiers.mjs +16 -11
- package/src/codemods/transforms/v0.1.2/rename-text-color-active-to-accent.mjs +15 -9
- package/src/codemods/transforms/v0.1.3/migrate-layout-components-to-experimental.mjs +17 -12
- package/src/codemods/transforms/v0.1.5/rename-switch-label-spacing-default-to-hug.mjs +15 -9
- package/src/codemods/transforms/v0.1.7/migrate-table-tableprops-to-direct-props.mjs +17 -10
- package/src/codemods/transforms/v0.1.7/rename-table-renderprops-styles-to-xstyle.mjs +18 -13
- package/src/codemods/transforms/v0.1.8/rename-avatar-size-scale.mjs +15 -10
- package/src/commands/agent-docs.mjs +23 -4
- package/src/commands/blog.mjs +44 -3
- package/src/commands/build-theme.mjs +119 -40
- package/src/commands/build.mjs +15 -5
- package/src/commands/component/index.mjs +32 -5
- package/src/commands/discover.mjs +12 -3
- package/src/commands/docs.mjs +35 -3
- package/src/commands/ensure-core-built.mjs +3 -2
- package/src/commands/hook/index.mjs +27 -4
- package/src/commands/init.mjs +13 -2
- package/src/commands/layout.mjs +55 -15
- package/src/commands/search.mjs +14 -7
- package/src/commands/swizzle.mjs +11 -4
- package/src/commands/template.mjs +63 -12
- package/src/commands/upgrade.mjs +143 -35
- package/src/index.mjs +6 -5
- package/src/lib/cli-error.mjs +2 -2
- package/src/lib/component-discovery.mjs +44 -2
- package/src/lib/component-format.mjs +65 -24
- package/src/lib/component-loader.mjs +27 -13
- package/src/lib/hook-discovery.mjs +14 -1
- package/src/lib/hook-format.mjs +29 -5
- package/src/lib/integration-warnings.mjs +1 -1
- package/src/lib/integrations.mjs +20 -0
- package/src/lib/json-shim.mjs +11 -6
- package/src/lib/json.mjs +3 -3
- package/src/lib/levenshtein.mjs +6 -0
- package/src/lib/manifest.mjs +5 -5
- package/src/lib/module-loader.mjs +1 -0
- package/src/lib/package-scanner.mjs +47 -0
- package/src/lib/project.mjs +46 -16
- package/src/lib/resolve-theme.mjs +5 -0
- package/src/lib/string-utils.mjs +15 -0
- package/src/lib/term-log.mjs +11 -2
- package/src/lib/xle/browser.mjs +18 -8
- package/src/lib/xle/expand.mjs +155 -22
- package/src/lib/xle/parse.mjs +119 -13
- package/src/lib/xle/print.mjs +54 -14
- package/src/lib/xle/registry-core.mjs +28 -3
- package/src/lib/xle/registry.mjs +9 -4
- package/src/lib/xle/splice.mjs +13 -2
- package/src/lib/xle/validate.mjs +95 -14
- package/src/lib/xle/xle-ast.d.ts +214 -0
- package/src/types/api.d.ts +4 -9
- package/src/types/base.d.ts +39 -4
- package/src/types/build.d.ts +23 -0
- package/src/types/codemod.d.ts +85 -0
- package/src/types/index.d.ts +1 -0
- package/src/types/jscodeshift.d.ts +19 -0
- package/src/types/layout.d.ts +74 -0
- package/src/types/swizzle.d.ts +4 -0
- package/src/types/theme.d.ts +30 -0
- package/src/types/upgrade.d.ts +8 -1
- package/src/utils/package-manager.mjs +30 -4
- package/src/utils/paths.mjs +3 -0
- package/src/utils/update-check.mjs +3 -3
- package/src/utils/update-check.test.mjs +3 -3
- package/templates/blocks/components/Avatar/AvatarShowcase.tsx +1 -1
- package/templates/blocks/components/Banner/BannerFloating.doc.mjs +14 -0
- package/templates/blocks/components/Banner/BannerFloating.tsx +16 -0
- package/templates/blocks/components/Button/ButtonFloating.doc.mjs +14 -0
- package/templates/blocks/components/Button/ButtonFloating.tsx +37 -0
- package/templates/blocks/components/ButtonGroup/ButtonGroupFloating.doc.mjs +14 -0
- package/templates/blocks/components/ButtonGroup/ButtonGroupFloating.tsx +23 -0
- package/templates/blocks/components/Card/CardElevations.doc.mjs +14 -0
- package/templates/blocks/components/Card/CardElevations.tsx +32 -0
- package/templates/blocks/components/Card/ClickableCardElevated.doc.mjs +14 -0
- package/templates/blocks/components/Card/ClickableCardElevated.tsx +21 -0
- package/templates/blocks/components/Card/SelectableCardElevated.doc.mjs +14 -0
- package/templates/blocks/components/Card/SelectableCardElevated.tsx +37 -0
- package/templates/blocks/components/ChatComposer/ChatComposerFlat.doc.mjs +14 -0
- package/templates/blocks/components/ChatComposer/ChatComposerFlat.tsx +75 -0
- package/templates/blocks/components/IconButton/IconButtonFloating.doc.mjs +14 -0
- package/templates/blocks/components/IconButton/IconButtonFloating.tsx +37 -0
- package/templates/blocks/components/OverflowList/OverflowListCappedToolbar.doc.mjs +14 -0
- package/templates/blocks/components/OverflowList/OverflowListCappedToolbar.tsx +39 -0
- package/templates/blocks/components/OverflowList/OverflowListMultiRowTags.doc.mjs +14 -0
- package/templates/blocks/components/OverflowList/OverflowListMultiRowTags.tsx +44 -0
- package/templates/blocks/components/Thumbnail/ThumbnailElevated.doc.mjs +14 -0
- package/templates/blocks/components/Thumbnail/ThumbnailElevated.tsx +31 -0
package/bin/astryx.mjs
CHANGED
|
@@ -26,6 +26,7 @@ import {realpathSync} from 'node:fs';
|
|
|
26
26
|
import {dirname, join} from 'node:path';
|
|
27
27
|
|
|
28
28
|
const binDir = dirname(realpathSync(fileURLToPath(import.meta.url)));
|
|
29
|
+
/** @param {string} rel */
|
|
29
30
|
const importSrc = rel =>
|
|
30
31
|
import(pathToFileURL(join(binDir, '..', 'src', rel)).href);
|
|
31
32
|
|
|
@@ -72,6 +73,7 @@ function inJsonMode() {
|
|
|
72
73
|
return isJsonMode() || process.argv.slice(2).includes('--json');
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
/** @param {unknown} err */
|
|
75
77
|
function handleFatal(err) {
|
|
76
78
|
// CommanderError (parse errors, --help, unknown command) routes
|
|
77
79
|
// through the JSON shim so that a --json consumer always gets a
|
package/docs/elevation.doc.mjs
CHANGED
|
@@ -36,10 +36,85 @@ export const docs = {
|
|
|
36
36
|
},
|
|
37
37
|
],
|
|
38
38
|
},
|
|
39
|
+
{
|
|
40
|
+
title: 'Choosing a level',
|
|
41
|
+
category: 'foundations',
|
|
42
|
+
content: [
|
|
43
|
+
{
|
|
44
|
+
type: 'prose',
|
|
45
|
+
text: 'Pick the level by how far the surface sits from the page, not by how much shadow you want. Elevation encodes stacking order: a higher level means the surface is layered over more of the UI. Use exactly one level per surface, and only raise a surface above `none` when it actually sits above other content.',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
type: 'table',
|
|
49
|
+
headers: ['Level', 'When to use', 'Examples'],
|
|
50
|
+
rows: [
|
|
51
|
+
[
|
|
52
|
+
'none',
|
|
53
|
+
'The component is flat and embedded in the surface — it is part of the page, not layered above it. This is the default for every surface except ChatComposer.',
|
|
54
|
+
'A Card in a grid, an inline Banner, a standard Button',
|
|
55
|
+
],
|
|
56
|
+
[
|
|
57
|
+
'low',
|
|
58
|
+
'The component is in the normal page flow but should read as distinct from the background. Use for emphasis or to separate the component from the surface behind it — the component still sits on the page, it is not floating over other content.',
|
|
59
|
+
'A raised Card that needs emphasis, a ChatComposer, a resting Thumbnail',
|
|
60
|
+
],
|
|
61
|
+
[
|
|
62
|
+
'med',
|
|
63
|
+
'The component sits over other content on the same page — it floats above nearby elements but not the whole screen.',
|
|
64
|
+
'A Popover, a floating Banner, a floating action Button',
|
|
65
|
+
],
|
|
66
|
+
[
|
|
67
|
+
'high',
|
|
68
|
+
'The component is placed over the entire UI — it is the topmost layer and typically has a backdrop or takes focus from everything else.',
|
|
69
|
+
'A modal Dialog, a full-screen overlay surface',
|
|
70
|
+
],
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
type: 'prose',
|
|
75
|
+
text: 'If two surfaces overlap, the one on top takes the higher level. If a surface does not overlap anything, it is `none` or `low` — never `med` or `high`.',
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
title: 'The elevation prop',
|
|
81
|
+
category: 'foundations',
|
|
82
|
+
content: [
|
|
83
|
+
{
|
|
84
|
+
type: 'prose',
|
|
85
|
+
text: 'Configurable surfaces expose a single `elevation` prop instead of asking consumers to hand-write a box-shadow. It takes the graded enum `none | low | med | high`, narrowed per component to the steps that surface needs: Card, ClickableCard, SelectableCard, Button, IconButton, ButtonGroup, Thumbnail, and Banner expose the full scale, while ChatComposer exposes only `none | low`. `none` is a flat literal (`box-shadow: none`); the other levels map to the `--shadow-*` tokens above, so a surface stays theme-agnostic.',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
type: 'prose',
|
|
89
|
+
text: 'Prop defaults preserve current appearance: every surface defaults to `none` except ChatComposer, which defaults to `low` to keep its raised look. Set `elevation="none"` to flatten it — the flat composer draws a border with the same rest / hover / focus treatment as a text input. Thumbnail keeps its existing hover-only shadow at the `none` default.',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
type: 'code',
|
|
93
|
+
lang: 'tsx',
|
|
94
|
+
label: 'Raising a surface with the elevation prop',
|
|
95
|
+
code: `// Flat by default — raise only when the surface needs to float.
|
|
96
|
+
<Card elevation="low">Raised card</Card>
|
|
97
|
+
|
|
98
|
+
// A floating action button.
|
|
99
|
+
<IconButton icon={<Icon icon="add" />} label="New" variant="primary" elevation="med" />
|
|
100
|
+
|
|
101
|
+
// Flatten the composer (defaults to 'low').
|
|
102
|
+
<ChatComposer elevation="none" onSubmit={handleSubmit} />`,
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
type: 'prose',
|
|
106
|
+
text: 'Always float — no prop: intrinsic overlays (Dialog, Popover, Tooltip, Toast, HoverCard, DropdownMenu, and the components that compose them) bake their elevation in and never expose the prop. Flow content (inputs, Text, layout) is never elevated; input fields use inset rings, which are a separate concept from elevation.',
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
},
|
|
39
110
|
{
|
|
40
111
|
title: 'Usage',
|
|
41
112
|
category: 'foundations',
|
|
42
113
|
content: [
|
|
114
|
+
{
|
|
115
|
+
type: 'prose',
|
|
116
|
+
text: 'When building a custom surface, read elevation from the token scale rather than a hand-rolled shadow — the same tokens the elevation prop resolves to.',
|
|
117
|
+
},
|
|
43
118
|
{
|
|
44
119
|
type: 'code',
|
|
45
120
|
lang: 'tsx',
|
|
@@ -68,7 +143,8 @@ const styles = stylex.create({
|
|
|
68
143
|
type: 'list',
|
|
69
144
|
style: 'do',
|
|
70
145
|
items: [
|
|
71
|
-
'
|
|
146
|
+
'Reach for the `elevation` prop on a configurable surface before writing any custom shadow.',
|
|
147
|
+
'Choose the level by how far the surface sits from the page: `none` when flat/embedded, `low` when in-flow but distinct, `med` when over page content, `high` when over the whole UI. See "Choosing a level".',
|
|
72
148
|
'Use inset shadows for input focus/selection states; they compose better than outlines.',
|
|
73
149
|
],
|
|
74
150
|
},
|
|
@@ -76,6 +152,8 @@ const styles = stylex.create({
|
|
|
76
152
|
type: 'list',
|
|
77
153
|
style: 'dont',
|
|
78
154
|
items: [
|
|
155
|
+
'Hand-write a `box-shadow` in app code — set the `elevation` prop or read `shadowVars` instead.',
|
|
156
|
+
'Raise a surface that does not sit above other content — a non-overlapping surface is `none` or `low`, never `med` or `high`.',
|
|
79
157
|
'Stack multiple elevation levels on the same element.',
|
|
80
158
|
'Use elevation shadows for decorative borders. Use --color-border tokens instead.',
|
|
81
159
|
],
|
|
@@ -49,7 +49,7 @@ function SaveButton() {
|
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
type: 'prose',
|
|
52
|
-
text: "Astryx ships translations only for English today. First-party translations for other locales are on the roadmap
|
|
52
|
+
text: "Astryx ships translations only for English today. First-party translations for other locales are on the roadmap. In the meantime, if you want astryx UI translated into another locale, you can ship your own catalog through the `messages` prop (covered in the next section). If you're using `useTranslator` for your own strings, you'll want to ship your own catalog either way, since astryx only carries the fallback for `@astryx.*` keys, not the ones you author.",
|
|
53
53
|
},
|
|
54
54
|
],
|
|
55
55
|
},
|
|
@@ -78,7 +78,7 @@ import fr from './locales/astryx/fr.json';
|
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
type: 'prose',
|
|
81
|
-
text: 'A community-maintained set of astryx translations is on the roadmap but not shipped yet. For now, consumer apps that ship in multiple languages own their astryx catalogs alongside their app catalogs.
|
|
81
|
+
text: 'A community-maintained set of astryx translations is on the roadmap but not shipped yet. For now, consumer apps that ship in multiple languages own their astryx catalogs alongside their app catalogs. Translations are coordinated through Crowdin — contribute at https://crowdin.com/project/astryx.',
|
|
82
82
|
},
|
|
83
83
|
],
|
|
84
84
|
},
|
|
@@ -237,6 +237,10 @@ export default function App() {
|
|
|
237
237
|
type: 'prose',
|
|
238
238
|
text: "Astryx's own strings live in `packages/core/locales/en.json`. New user-facing strings must go through `useTranslator`; this is enforced by the `@astryx/no-hardcoded-i18n-string` ESLint rule. See the AI contribution guide for the alias-and-resolve pattern used when adding new keys.",
|
|
239
239
|
},
|
|
240
|
+
{
|
|
241
|
+
type: 'prose',
|
|
242
|
+
text: 'Translators: Crowdin is the preferred way to contribute — join a language at https://crowdin.com/project/astryx, translate strings in the web UI, and your work syncs back to the repo without opening a PR. Direct PRs against `packages/core/locales/*.json` also work if you prefer that flow.',
|
|
243
|
+
},
|
|
240
244
|
],
|
|
241
245
|
},
|
|
242
246
|
],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/cli",
|
|
3
|
-
"version": "0.1.8-canary.
|
|
3
|
+
"version": "0.1.8-canary.4cc4d7a",
|
|
4
4
|
"displayName": "CLI",
|
|
5
5
|
"description": "Scaffold projects, browse templates, generate themes, and get agent-ready docs from the command line.",
|
|
6
6
|
"author": "Meta Open Source",
|
|
@@ -79,10 +79,10 @@
|
|
|
79
79
|
"zod": "^4.4.3"
|
|
80
80
|
},
|
|
81
81
|
"peerDependencies": {
|
|
82
|
-
"@astryxdesign/charts": "0.1.8-canary.
|
|
83
|
-
"@astryxdesign/core": "0.1.8-canary.
|
|
84
|
-
"@astryxdesign/lab": "0.1.8-canary.
|
|
85
|
-
"@astryxdesign/theme-neutral": "0.1.8-canary.
|
|
82
|
+
"@astryxdesign/charts": "0.1.8-canary.4cc4d7a",
|
|
83
|
+
"@astryxdesign/core": "0.1.8-canary.4cc4d7a",
|
|
84
|
+
"@astryxdesign/lab": "0.1.8-canary.4cc4d7a",
|
|
85
|
+
"@astryxdesign/theme-neutral": "0.1.8-canary.4cc4d7a",
|
|
86
86
|
"gpt-tokenizer": "^3.4.0"
|
|
87
87
|
},
|
|
88
88
|
"peerDependenciesMeta": {
|
|
@@ -100,16 +100,17 @@
|
|
|
100
100
|
}
|
|
101
101
|
},
|
|
102
102
|
"devDependencies": {
|
|
103
|
-
"@astryxdesign/charts": "0.1.8-canary.
|
|
104
|
-
"@astryxdesign/core": "0.1.8-canary.
|
|
105
|
-
"@astryxdesign/lab": "0.1.8-canary.
|
|
106
|
-
"@astryxdesign/theme-neutral": "0.1.8-canary.
|
|
103
|
+
"@astryxdesign/charts": "0.1.8-canary.4cc4d7a",
|
|
104
|
+
"@astryxdesign/core": "0.1.8-canary.4cc4d7a",
|
|
105
|
+
"@astryxdesign/lab": "0.1.8-canary.4cc4d7a",
|
|
106
|
+
"@astryxdesign/theme-neutral": "0.1.8-canary.4cc4d7a",
|
|
107
107
|
"gpt-tokenizer": "^3.4.0"
|
|
108
108
|
},
|
|
109
109
|
"scripts": {
|
|
110
110
|
"astryx": "node bin/astryx.mjs",
|
|
111
111
|
"postinstall": "node scripts/postinstall.mjs",
|
|
112
112
|
"typecheck:template-docs": "tsc --project tsconfig.template-docs.json",
|
|
113
|
-
"typecheck:json-api": "tsc --project tsconfig.json-api.json && tsc --project tsconfig.api-contract.json"
|
|
113
|
+
"typecheck:json-api": "tsc --project tsconfig.json-api.json && tsc --project tsconfig.api-contract.json",
|
|
114
|
+
"typecheck:strict": "tsc --project tsconfig.strict.json"
|
|
114
115
|
}
|
|
115
116
|
}
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -24,11 +24,11 @@ const HERE = fileURLToPath(import.meta.url);
|
|
|
24
24
|
* Pure decision: should the postinstall print the setup nudge? Split out so the
|
|
25
25
|
* matrix is unit-testable without an actual npm install.
|
|
26
26
|
*
|
|
27
|
-
* @param {object} opts
|
|
28
|
-
* @param {string} opts.scriptPath - Absolute path of this script (location tells
|
|
27
|
+
* @param {object} [opts]
|
|
28
|
+
* @param {string} [opts.scriptPath] - Absolute path of this script (location tells
|
|
29
29
|
* us dependency vs monorepo vs npx-cache).
|
|
30
30
|
* @param {string} [opts.npmCommand] - process.env.npm_command ('install', 'exec', …).
|
|
31
|
-
* @param {boolean} opts.isSetUp - Whether the project already ran init.
|
|
31
|
+
* @param {boolean} [opts.isSetUp] - Whether the project already ran init.
|
|
32
32
|
* @returns {boolean}
|
|
33
33
|
*/
|
|
34
34
|
export function shouldNudge({scriptPath, npmCommand, isSetUp} = {}) {
|
package/src/api/blog.mjs
CHANGED
|
@@ -20,6 +20,10 @@ const MAX_BYTES = 5 * 1024 * 1024; // 5 MB — a blog feed is never larger.
|
|
|
20
20
|
|
|
21
21
|
const FEED_URL = new URL('/rss.xml', SITE_URL).toString();
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* @param {string} url
|
|
25
|
+
* @returns {Promise<string>}
|
|
26
|
+
*/
|
|
23
27
|
async function fetchText(url) {
|
|
24
28
|
const controller = new AbortController();
|
|
25
29
|
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
@@ -31,7 +35,12 @@ async function fetchText(url) {
|
|
|
31
35
|
});
|
|
32
36
|
} catch (e) {
|
|
33
37
|
clearTimeout(timer);
|
|
34
|
-
const reason =
|
|
38
|
+
const reason =
|
|
39
|
+
e instanceof Error
|
|
40
|
+
? e.name === 'AbortError'
|
|
41
|
+
? 'timed out'
|
|
42
|
+
: e.message
|
|
43
|
+
: String(e);
|
|
35
44
|
throw new AstryxError(
|
|
36
45
|
`Could not reach ${url}: ${reason}`,
|
|
37
46
|
[],
|
|
@@ -61,6 +70,7 @@ async function fetchText(url) {
|
|
|
61
70
|
* Defense in depth: a post's plaintext URL comes from feed content. Require it
|
|
62
71
|
* to live on the canonical origin so even a tampered feed can't redirect the
|
|
63
72
|
* CLI to another host.
|
|
73
|
+
* @param {string} target
|
|
64
74
|
*/
|
|
65
75
|
function assertCanonicalOrigin(target) {
|
|
66
76
|
let targetOrigin;
|
|
@@ -82,6 +92,10 @@ function assertCanonicalOrigin(target) {
|
|
|
82
92
|
}
|
|
83
93
|
}
|
|
84
94
|
|
|
95
|
+
/**
|
|
96
|
+
* @param {string} value
|
|
97
|
+
* @returns {string}
|
|
98
|
+
*/
|
|
85
99
|
function unescapeXml(value) {
|
|
86
100
|
return value
|
|
87
101
|
.replace(/</g, '<')
|
|
@@ -91,12 +105,23 @@ function unescapeXml(value) {
|
|
|
91
105
|
.replace(/&/g, '&');
|
|
92
106
|
}
|
|
93
107
|
|
|
108
|
+
/**
|
|
109
|
+
* @param {string} item
|
|
110
|
+
* @param {string} name
|
|
111
|
+
* @returns {string}
|
|
112
|
+
*/
|
|
94
113
|
function tag(item, name) {
|
|
95
114
|
const m = item.match(new RegExp(`<${name}[^>]*>([\\s\\S]*?)</${name}>`));
|
|
96
115
|
return m ? unescapeXml(m[1].trim()) : '';
|
|
97
116
|
}
|
|
98
117
|
|
|
118
|
+
/**
|
|
119
|
+
* @param {string} item
|
|
120
|
+
* @param {string} name
|
|
121
|
+
* @returns {string[]}
|
|
122
|
+
*/
|
|
99
123
|
function tagAll(item, name) {
|
|
124
|
+
/** @type {string[]} */
|
|
100
125
|
const out = [];
|
|
101
126
|
const re = new RegExp(`<${name}[^>]*>([\\s\\S]*?)</${name}>`, 'g');
|
|
102
127
|
let m;
|
|
@@ -104,7 +129,11 @@ function tagAll(item, name) {
|
|
|
104
129
|
return out;
|
|
105
130
|
}
|
|
106
131
|
|
|
107
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* Extract the plaintext alternate href from an <item>.
|
|
134
|
+
* @param {string} item
|
|
135
|
+
* @returns {string | null}
|
|
136
|
+
*/
|
|
108
137
|
function textHref(item) {
|
|
109
138
|
// Match the atom:link alternate regardless of attribute order/quoting; then
|
|
110
139
|
// confirm it's the text/plain alternate before trusting the href.
|
|
@@ -121,7 +150,11 @@ function textHref(item) {
|
|
|
121
150
|
return null;
|
|
122
151
|
}
|
|
123
152
|
|
|
124
|
-
/**
|
|
153
|
+
/**
|
|
154
|
+
* Derive a slug from a post link (last path segment).
|
|
155
|
+
* @param {string} link
|
|
156
|
+
* @returns {string}
|
|
157
|
+
*/
|
|
125
158
|
function slugFromLink(link) {
|
|
126
159
|
try {
|
|
127
160
|
const path = new URL(link).pathname.replace(/\/$/, '');
|
|
@@ -131,6 +164,9 @@ function slugFromLink(link) {
|
|
|
131
164
|
}
|
|
132
165
|
}
|
|
133
166
|
|
|
167
|
+
/**
|
|
168
|
+
* @param {string} xml
|
|
169
|
+
*/
|
|
134
170
|
function parseFeed(xml) {
|
|
135
171
|
const items = [];
|
|
136
172
|
const re = /<item>([\s\S]*?)<\/item>/g;
|
package/src/api/component.mjs
CHANGED
|
@@ -29,17 +29,39 @@ import {searchComponents} from '../lib/string-utils.mjs';
|
|
|
29
29
|
import {AstryxError} from './error.mjs';
|
|
30
30
|
import {findShowcase, findRelatedBlocks} from './template.mjs';
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* A loaded component doc. `loadDocs` returns the authored `.doc.mjs` shape,
|
|
34
|
+
* which is either a single-component or multi-component doc; this loose view
|
|
35
|
+
* captures the fields the API reads across both forms.
|
|
36
|
+
* @typedef {object} LoadedComponentDoc
|
|
37
|
+
* @property {string} [name]
|
|
38
|
+
* @property {string} [description]
|
|
39
|
+
* @property {any[]} [props]
|
|
40
|
+
* @property {any[]} [components]
|
|
41
|
+
* @property {{description?: string}} [usage]
|
|
42
|
+
* @property {any} [theming]
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Options object for `loadDocs`, matching its declared parameter shape (used
|
|
47
|
+
* as a cast target so `lang` (which the API may hold as `string|null`) type
|
|
48
|
+
* checks against `loadDocs`'s `lang?: string`).
|
|
49
|
+
* @typedef {{zh?: boolean, dense?: boolean, lang?: string}} LoadDocsOpts
|
|
50
|
+
*/
|
|
51
|
+
|
|
32
52
|
/**
|
|
33
53
|
* Load the configured integrations for `cwd`, swallowing any config errors so
|
|
34
54
|
* component discovery never hard-fails on a malformed/absent integration. An
|
|
35
55
|
* empty list means "core only".
|
|
36
56
|
* @param {string} cwd
|
|
37
|
-
* @returns {Promise<
|
|
57
|
+
* @returns {Promise<import('../lib/integrations.mjs').LoadedIntegration[]>}
|
|
38
58
|
*/
|
|
39
59
|
async function loadIntegrationsSafely(cwd) {
|
|
40
60
|
try {
|
|
41
61
|
const project = await Project.load(cwd);
|
|
42
|
-
return
|
|
62
|
+
return /** @type {import('../lib/integrations.mjs').LoadedIntegration[]} */ (
|
|
63
|
+
project.loadedIntegrations
|
|
64
|
+
);
|
|
43
65
|
} catch {
|
|
44
66
|
return [];
|
|
45
67
|
}
|
|
@@ -47,7 +69,7 @@ async function loadIntegrationsSafely(cwd) {
|
|
|
47
69
|
|
|
48
70
|
/**
|
|
49
71
|
* Resolve a loaded integration by package name.
|
|
50
|
-
* @param {
|
|
72
|
+
* @param {import('../lib/integrations.mjs').LoadedIntegration[]} loadedIntegrations
|
|
51
73
|
* @param {string} packageName
|
|
52
74
|
*/
|
|
53
75
|
function findLoadedIntegration(loadedIntegrations, packageName) {
|
|
@@ -133,7 +155,7 @@ export async function component(name, options = {}) {
|
|
|
133
155
|
const readme = findComponentReadme(coreDir, comp);
|
|
134
156
|
if (readme && readme.endsWith('.doc.mjs')) {
|
|
135
157
|
try {
|
|
136
|
-
const docs = await loadDocs(readme, {zh, lang});
|
|
158
|
+
const docs = /** @type {LoadedComponentDoc} */ (await loadDocs(readme, /** @type {LoadDocsOpts} */ ({zh, lang})));
|
|
137
159
|
entries.push({name: comp, description: docs.usage?.description || docs.description || '', import: resolveImportPath(coreDir, comp)});
|
|
138
160
|
} catch {
|
|
139
161
|
entries.push({name: comp, description: '', import: resolveImportPath(coreDir, comp)});
|
|
@@ -151,7 +173,7 @@ export async function component(name, options = {}) {
|
|
|
151
173
|
const readme = findComponentReadme(coreDir, comp);
|
|
152
174
|
if (readme && readme.endsWith('.doc.mjs')) {
|
|
153
175
|
try {
|
|
154
|
-
entries.push(await loadDocs(readme, {zh, lang, dense}));
|
|
176
|
+
entries.push(await loadDocs(readme, /** @type {LoadDocsOpts} */ ({zh, lang, dense})));
|
|
155
177
|
} catch {
|
|
156
178
|
entries.push({name: `XDS${comp}`, description: ''});
|
|
157
179
|
}
|
|
@@ -181,7 +203,7 @@ export async function component(name, options = {}) {
|
|
|
181
203
|
const readme = findComponentReadme(coreDir, comp);
|
|
182
204
|
if (readme && readme.endsWith('.doc.mjs')) {
|
|
183
205
|
try {
|
|
184
|
-
const docs = await loadDocs(readme, {zh, lang});
|
|
206
|
+
const docs = /** @type {LoadedComponentDoc} */ (await loadDocs(readme, /** @type {LoadDocsOpts} */ ({zh, lang})));
|
|
185
207
|
result[cat].push({name: comp, description: docs.usage?.description || docs.description || '', import: resolveImportPath(coreDir, comp)});
|
|
186
208
|
} catch {
|
|
187
209
|
result[cat].push({name: comp, description: '', import: resolveImportPath(coreDir, comp)});
|
|
@@ -203,7 +225,7 @@ export async function component(name, options = {}) {
|
|
|
203
225
|
const readme = findComponentReadme(coreDir, comp);
|
|
204
226
|
if (readme && readme.endsWith('.doc.mjs')) {
|
|
205
227
|
try {
|
|
206
|
-
result[cat].push(await loadDocs(readme, {zh, lang, dense}));
|
|
228
|
+
result[cat].push(await loadDocs(readme, /** @type {LoadDocsOpts} */ ({zh, lang, dense})));
|
|
207
229
|
} catch {
|
|
208
230
|
result[cat].push({name: `XDS${comp}`, description: ''});
|
|
209
231
|
}
|
|
@@ -238,7 +260,7 @@ export async function component(name, options = {}) {
|
|
|
238
260
|
const groupLabel = rec.group ?? integration.name;
|
|
239
261
|
const key = `${groupLabel} (${integration.name})`;
|
|
240
262
|
if (!byGroup.has(key)) byGroup.set(key, []);
|
|
241
|
-
byGroup.get(key)
|
|
263
|
+
byGroup.get(key)?.push({name: rec.name, package: integration.name});
|
|
242
264
|
}
|
|
243
265
|
for (const [key, members] of byGroup) {
|
|
244
266
|
members.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -334,14 +356,14 @@ export async function component(name, options = {}) {
|
|
|
334
356
|
* `package`, the resolved `import` specifier, and `sourceAvailable` (whether
|
|
335
357
|
* a swizzleable source file exists for the owner). Existing doc fields
|
|
336
358
|
* (name, usage, props, …) are preserved.
|
|
337
|
-
* @param {
|
|
359
|
+
* @param {LoadedComponentDoc} docs
|
|
338
360
|
* @param {{package: string, sourcePath: string|null}} owner
|
|
339
361
|
* @param {string} componentName
|
|
340
362
|
*/
|
|
341
363
|
function withOwnership(docs, owner, componentName) {
|
|
342
364
|
const importSpec =
|
|
343
365
|
owner.package === CORE_PACKAGE
|
|
344
|
-
? resolveImportPath(coreDir, componentName)
|
|
366
|
+
? resolveImportPath(/** @type {string} */ (coreDir), componentName)
|
|
345
367
|
: `${owner.package}/${componentName}`;
|
|
346
368
|
return {
|
|
347
369
|
...docs,
|
|
@@ -368,7 +390,7 @@ export async function component(name, options = {}) {
|
|
|
368
390
|
}
|
|
369
391
|
return {type: 'component.detail.source', data: {component: dirName, source: fs.readFileSync(owner.sourcePath, 'utf-8')}};
|
|
370
392
|
}
|
|
371
|
-
const docs = await loadDocs(owner.docPath, {zh, dense, lang});
|
|
393
|
+
const docs = /** @type {LoadedComponentDoc} */ (await loadDocs(owner.docPath, /** @type {LoadDocsOpts} */ ({zh, dense, lang})));
|
|
372
394
|
if (props) {
|
|
373
395
|
const p = docs.props || (docs.components ? docs.components.flatMap(c => c.props || []) : []);
|
|
374
396
|
return {type: 'component.detail.props', data: p};
|
|
@@ -389,7 +411,7 @@ export async function component(name, options = {}) {
|
|
|
389
411
|
}
|
|
390
412
|
return {type: 'component.detail.source', data: {component: dirName, source: fs.readFileSync(owner.sourcePath, 'utf-8')}};
|
|
391
413
|
}
|
|
392
|
-
const docs = await loadDocs(owner.docPath, {zh, dense, lang});
|
|
414
|
+
const docs = /** @type {LoadedComponentDoc} */ (await loadDocs(owner.docPath, /** @type {LoadDocsOpts} */ ({zh, dense, lang})));
|
|
393
415
|
if (props) {
|
|
394
416
|
const p = docs.props || (docs.components ? docs.components.flatMap(c => c.props || []) : []);
|
|
395
417
|
return {type: 'component.detail.props', data: p};
|
|
@@ -420,7 +442,7 @@ export async function component(name, options = {}) {
|
|
|
420
442
|
|
|
421
443
|
const extDocPath = findExternalComponentDoc(ext.docsDir, dirName);
|
|
422
444
|
if (extDocPath && extDocPath.endsWith('.doc.mjs')) {
|
|
423
|
-
const docs = await loadDocs(extDocPath, {zh, dense, lang});
|
|
445
|
+
const docs = /** @type {LoadedComponentDoc} */ (await loadDocs(extDocPath, /** @type {LoadDocsOpts} */ ({zh, dense, lang})));
|
|
424
446
|
|
|
425
447
|
if (props) {
|
|
426
448
|
const p = docs.props || (docs.components ? docs.components.flatMap(c => c.props || []) : []);
|
|
@@ -461,7 +483,7 @@ export async function component(name, options = {}) {
|
|
|
461
483
|
if (showcase) {
|
|
462
484
|
throw new AstryxError(`No showcase found for "${name}"`, undefined, ERROR_CODES.ERR_NO_SHOWCASE);
|
|
463
485
|
}
|
|
464
|
-
const docs = await loadDocs(owner.docPath, {zh, dense, lang});
|
|
486
|
+
const docs = /** @type {LoadedComponentDoc} */ (await loadDocs(owner.docPath, /** @type {LoadDocsOpts} */ ({zh, dense, lang})));
|
|
465
487
|
if (props) {
|
|
466
488
|
const p = docs.props || (docs.components ? docs.components.flatMap(c => c.props || []) : []);
|
|
467
489
|
return {type: 'component.detail.props', data: p};
|
|
@@ -546,12 +568,12 @@ export async function component(name, options = {}) {
|
|
|
546
568
|
throw new AstryxError(`No .doc.mjs found for "${resolvedName}". The component needs a typed doc file.`, undefined, ERROR_CODES.ERR_NO_DOC);
|
|
547
569
|
}
|
|
548
570
|
|
|
549
|
-
const docs = await loadDocs(readmePath, {zh, dense, lang});
|
|
571
|
+
const docs = /** @type {LoadedComponentDoc} */ (await loadDocs(readmePath, /** @type {LoadDocsOpts} */ ({zh, dense, lang})));
|
|
550
572
|
|
|
551
573
|
// ── Blocks mode ──────────────────────────────────────────────
|
|
552
574
|
if (blocks) {
|
|
553
575
|
const allBlocks = await findRelatedBlocks(dirName);
|
|
554
|
-
const toEntry = (b) => ({
|
|
576
|
+
const toEntry = (/** @type {any} */ b) => ({
|
|
555
577
|
name: b.dirName,
|
|
556
578
|
displayName: b.name,
|
|
557
579
|
description: b.description,
|
|
@@ -561,7 +583,7 @@ export async function component(name, options = {}) {
|
|
|
561
583
|
|
|
562
584
|
// Examples: blocks in the component's own directory, or
|
|
563
585
|
// componentsUsed match for sub-components without a directory.
|
|
564
|
-
const ownDir = allBlocks.filter(b => path.basename(b.category) === dirName);
|
|
586
|
+
const ownDir = allBlocks.filter((/** @type {any} */ b) => path.basename(b.category) === dirName);
|
|
565
587
|
const examples = ownDir.length > 0
|
|
566
588
|
? ownDir
|
|
567
589
|
: allBlocks.filter(b => b.componentsUsed?.some(c => c === dirName));
|
|
@@ -595,6 +617,7 @@ export async function component(name, options = {}) {
|
|
|
595
617
|
: null;
|
|
596
618
|
|
|
597
619
|
if (matchingComponent) {
|
|
620
|
+
/** @type {LoadedComponentDoc & {parentDoc?: string, import?: string}} */
|
|
598
621
|
const scoped = {
|
|
599
622
|
name: dirName,
|
|
600
623
|
description: matchingComponent.description,
|
package/src/api/discover.mjs
CHANGED
|
@@ -14,18 +14,27 @@ import {levenshteinDistance} from '../lib/string-utils.mjs';
|
|
|
14
14
|
import {AstryxError} from './error.mjs';
|
|
15
15
|
import {ERROR_CODES} from '../lib/error-codes.mjs';
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {import('../lib/package-scanner.mjs').ScannedPackage} ScannedPackage
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {unknown} docs
|
|
23
|
+
* @returns {string | null}
|
|
24
|
+
*/
|
|
17
25
|
function validateDocs(docs) {
|
|
18
26
|
if (!docs || typeof docs !== 'object')
|
|
19
27
|
return 'docs export is missing or not an object';
|
|
20
|
-
|
|
28
|
+
const d = /** @type {Record<string, any>} */ (docs);
|
|
29
|
+
if (typeof d.name !== 'string' || !d.name)
|
|
21
30
|
return 'docs.name is missing or not a string';
|
|
22
|
-
if (!
|
|
31
|
+
if (!d.usage || typeof d.usage.description !== 'string')
|
|
23
32
|
return 'docs.usage.description is missing or not a string';
|
|
24
|
-
if (
|
|
33
|
+
if (d.props && !Array.isArray(d.props))
|
|
25
34
|
return 'docs.props must be an array';
|
|
26
|
-
if (
|
|
35
|
+
if (d.components && !Array.isArray(d.components))
|
|
27
36
|
return 'docs.components must be an array';
|
|
28
|
-
if (
|
|
37
|
+
if (d.usage?.bestPractices && !Array.isArray(d.usage.bestPractices))
|
|
29
38
|
return 'docs.usage.bestPractices must be an array';
|
|
30
39
|
return null;
|
|
31
40
|
}
|
|
@@ -36,11 +45,21 @@ function validateDocs(docs) {
|
|
|
36
45
|
* @param {boolean} [options.components]
|
|
37
46
|
* @param {string} [options.lang]
|
|
38
47
|
* @param {boolean} [options.zh]
|
|
39
|
-
* @returns {Promise<
|
|
48
|
+
* @returns {Promise<
|
|
49
|
+
* import('../types/discover').DiscoverListResponse |
|
|
50
|
+
* import('../types/discover').DiscoverDetailResponse |
|
|
51
|
+
* import('../types/discover').DiscoverDetailDocResponse |
|
|
52
|
+
* import('../types/discover').DiscoverSearchResponse
|
|
53
|
+
* >}
|
|
40
54
|
*/
|
|
41
55
|
export async function discover(query, options = {}) {
|
|
42
56
|
const {lang = null, zh = false} = options;
|
|
43
57
|
const project = await Project.load();
|
|
58
|
+
const loadedIntegrations =
|
|
59
|
+
/** @type {import('../lib/integrations.mjs').LoadedIntegration[]} */ (
|
|
60
|
+
project.loadedIntegrations
|
|
61
|
+
);
|
|
62
|
+
/** @param {ScannedPackage} pkg */
|
|
44
63
|
const toEntry = pkg => ({
|
|
45
64
|
name: pkg.name,
|
|
46
65
|
category: pkg.category,
|
|
@@ -52,7 +71,7 @@ export async function discover(query, options = {}) {
|
|
|
52
71
|
|
|
53
72
|
// External packages come from configured integrations that declare a
|
|
54
73
|
// components root. Each becomes a scannable package keyed by its docsDir.
|
|
55
|
-
const explicitPackages =
|
|
74
|
+
const explicitPackages = loadedIntegrations
|
|
56
75
|
.filter(integration => integration.components)
|
|
57
76
|
.map(integration => ({
|
|
58
77
|
name: integration.name,
|
|
@@ -64,7 +83,10 @@ export async function discover(query, options = {}) {
|
|
|
64
83
|
return {type: 'discover.list', data: [], meta: {configured: false}};
|
|
65
84
|
}
|
|
66
85
|
|
|
67
|
-
const packages = scanAllPackages(
|
|
86
|
+
const packages = scanAllPackages(
|
|
87
|
+
[],
|
|
88
|
+
/** @type {ScannedPackage[]} */ (/** @type {unknown} */ (explicitPackages)),
|
|
89
|
+
);
|
|
68
90
|
|
|
69
91
|
if (packages.length === 0) {
|
|
70
92
|
return {type: 'discover.list', data: [], meta: {configured: true}};
|
|
@@ -105,7 +127,7 @@ export async function discover(query, options = {}) {
|
|
|
105
127
|
return await loadAndValidate(exact, {lang, zh});
|
|
106
128
|
}
|
|
107
129
|
|
|
108
|
-
const substringMatches = [];
|
|
130
|
+
const substringMatches = /** @type {Array<{pkg: ScannedPackage, comp: string}>} */ ([]);
|
|
109
131
|
for (const pkg of packages) {
|
|
110
132
|
for (const comp of pkg.components) {
|
|
111
133
|
if (comp.toLowerCase().includes(lower)) {
|
|
@@ -134,7 +156,7 @@ export async function discover(query, options = {}) {
|
|
|
134
156
|
}
|
|
135
157
|
|
|
136
158
|
// Fuzzy fallback
|
|
137
|
-
const allComponents = [];
|
|
159
|
+
const allComponents = /** @type {Array<{pkg: ScannedPackage, comp: string}>} */ ([]);
|
|
138
160
|
for (const pkg of packages) {
|
|
139
161
|
for (const comp of pkg.components) {
|
|
140
162
|
allComponents.push({pkg, comp});
|
|
@@ -167,6 +189,13 @@ export async function discover(query, options = {}) {
|
|
|
167
189
|
);
|
|
168
190
|
}
|
|
169
191
|
|
|
192
|
+
/**
|
|
193
|
+
* @param {ScannedPackage[]} packages
|
|
194
|
+
* @param {string} compName
|
|
195
|
+
* @param {string} pkgName
|
|
196
|
+
* @param {{lang?: string|null, zh?: boolean}} opts
|
|
197
|
+
* @returns {Promise<import('../types/discover').DiscoverDetailDocResponse>}
|
|
198
|
+
*/
|
|
170
199
|
async function resolveComponentDocs(packages, compName, pkgName, {lang, zh}) {
|
|
171
200
|
const pkg = packages.find(p => p.name === pkgName);
|
|
172
201
|
if (!pkg)
|
|
@@ -202,13 +231,21 @@ async function resolveComponentDocs(packages, compName, pkgName, {lang, zh}) {
|
|
|
202
231
|
return await loadAndValidate(result, {lang, zh});
|
|
203
232
|
}
|
|
204
233
|
|
|
234
|
+
/**
|
|
235
|
+
* @param {{docPath: string, componentName: string, pkg: ScannedPackage}} result
|
|
236
|
+
* @param {{lang?: string|null, zh?: boolean}} opts
|
|
237
|
+
* @returns {Promise<import('../types/discover').DiscoverDetailDocResponse>}
|
|
238
|
+
*/
|
|
205
239
|
async function loadAndValidate(result, {lang, zh}) {
|
|
206
240
|
let docs;
|
|
207
241
|
try {
|
|
208
|
-
docs = await loadDocs(
|
|
242
|
+
docs = await loadDocs(
|
|
243
|
+
result.docPath,
|
|
244
|
+
/** @type {{zh?: boolean, dense?: boolean, lang?: string}} */ ({zh, lang}),
|
|
245
|
+
);
|
|
209
246
|
} catch (e) {
|
|
210
247
|
throw new AstryxError(
|
|
211
|
-
`Failed to load docs for ${result.componentName}: ${e.message}`,
|
|
248
|
+
`Failed to load docs for ${result.componentName}: ${/** @type {any} */ (e).message}`,
|
|
212
249
|
undefined,
|
|
213
250
|
ERROR_CODES.ERR_INVALID_DOC,
|
|
214
251
|
);
|