@astryxdesign/cli 0.7.2-canary.f16dc9f → 0.7.2-canary.f2b60a0
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 +15 -14
- package/api/build/help/help.mjs +1 -1
- package/api/component/_adapter.d.mts +41 -1
- package/api/component/_adapter.mjs +93 -7
- package/api/component/component.mjs +22 -5
- package/api/component/component.test.mjs +420 -167
- package/api/component/integration-export-name.test.mjs +644 -0
- package/api/component/list/list.mjs +42 -4
- package/api/docs/_adapter.d.mts +11 -0
- package/api/docs/_adapter.mjs +244 -1
- package/api/docs/integrationDocs.test.mjs +124 -0
- package/api/doctor/doctor.mjs +27 -3
- package/api/doctor/doctor.test.mjs +43 -0
- package/api/doctor/theme-checks.mjs +1 -1
- package/api/gap-report/gap-report.mjs +67 -4
- package/api/index.d.mts +2 -0
- package/api/index.mjs +2 -0
- package/api/init/run/run.mjs +2 -2
- package/api/integration/_adapter.d.mts +12 -0
- package/api/integration/_adapter.mjs +54 -0
- package/api/integration/add-contribution.component-names.test.mjs +66 -0
- package/api/integration/add-contribution.mjs +8 -4
- package/api/integration/add-contribution.test.mjs +4 -2
- package/api/integration/authoring-checks.mjs +11 -2
- package/api/integration/authoring-checks.test.mjs +39 -0
- package/api/integration/authoring-checks.type.d.mts +9 -0
- package/api/integration/authoring-checks.type.mjs +14 -1
- package/api/integration/check/check.d.mts +96 -0
- package/api/integration/check/check.mjs +357 -0
- package/api/integration/check/check.test.mjs +489 -0
- package/api/integration/integration-check.type.d.mts +118 -0
- package/api/integration/integration-check.type.mjs +84 -0
- package/api/integration/integrationCheck.doc.d.mts +10 -0
- package/api/integration/integrationCheck.doc.mjs +73 -0
- package/api/integration/integrationComponentConflicts.doc.mjs +36 -5
- package/api/integration/pack-check.mjs +61 -107
- package/api/integration/pack-check.test.mjs +359 -0
- package/api/integration/validate-integration.mjs +8 -0
- package/api/integration/validate-integration.test.mjs +11 -0
- package/api/integration/validate-integration.type.d.mts +8 -0
- package/api/integration/validate-integration.type.mjs +12 -1
- package/api/integration/validateIntegration.doc.mjs +17 -3
- package/api/search/search.d.mts +22 -6
- package/api/search/search.domain-priority.test.mjs +108 -18
- package/api/search/search.integration-check.test.mjs +24 -0
- package/api/search/search.mjs +141 -25
- package/api/search/search.test.mjs +36 -0
- package/api/swizzle/copy/copy.mjs +28 -14
- package/api/template/template.test.mjs +120 -3
- package/api/theme/build/build.family.test.mjs +64 -0
- package/api/theme/build/build.mjs +180 -24
- package/api/theme/themeBuild.doc.mjs +4 -4
- package/assets/codemods/transforms/next/__tests__/migrate-list-edge-compensation-to-edge.test.mjs +120 -0
- package/assets/codemods/transforms/next/index.mjs +11 -1
- package/assets/codemods/transforms/next/migrate-list-edge-compensation-to-edge.mjs +198 -0
- package/assets/docs/README.md +10 -0
- package/assets/docs/author-a-theme.doc.mjs +1 -1
- package/assets/docs/getting-started.doc.mjs +3 -3
- package/assets/docs/styling-overview.doc.mjs +1 -1
- package/assets/docs/theme.doc.dense.mjs +1 -1
- package/assets/docs/theme.doc.mjs +1 -1
- package/assets/docs/theme.doc.zh.mjs +1 -1
- package/assets/docs/tree/add-a-component.doc.mjs +11 -1
- package/assets/docs/tree/add-a-theme.doc.mjs +1 -1
- package/assets/docs/tree/add-a-topic.doc.mjs +10 -7
- package/assets/docs/tree/agent-guidance.doc.mjs +2 -2
- package/assets/docs/tree/block-template.doc.mjs +8 -20
- package/assets/docs/tree/build-the-template.doc.mjs +1 -1
- package/assets/docs/tree/check-the-props.doc.mjs +196 -0
- package/assets/docs/tree/check-your-docs.doc.mjs +51 -5
- package/assets/docs/tree/checks.doc.mjs +24 -16
- package/assets/docs/tree/codemods.doc.mjs +4 -4
- package/assets/docs/tree/component-family.doc.mjs +8 -6
- package/assets/docs/tree/component-imports.doc.mjs +10 -2
- package/assets/docs/tree/components.doc.mjs +1 -1
- package/assets/docs/tree/debug-and-gap-reports.doc.mjs +7 -18
- package/assets/docs/tree/define-the-theme.doc.mjs +6 -5
- package/assets/docs/tree/describe-the-component.doc.mjs +2 -2
- package/assets/docs/tree/docs.doc.mjs +3 -3
- 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 +6 -6
- package/assets/docs/tree/fonts-and-assets.doc.mjs +2 -2
- package/assets/docs/tree/generate-a-palette.doc.mjs +5 -3
- package/assets/docs/tree/grade-template-with-agent.doc.mjs +1 -1
- package/assets/docs/tree/integrations.doc.mjs +8 -0
- package/assets/docs/tree/links.doc.mjs +6 -5
- package/assets/docs/tree/migration-setup.doc.mjs +1 -0
- package/assets/docs/tree/migration-verification.doc.mjs +0 -1
- package/assets/docs/tree/migration.doc.mjs +0 -1
- package/assets/docs/tree/package-and-test.doc.mjs +1 -1
- package/assets/docs/tree/page-template.doc.mjs +4 -8
- package/assets/docs/tree/publishing.doc.mjs +1 -1
- package/assets/docs/tree/quick-start.doc.mjs +37 -21
- package/assets/docs/tree/replace-a-core-component.doc.mjs +4 -6
- package/assets/docs/tree/replace-a-core-template.doc.mjs +6 -8
- package/assets/docs/tree/sections-and-placement.doc.mjs +4 -6
- package/assets/docs/tree/see-it-in-an-app.doc.mjs +4 -2
- package/assets/docs/tree/short-and-findable.doc.mjs +6 -5
- package/assets/docs/tree/single-component.doc.mjs +15 -6
- package/assets/docs/tree/start-a-template.doc.mjs +3 -3
- package/assets/docs/tree/subcomponent.doc.mjs +6 -6
- package/assets/docs/tree/template-assets.doc.mjs +2 -2
- package/assets/docs/tree/template-doc-overview.doc.mjs +4 -15
- package/assets/docs/tree/template-fonts.doc.mjs +1 -1
- package/assets/docs/tree/template-icons.doc.mjs +1 -1
- package/assets/docs/tree/template-images-media.doc.mjs +1 -1
- package/assets/docs/tree/template-styles.doc.mjs +2 -2
- package/assets/docs/tree/templates.doc.mjs +1 -1
- package/assets/docs/tree/test-in-an-app.doc.mjs +3 -3
- package/assets/docs/tree/test-template-in-app.doc.mjs +1 -1
- package/assets/docs/tree/tokens-and-setup.doc.mjs +19 -15
- package/assets/docs/tree/troubleshooting.doc.mjs +13 -5
- package/assets/docs/tree/type-scale.doc.mjs +3 -3
- package/assets/docs/tree/upgrading.doc.mjs +3 -3
- package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +3 -1
- package/assets/docs/tree/verify-packed-template.doc.mjs +1 -1
- package/assets/docs/tree/versioning.doc.mjs +7 -6
- package/assets/docs/tree/write-the-template-file.doc.mjs +4 -3
- package/assets/docs/use-a-theme.doc.mjs +2 -2
- package/assets/docs/working-with-ai.doc.mjs +43 -4
- package/assets/templates/blocks/components/Drawer/DrawerForm.doc.mjs +24 -0
- package/assets/templates/blocks/components/Drawer/DrawerForm.tsx +62 -0
- package/assets/templates/blocks/components/Drawer/DrawerScrim.doc.mjs +22 -0
- package/assets/templates/blocks/components/Drawer/DrawerScrim.tsx +68 -0
- package/assets/templates/blocks/components/Drawer/DrawerShowcase.doc.mjs +22 -0
- package/assets/templates/blocks/components/Drawer/DrawerShowcase.tsx +41 -0
- package/assets/templates/blocks/components/Drawer/DrawerStart.doc.mjs +21 -0
- package/assets/templates/blocks/components/Drawer/DrawerStart.tsx +41 -0
- package/assets/templates/blocks/components/Drawer/DrawerWidths.doc.mjs +22 -0
- package/assets/templates/blocks/components/Drawer/DrawerWidths.tsx +54 -0
- package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderShowcase.doc.mjs +15 -0
- package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderShowcase.tsx +73 -0
- package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderSubtitleEndContent.doc.mjs +22 -0
- package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderSubtitleEndContent.tsx +49 -0
- package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderWithCloseButton.doc.mjs +21 -0
- package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderWithCloseButton.tsx +42 -0
- package/assets/templates/pages/detail-page/page.tsx +3 -3
- package/assets/templates/pages/table-inbox/page.tsx +10 -8
- package/assets/theme.template.ts +1 -1
- package/authoring/codemod/codemod.doc.mjs +7 -3
- package/authoring/config/config.doc.mjs +9 -6
- package/authoring/config/type.ts +4 -2
- package/authoring/discover/discover.doc.mjs +1 -1
- package/authoring/doctypes/base/graph-fields.doc.mjs +6 -4
- package/authoring/doctypes/base/type.ts +6 -0
- package/authoring/doctypes/command/command.doc.mjs +14 -12
- package/authoring/doctypes/component/component.doc.mjs +31 -28
- package/authoring/doctypes/component/type.ts +13 -8
- package/authoring/doctypes/enum/enum.doc.mjs +7 -4
- package/authoring/doctypes/function/function.doc.mjs +9 -8
- package/authoring/doctypes/hook/hook.doc.mjs +5 -4
- package/authoring/doctypes/namespace/namespace.doc.mjs +7 -6
- package/authoring/doctypes/reference/reference.doc.mjs +11 -10
- package/authoring/doctypes/schema/schema.doc.mjs +9 -9
- package/authoring/doctypes/template/integration-template.doc.d.mts +11 -0
- package/authoring/doctypes/template/integration-template.doc.mjs +49 -0
- package/authoring/doctypes/template/parse.d.mts +6 -2
- package/authoring/doctypes/template/parse.mjs +3 -1
- package/authoring/doctypes/template/template.doc.mjs +3 -3
- package/authoring/doctypes/template/type.ts +22 -0
- package/authoring/doctypes/theme/theme.doc.mjs +2 -2
- package/authoring/gap-report/gap-report.doc.mjs +2 -2
- package/authoring/identity/identity.doc.mjs +3 -3
- package/authoring/index.d.ts +1 -0
- package/authoring/integration/integration.doc.mjs +12 -4
- package/authoring/integration/parse.test.mjs +10 -0
- package/authoring/integration/schema.d.mts +2 -0
- package/authoring/integration/schema.mjs +1 -0
- package/authoring/integration/type.ts +4 -0
- package/clients/cli/commands/build-theme.batch-report.test.mjs +3 -1
- package/clients/cli/commands/build-theme.mjs +1 -1
- package/clients/cli/commands/build-theme.path-safety.test.mjs +224 -6
- package/clients/cli/commands/build-theme.registry.test.mjs +5 -2
- package/clients/cli/commands/cli-postinstall.test.mjs +49 -1
- package/clients/cli/commands/doctor-integration-components.doc.mjs +15 -4
- package/clients/cli/commands/doctor-integration-validate.doc.mjs +11 -2
- package/clients/cli/commands/doctor-integration.test.mjs +837 -0
- package/clients/cli/commands/doctor.mjs +190 -0
- package/clients/cli/commands/integration-check.doc.mjs +85 -0
- package/clients/cli/commands/integration-check.test.mjs +267 -0
- package/clients/cli/commands/integration.doc.mjs +6 -2
- package/clients/cli/commands/integration.mjs +107 -1
- package/clients/cli/commands/setup-nudge.test.mjs +79 -1
- package/clients/cli/commands/swizzle.mjs +5 -3
- package/clients/cli/commands/swizzle.routing.test.mjs +18 -1
- package/clients/cli/commands/text-json-parity.test.mjs +5 -0
- package/clients/cli/commands/theme-build.doc.mjs +2 -2
- package/clients/cli/index.mjs +30 -5
- package/clients/cli/lib/component-format.mjs +5 -1
- package/foundation/agent-docs/agent-doc-state.d.mts +14 -0
- package/foundation/agent-docs/agent-doc-state.mjs +38 -0
- package/foundation/agent-docs/agent-docs.d.mts +2 -1
- package/foundation/agent-docs/agent-docs.mjs +8 -6
- package/foundation/config/project.mjs +82 -0
- package/foundation/discovery/authoring-self-docs.mjs +2 -1
- package/foundation/discovery/component-discovery.d.mts +190 -10
- package/foundation/discovery/component-discovery.export-names.test.mjs +760 -0
- package/foundation/discovery/component-discovery.mjs +864 -56
- package/foundation/discovery/component-discovery.test.mjs +96 -0
- package/foundation/discovery/component-replacement.mjs +18 -0
- package/foundation/discovery/component-replacement.test.mjs +40 -0
- package/foundation/discovery/doc-types.d.mts +157 -0
- package/foundation/discovery/doc-types.mjs +1224 -0
- package/foundation/discovery/doc-types.test.mjs +652 -0
- package/foundation/discovery/docs-discovery.d.mts +81 -0
- package/foundation/discovery/docs-discovery.mjs +57 -0
- package/foundation/discovery/integration-doc-files.d.mts +22 -0
- package/foundation/discovery/integration-doc-files.mjs +231 -0
- package/foundation/discovery/integration-doc-files.test.mjs +246 -0
- package/foundation/discovery/module-exports.d.mts +27 -0
- package/foundation/discovery/module-exports.mjs +356 -31
- package/foundation/discovery/module-exports.test.mjs +172 -0
- package/foundation/discovery/package-tsconfig.d.mts +14 -0
- package/foundation/discovery/package-tsconfig.mjs +228 -0
- package/foundation/discovery/package-tsconfig.test.mjs +210 -0
- package/foundation/discovery/project-typescript.d.mts +23 -0
- package/foundation/discovery/project-typescript.mjs +61 -0
- package/foundation/discovery/prop-drift.d.mts +278 -0
- package/foundation/discovery/prop-drift.mjs +2263 -0
- package/foundation/discovery/prop-drift.test.mjs +2219 -0
- package/foundation/discovery/template-adapter.mjs +74 -5
- package/foundation/discovery/type-match.d.mts +53 -0
- package/foundation/discovery/type-match.mjs +660 -0
- package/foundation/discovery/type-match.test.mjs +169 -0
- package/foundation/discovery/type-text.d.mts +68 -0
- package/foundation/discovery/type-text.mjs +658 -0
- package/foundation/discovery/type-text.test.mjs +196 -0
- package/foundation/discovery/unresolved-references.d.mts +62 -0
- package/foundation/discovery/unresolved-references.mjs +790 -0
- package/foundation/discovery/unresolved-references.test.mjs +567 -0
- package/foundation/doc-compiler/doc-loads.test.mjs +11 -1
- package/foundation/doc-compiler/inputs.mjs +23 -0
- package/foundation/doc-compiler/inputs.test.mjs +0 -1
- package/foundation/doc-compiler/links.d.mts +9 -5
- package/foundation/doc-compiler/links.mjs +4 -2
- package/foundation/integrations/contribution-inventory.mjs +6 -1
- package/foundation/response/response-types.doc.mjs +7 -2
- package/foundation/xle/registry.mjs +4 -0
- package/foundation/xle/registry.test.mjs +28 -0
- package/package.json +10 -10
- package/scripts/postinstall.mjs +3 -3
|
@@ -5,11 +5,22 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import * as fs from 'node:fs';
|
|
8
|
+
import {createRequire} from 'node:module';
|
|
8
9
|
import * as path from 'node:path';
|
|
10
|
+
import {DOC_EXPORTS} from '../doc-compiler/read.mjs';
|
|
9
11
|
import {listDir, listDirEntries, pathExists} from '../fs/listing-cache.mjs';
|
|
10
12
|
import {existsCaseExact} from '../fs/paths.mjs';
|
|
11
13
|
import {CORE_PROVIDER_ID} from '../identity/providers.mjs';
|
|
12
14
|
import {loadComponentDoc} from './component-loader.mjs';
|
|
15
|
+
import {
|
|
16
|
+
HAS_COMPUTED,
|
|
17
|
+
moduleExportSource,
|
|
18
|
+
moduleExportsName,
|
|
19
|
+
staticExportObject,
|
|
20
|
+
} from './module-exports.mjs';
|
|
21
|
+
import {inspectPackageExport} from './package-exports.mjs';
|
|
22
|
+
|
|
23
|
+
const require = createRequire(import.meta.url);
|
|
13
24
|
|
|
14
25
|
const SKIP_DIRS = new Set(['hooks', 'utils', '__tests__', 'node_modules']);
|
|
15
26
|
|
|
@@ -79,12 +90,40 @@ function componentNameFromFile(fileName) {
|
|
|
79
90
|
const GROUP_RE = /(?:^|\n) {0,2}group:\s*['"]([^'"]+)['"]/;
|
|
80
91
|
const HIDDEN_COMPONENTS_RE = /(?:^|\n) {0,2}hiddenComponents:\s*\[([^\]]*)\]/;
|
|
81
92
|
const HIDDEN_RE = /(?:^|\n) {0,2}hidden:\s*true/;
|
|
93
|
+
// Every top-level `name: '...'` (or `"name": "..."`) in the file: a quick check
|
|
94
|
+
// that a doc is named after its file before anything parses it.
|
|
95
|
+
const NAME_FIELD_RE = /(?:^|\n) {0,2}['"]?name['"]?\s*:\s*['"]([^'"\n]+)['"]/g;
|
|
96
|
+
// A `components` field anywhere in the file, written out or as a shorthand: the
|
|
97
|
+
// quick check for a family doc, before anything parses it. A false match only
|
|
98
|
+
// costs the parse.
|
|
99
|
+
const MEMBERS_FIELD_RE = /\bcomponents\b/;
|
|
100
|
+
// A top-level `replaces: '...'`: the Core name a doc claims for its package.
|
|
101
|
+
const REPLACES_FIELD_RE =
|
|
102
|
+
/(?:^|\n) {0,2}['"]?replaces['"]?\s*:\s*['"]([^'"\n]+)['"]/;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* What a quick scan of a doc's text finds before anything parses it: the
|
|
106
|
+
* top-level `name` values it writes (none when a spread could set the name),
|
|
107
|
+
* whether it may list family members, and the Core name it replaces.
|
|
108
|
+
* @param {string} text
|
|
109
|
+
* @returns {{names: string[], hasMembers: boolean, replaces: string | null}}
|
|
110
|
+
*/
|
|
111
|
+
function scanDocText(text) {
|
|
112
|
+
return {
|
|
113
|
+
names: text.includes('...')
|
|
114
|
+
? []
|
|
115
|
+
: [...text.matchAll(NAME_FIELD_RE)].map(match => match[1]),
|
|
116
|
+
hasMembers: MEMBERS_FIELD_RE.test(text),
|
|
117
|
+
replaces: REPLACES_FIELD_RE.exec(text)?.[1] ?? null,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
82
120
|
|
|
83
121
|
/**
|
|
84
122
|
* Read the `group`, `hiddenComponents`, and `hidden` fields from a
|
|
85
|
-
* component's .doc.mjs file (synchronous)
|
|
123
|
+
* component's .doc.mjs file (synchronous), with what {@link scanDocText}
|
|
124
|
+
* finds in it.
|
|
86
125
|
* @param {string} docPath
|
|
87
|
-
* @returns {{group: string | null, hiddenComponents: Set<string>, hidden: boolean}}
|
|
126
|
+
* @returns {{group: string | null, hiddenComponents: Set<string>, hidden: boolean, names: string[], hasMembers: boolean, replaces: string | null}}
|
|
88
127
|
*/
|
|
89
128
|
function readDocMeta(docPath) {
|
|
90
129
|
try {
|
|
@@ -103,14 +142,214 @@ function readDocMeta(docPath) {
|
|
|
103
142
|
group: groupMatch ? groupMatch[1] : null,
|
|
104
143
|
hiddenComponents: hiddenSet,
|
|
105
144
|
hidden,
|
|
145
|
+
...scanDocText(content),
|
|
106
146
|
};
|
|
107
147
|
} catch {
|
|
108
|
-
return {
|
|
148
|
+
return {
|
|
149
|
+
group: null,
|
|
150
|
+
hiddenComponents: new Set(),
|
|
151
|
+
hidden: false,
|
|
152
|
+
names: [],
|
|
153
|
+
hasMembers: false,
|
|
154
|
+
replaces: null,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** @type {typeof import('@babel/parser') | undefined} */
|
|
160
|
+
let babelParser;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* A property of an object literal, by its plain key.
|
|
164
|
+
* @param {any} object - an ObjectExpression node
|
|
165
|
+
* @param {string} key
|
|
166
|
+
* @returns {any}
|
|
167
|
+
*/
|
|
168
|
+
function propertyValue(object, key) {
|
|
169
|
+
for (const property of object.properties) {
|
|
170
|
+
if (property.type !== 'ObjectProperty' || property.computed) continue;
|
|
171
|
+
const name =
|
|
172
|
+
property.key.type === 'Identifier'
|
|
173
|
+
? property.key.name
|
|
174
|
+
: property.key.type === 'StringLiteral'
|
|
175
|
+
? property.key.value
|
|
176
|
+
: null;
|
|
177
|
+
if (name === key) return property.value;
|
|
109
178
|
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The names a component doc lists as `components` members, read from its
|
|
184
|
+
* source without running it. Only a static `{name: '<Name>'}` entry in the doc
|
|
185
|
+
* object the loader reads (the `default` export, else `docs`) counts: a name in
|
|
186
|
+
* prose, an example, or `relatedComponents` is not a member.
|
|
187
|
+
* @param {string} source - the doc file's text
|
|
188
|
+
* @returns {string[]}
|
|
189
|
+
*/
|
|
190
|
+
function docMemberNames(source) {
|
|
191
|
+
/** @type {any} */
|
|
192
|
+
let ast;
|
|
193
|
+
try {
|
|
194
|
+
babelParser ??= require('@babel/parser');
|
|
195
|
+
ast = /** @type {typeof import('@babel/parser')} */ (babelParser).parse(
|
|
196
|
+
source,
|
|
197
|
+
{
|
|
198
|
+
sourceType: 'module',
|
|
199
|
+
},
|
|
200
|
+
);
|
|
201
|
+
} catch {
|
|
202
|
+
return [];
|
|
203
|
+
}
|
|
204
|
+
/** @type {Map<string, any>} */
|
|
205
|
+
const constants = new Map();
|
|
206
|
+
let defaultExport = null;
|
|
207
|
+
let docsExport = null;
|
|
208
|
+
for (const node of ast.program.body) {
|
|
209
|
+
const declaration =
|
|
210
|
+
node.type === 'ExportNamedDeclaration' ? node.declaration : node;
|
|
211
|
+
if (declaration?.type === 'VariableDeclaration') {
|
|
212
|
+
for (const declarator of declaration.declarations) {
|
|
213
|
+
if (declarator.id.type !== 'Identifier' || !declarator.init) continue;
|
|
214
|
+
constants.set(declarator.id.name, declarator.init);
|
|
215
|
+
if (
|
|
216
|
+
node.type === 'ExportNamedDeclaration' &&
|
|
217
|
+
declarator.id.name === 'docs'
|
|
218
|
+
) {
|
|
219
|
+
docsExport = declarator.init;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (node.type === 'ExportDefaultDeclaration')
|
|
224
|
+
defaultExport = node.declaration;
|
|
225
|
+
}
|
|
226
|
+
let doc = defaultExport ?? docsExport;
|
|
227
|
+
if (doc?.type === 'Identifier') doc = constants.get(doc.name);
|
|
228
|
+
if (doc?.type !== 'ObjectExpression') return [];
|
|
229
|
+
const members = propertyValue(doc, 'components');
|
|
230
|
+
if (members?.type !== 'ArrayExpression') return [];
|
|
231
|
+
return members.elements.flatMap((/** @type {any} */ element) => {
|
|
232
|
+
if (element?.type !== 'ObjectExpression') return [];
|
|
233
|
+
const name = propertyValue(element, 'name');
|
|
234
|
+
return name?.type === 'StringLiteral' ? [name.value] : [];
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Whether `name` has its own component folder in Core: `src/<Name>/<Name>.tsx`
|
|
240
|
+
* (or the `XDS`-prefixed form).
|
|
241
|
+
* @param {string} srcDir
|
|
242
|
+
* @param {string} name
|
|
243
|
+
* @returns {boolean}
|
|
244
|
+
*/
|
|
245
|
+
function hasOwnFolder(srcDir, name) {
|
|
246
|
+
return [`${name}.tsx`, `XDS${name}.tsx`].some(file =>
|
|
247
|
+
existsCaseExact(path.join(srcDir, name, file), srcDir),
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Member names read in this process, by doc path, reused while the file keeps
|
|
253
|
+
* its size and mtime.
|
|
254
|
+
* @type {Map<string, {size: number, mtimeMs: number, members: string[]}>}
|
|
255
|
+
*/
|
|
256
|
+
const memberReads = new Map();
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* The names a doc lists as `components` members (see docMemberNames).
|
|
260
|
+
* @param {string} docPath
|
|
261
|
+
* @returns {string[]}
|
|
262
|
+
*/
|
|
263
|
+
function readDocMembers(docPath) {
|
|
264
|
+
let stat;
|
|
265
|
+
try {
|
|
266
|
+
stat = fs.statSync(docPath);
|
|
267
|
+
} catch {
|
|
268
|
+
return [];
|
|
269
|
+
}
|
|
270
|
+
const cached = memberReads.get(docPath);
|
|
271
|
+
if (cached && cached.size === stat.size && cached.mtimeMs === stat.mtimeMs) {
|
|
272
|
+
return cached.members;
|
|
273
|
+
}
|
|
274
|
+
/** @type {string[]} */
|
|
275
|
+
let members;
|
|
276
|
+
try {
|
|
277
|
+
members = docMemberNames(fs.readFileSync(docPath, 'utf-8'));
|
|
278
|
+
} catch {
|
|
279
|
+
members = [];
|
|
280
|
+
}
|
|
281
|
+
memberReads.set(docPath, {size: stat.size, mtimeMs: stat.mtimeMs, members});
|
|
282
|
+
return members;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// A sibling component folder named by a relative import or re-export.
|
|
286
|
+
const SIBLING_IMPORT_RE =
|
|
287
|
+
/(?:\bfrom|\bimport\s*\()\s*['"]\.\.\/([A-Za-z0-9_-]+)(?:\/[^'"]*)?['"]/g;
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* The Core doc that documents `name` as one of its `components` members: the
|
|
291
|
+
* doc of a component it builds on. HStack and VStack import Stack, and Stack's
|
|
292
|
+
* doc lists them, so each has its own folder and import path but no doc of its
|
|
293
|
+
* own. Only the folders the component's own source imports from are read.
|
|
294
|
+
* @param {string} srcDir
|
|
295
|
+
* @param {string} name
|
|
296
|
+
* @returns {string | null}
|
|
297
|
+
*/
|
|
298
|
+
function findFamilyDoc(srcDir, name) {
|
|
299
|
+
const sourcePath = [`${name}.tsx`, `XDS${name}.tsx`]
|
|
300
|
+
.map(file => path.join(srcDir, name, file))
|
|
301
|
+
.find(file => existsCaseExact(file, srcDir));
|
|
302
|
+
if (!sourcePath) return null;
|
|
303
|
+
let source;
|
|
304
|
+
try {
|
|
305
|
+
source = fs.readFileSync(sourcePath, 'utf-8');
|
|
306
|
+
} catch {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
const families = [
|
|
310
|
+
...new Set([...source.matchAll(SIBLING_IMPORT_RE)].map(match => match[1])),
|
|
311
|
+
]
|
|
312
|
+
.filter(dir => dir !== name && !SKIP_DIRS.has(dir))
|
|
313
|
+
.sort();
|
|
314
|
+
for (const dir of families) {
|
|
315
|
+
const dirPath = path.join(srcDir, dir);
|
|
316
|
+
if (!existsCaseExact(dirPath, srcDir)) continue;
|
|
317
|
+
for (const file of [...listDir(dirPath)].sort()) {
|
|
318
|
+
if (!file.endsWith('.doc.mjs')) continue;
|
|
319
|
+
const docPath = path.join(dirPath, file);
|
|
320
|
+
const members = readDocMembers(docPath);
|
|
321
|
+
if (members.includes(name) || members.includes(`XDS${name}`)) {
|
|
322
|
+
return docPath;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* The entry a family doc gives `name` in its `components`, when the doc is not
|
|
331
|
+
* the component's own (Stack's doc for VStack); null otherwise.
|
|
332
|
+
* @param {any} docs - a loaded component doc
|
|
333
|
+
* @param {string} name - bare component name
|
|
334
|
+
* @returns {any}
|
|
335
|
+
*/
|
|
336
|
+
export function familyMember(docs, name) {
|
|
337
|
+
if (!docs || docs.name === name || docs.name === `XDS${name}`) return null;
|
|
338
|
+
const members = Array.isArray(docs.components) ? docs.components : [];
|
|
339
|
+
return (
|
|
340
|
+
members.find(
|
|
341
|
+
(/** @type {any} */ member) =>
|
|
342
|
+
member?.name === name || member?.name === `XDS${name}`,
|
|
343
|
+
) ?? null
|
|
344
|
+
);
|
|
110
345
|
}
|
|
111
346
|
|
|
112
347
|
/**
|
|
113
348
|
* Auto-discover components by scanning for Astryx*.tsx files in core/src/.
|
|
349
|
+
* A component is listed when a doc documents it: a doc in its folder, or, for a
|
|
350
|
+
* component folder with no doc of its own, the doc `findComponentReadme` finds
|
|
351
|
+
* elsewhere (Heading's doc in the Text folder; HStack and VStack as members of
|
|
352
|
+
* Stack's doc).
|
|
114
353
|
*
|
|
115
354
|
* Returns an ordered Record where:
|
|
116
355
|
* - Grouped components use the group name as key: `'Buttons': ['Button', 'IconButton']`
|
|
@@ -192,6 +431,27 @@ export function discoverComponents(coreDir) {
|
|
|
192
431
|
}
|
|
193
432
|
}
|
|
194
433
|
|
|
434
|
+
// A component folder with no doc in it is still listed when a doc elsewhere
|
|
435
|
+
// documents it, the doc `component <Name>` opens: its own same-stem doc in a
|
|
436
|
+
// family folder (Text/Heading.doc.mjs), or a member entry in the family's doc
|
|
437
|
+
// (HStack and VStack in Stack's doc).
|
|
438
|
+
for (const entry of topEntries) {
|
|
439
|
+
if (!entry.isDirectory() || SKIP_DIRS.has(entry.name)) continue;
|
|
440
|
+
const name = entry.name;
|
|
441
|
+
if (componentGroups.has(name) || name.endsWith('Context')) continue;
|
|
442
|
+
if (
|
|
443
|
+
listDir(path.join(srcDir, name)).some(file => file.endsWith('.doc.mjs'))
|
|
444
|
+
) {
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (!hasOwnFolder(srcDir, name)) continue;
|
|
448
|
+
const docPath = findComponentReadme(coreDir, name);
|
|
449
|
+
if (!docPath?.endsWith('.doc.mjs')) continue;
|
|
450
|
+
const meta = readDocMeta(docPath);
|
|
451
|
+
if (meta.hidden || meta.hiddenComponents.has(name)) continue;
|
|
452
|
+
componentGroups.set(name, meta.group);
|
|
453
|
+
}
|
|
454
|
+
|
|
195
455
|
// Build the result: group name → sorted members, or component name → [self]
|
|
196
456
|
/** @type {Map<string, string[]>} */
|
|
197
457
|
const groups = new Map();
|
|
@@ -284,6 +544,10 @@ export function findComponentReadme(coreDir, name) {
|
|
|
284
544
|
}
|
|
285
545
|
}
|
|
286
546
|
|
|
547
|
+
// A component with its own folder, documented as a member of its family's
|
|
548
|
+
// doc (HStack and VStack in Stack's doc).
|
|
549
|
+
if (hasOwnFolder(srcDir, name)) return findFamilyDoc(srcDir, name);
|
|
550
|
+
|
|
287
551
|
return null;
|
|
288
552
|
}
|
|
289
553
|
|
|
@@ -632,6 +896,345 @@ export function findExternalComponentDoc(docsDir, name) {
|
|
|
632
896
|
// commands — and the future integration-component swizzle — can disambiguate
|
|
633
897
|
// by package and route source/issues correctly.
|
|
634
898
|
|
|
899
|
+
// A doc's `name` can name another export of its file, and a family doc's full
|
|
900
|
+
// entries are components under their own names; every other doc reads as before.
|
|
901
|
+
const COMPONENT_NAME_RE = /^[A-Z][\w$]*$/;
|
|
902
|
+
|
|
903
|
+
/** @type {Map<string, {stamp: string, value: Record<string, any> | null}>} doc reads, reused while the doc file is unchanged */
|
|
904
|
+
const docReads = new Map();
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* A file's identity for a cache: ctime and inode change on a rewrite even when
|
|
908
|
+
* npm stamps the same size and mtime.
|
|
909
|
+
* @param {string} file
|
|
910
|
+
*/
|
|
911
|
+
function fileStamp(file) {
|
|
912
|
+
try {
|
|
913
|
+
const {size, mtimeMs, ctimeMs, ino} = fs.statSync(file);
|
|
914
|
+
return `${size}:${mtimeMs}:${ctimeMs}:${ino}`;
|
|
915
|
+
} catch {
|
|
916
|
+
return 'missing';
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/** UTF-16 code-unit order, the same in every locale. @param {string} a @param {string} b */
|
|
921
|
+
function byCodeUnit(a, b) {
|
|
922
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* A component doc's exported object as plain data, read without running it.
|
|
927
|
+
* @param {string} docPath
|
|
928
|
+
* @returns {Record<string, any> | null}
|
|
929
|
+
*/
|
|
930
|
+
function docObject(docPath) {
|
|
931
|
+
const stamp = fileStamp(docPath);
|
|
932
|
+
const cached = docReads.get(docPath);
|
|
933
|
+
if (cached?.stamp === stamp) return cached.value;
|
|
934
|
+
const value = staticExportObject(docPath, DOC_EXPORTS.components);
|
|
935
|
+
docReads.set(docPath, {stamp, value});
|
|
936
|
+
return value;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
/** @param {string} docPath */
|
|
940
|
+
export function docStem(docPath) {
|
|
941
|
+
return path.basename(docPath).replace(/\.doc\.(ts|mjs|js)$/, '');
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
/**
|
|
945
|
+
* The export a component doc names when its same-stem source is not named
|
|
946
|
+
* after an export, or null to keep the stem.
|
|
947
|
+
* @param {{docPath: string, sourcePath: string|null, stem: string, names: string[]}} doc
|
|
948
|
+
* `names`: top-level names a quick scan found (none: the doc is read)
|
|
949
|
+
* @returns {string | null}
|
|
950
|
+
*/
|
|
951
|
+
function otherExportName({docPath, sourcePath, stem, names}) {
|
|
952
|
+
if (sourcePath == null) return null;
|
|
953
|
+
if (names.length > 0 && names.every(name => name === stem)) return null;
|
|
954
|
+
const name = docObject(docPath)?.name;
|
|
955
|
+
return typeof name === 'string' &&
|
|
956
|
+
name !== stem &&
|
|
957
|
+
COMPONENT_NAME_RE.test(name) &&
|
|
958
|
+
moduleExportsName(sourcePath, name) &&
|
|
959
|
+
!moduleExportsName(sourcePath, stem)
|
|
960
|
+
? name
|
|
961
|
+
: null;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* The file Node loads for a package without `exports`: its `main`, else its
|
|
966
|
+
* `index` file.
|
|
967
|
+
* @param {string} packageDir
|
|
968
|
+
* @returns {string | null}
|
|
969
|
+
*/
|
|
970
|
+
function packageMain(packageDir) {
|
|
971
|
+
let main;
|
|
972
|
+
try {
|
|
973
|
+
main = JSON.parse(
|
|
974
|
+
fs.readFileSync(path.join(packageDir, 'package.json'), 'utf-8'),
|
|
975
|
+
).main;
|
|
976
|
+
} catch {
|
|
977
|
+
return null;
|
|
978
|
+
}
|
|
979
|
+
const entry =
|
|
980
|
+
typeof main === 'string' && main !== ''
|
|
981
|
+
? `./${path.posix.normalize(main).replace(/^\.\//, '')}`
|
|
982
|
+
: './index';
|
|
983
|
+
return inspectPackageExport({}, packageDir, entry).target;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* The local module a doc's import names: the doc's own `import`, else the
|
|
988
|
+
* subpath its directory publishes, read through the package's `exports` (its
|
|
989
|
+
* `main` when it has none). Undefined when the package directory is unknown;
|
|
990
|
+
* null when the import names another package or resolves to no file.
|
|
991
|
+
*
|
|
992
|
+
* @param {{name?: string, __packageDir?: string, __packageExports?: Record<string, unknown>|null}} integration
|
|
993
|
+
* @param {string} docPath
|
|
994
|
+
* @param {Record<string, any> | null} doc
|
|
995
|
+
* @returns {string | null | undefined}
|
|
996
|
+
*/
|
|
997
|
+
function importedModule(integration, docPath, doc) {
|
|
998
|
+
const packageName = integration?.name;
|
|
999
|
+
const packageDir = integration?.__packageDir;
|
|
1000
|
+
if (!packageName || !packageDir) return undefined;
|
|
1001
|
+
const exportsMap =
|
|
1002
|
+
integration.__packageExports === undefined
|
|
1003
|
+
? readPackageExports(packageDir)
|
|
1004
|
+
: integration.__packageExports;
|
|
1005
|
+
const specifier = resolveIntegrationImportPath(
|
|
1006
|
+
{exportsMap, packageDir, docPath, packageName},
|
|
1007
|
+
docStem(docPath),
|
|
1008
|
+
typeof doc?.import === 'string' ? doc.import : null,
|
|
1009
|
+
);
|
|
1010
|
+
if (specifier !== packageName && !specifier.startsWith(`${packageName}/`)) {
|
|
1011
|
+
return null;
|
|
1012
|
+
}
|
|
1013
|
+
if (specifier === packageName && exportsMap == null) {
|
|
1014
|
+
return packageMain(packageDir);
|
|
1015
|
+
}
|
|
1016
|
+
const subpath =
|
|
1017
|
+
specifier === packageName
|
|
1018
|
+
? '.'
|
|
1019
|
+
: `./${specifier.slice(packageName.length + 1)}`;
|
|
1020
|
+
return inspectPackageExport(
|
|
1021
|
+
{name: packageName, exports: exportsMap ?? undefined},
|
|
1022
|
+
packageDir,
|
|
1023
|
+
subpath,
|
|
1024
|
+
).target;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
/**
|
|
1028
|
+
* Whether `file` sits in `folder` or below it.
|
|
1029
|
+
* @param {string} folder
|
|
1030
|
+
* @param {string} file
|
|
1031
|
+
*/
|
|
1032
|
+
function isWithin(folder, file) {
|
|
1033
|
+
const relative = path.relative(folder, file);
|
|
1034
|
+
return (
|
|
1035
|
+
relative !== '' &&
|
|
1036
|
+
!path.isAbsolute(relative) &&
|
|
1037
|
+
relative.split(path.sep)[0] !== '..'
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* The `.tsx` file in `folder`, or below it, that declares `name`, taking files
|
|
1043
|
+
* in path order: the source of a member whose import never reaches a `.tsx`
|
|
1044
|
+
* file, as when the import names built output.
|
|
1045
|
+
*
|
|
1046
|
+
* @param {string} folder
|
|
1047
|
+
* @param {string} name
|
|
1048
|
+
* @returns {string | null}
|
|
1049
|
+
*/
|
|
1050
|
+
export function folderSourceFor(folder, name) {
|
|
1051
|
+
/** @type {string[]} */
|
|
1052
|
+
const files = [];
|
|
1053
|
+
/** @param {string} dirPath */
|
|
1054
|
+
const scan = dirPath => {
|
|
1055
|
+
for (const entry of listDirEntries(dirPath)) {
|
|
1056
|
+
if (entry.name === 'node_modules' || entry.name === '__tests__') continue;
|
|
1057
|
+
if (entry.name.startsWith('.')) continue;
|
|
1058
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
1059
|
+
if (entry.isDirectory()) scan(fullPath);
|
|
1060
|
+
else if (
|
|
1061
|
+
entry.name.endsWith('.tsx') &&
|
|
1062
|
+
!/\.(?:test|spec|stories)\.tsx$/.test(entry.name)
|
|
1063
|
+
) {
|
|
1064
|
+
files.push(fullPath);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
};
|
|
1068
|
+
scan(folder);
|
|
1069
|
+
for (const file of files.sort(byCodeUnit)) {
|
|
1070
|
+
const declared = moduleExportSource(file, name);
|
|
1071
|
+
if (
|
|
1072
|
+
declared != null &&
|
|
1073
|
+
declared.endsWith('.tsx') &&
|
|
1074
|
+
isWithin(folder, declared)
|
|
1075
|
+
) {
|
|
1076
|
+
return declared;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
return null;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
/**
|
|
1083
|
+
* The full entries a family doc lists in `components` that the module its
|
|
1084
|
+
* import names exports, each with its source. An entry is full when it says
|
|
1085
|
+
* more than its `name`; the name must be static PascalCase, not hidden, and
|
|
1086
|
+
* neither the doc's stem nor the export the doc is recorded under. A member's
|
|
1087
|
+
* source is the `.tsx` file in the family doc's folder, or below it, that
|
|
1088
|
+
* declares it: the one the family's own source or the imported module reaches,
|
|
1089
|
+
* else the one in that folder that declares the name, as when the import names
|
|
1090
|
+
* built output. A member that only a bundle or a file outside the folder
|
|
1091
|
+
* declares has no source.
|
|
1092
|
+
*
|
|
1093
|
+
* @param {{name?: string, __packageDir?: string, __packageExports?: Record<string, unknown>|null}} integration
|
|
1094
|
+
* @param {{docPath: string, sourcePath: string|null}} record the doc's own
|
|
1095
|
+
* @param {string | null | undefined} ownName the export the doc is recorded
|
|
1096
|
+
* under, when it names one
|
|
1097
|
+
* @param {string} [only] look for this member alone
|
|
1098
|
+
* @returns {Array<{name: string, sourcePath: string | null}>}
|
|
1099
|
+
*/
|
|
1100
|
+
function familyMembers(integration, record, ownName, only) {
|
|
1101
|
+
const familySource = record.sourcePath;
|
|
1102
|
+
const doc = docObject(record.docPath);
|
|
1103
|
+
const entries = Array.isArray(doc?.components) ? doc.components : [];
|
|
1104
|
+
const hidden = new Set(
|
|
1105
|
+
Array.isArray(doc?.hiddenComponents) ? doc.hiddenComponents : [],
|
|
1106
|
+
);
|
|
1107
|
+
const stem = docStem(record.docPath);
|
|
1108
|
+
/** @type {string[]} */
|
|
1109
|
+
const names = [];
|
|
1110
|
+
for (const entry of entries) {
|
|
1111
|
+
if (entry == null || typeof entry !== 'object') continue;
|
|
1112
|
+
const name = entry.name;
|
|
1113
|
+
const full = Object.keys(entry).length > 1 || entry[HAS_COMPUTED] === true;
|
|
1114
|
+
if (typeof name !== 'string' || !full) continue;
|
|
1115
|
+
if (!COMPONENT_NAME_RE.test(name) || hidden.has(name)) continue;
|
|
1116
|
+
if (name === stem || name === ownName) continue;
|
|
1117
|
+
if (only != null && name !== only) continue;
|
|
1118
|
+
names.push(name);
|
|
1119
|
+
}
|
|
1120
|
+
if (names.length === 0) return [];
|
|
1121
|
+
const imported = importedModule(integration, record.docPath, doc);
|
|
1122
|
+
const module = imported === undefined ? familySource : imported;
|
|
1123
|
+
if (module == null) return [];
|
|
1124
|
+
const folder = path.dirname(record.docPath);
|
|
1125
|
+
/** @param {string | null} file */
|
|
1126
|
+
const inFolder = file =>
|
|
1127
|
+
file != null && file.endsWith('.tsx') && isWithin(folder, file);
|
|
1128
|
+
return names.flatMap(name => {
|
|
1129
|
+
const exported = moduleExportSource(module, name);
|
|
1130
|
+
if (exported == null) return [];
|
|
1131
|
+
// A member's source is a `.tsx` file in the family's folder, as a doc's own
|
|
1132
|
+
// is: never a bundle, even one under a family at the package root.
|
|
1133
|
+
const reached = [
|
|
1134
|
+
familySource == null ? null : moduleExportSource(familySource, name),
|
|
1135
|
+
exported,
|
|
1136
|
+
].find(inFolder);
|
|
1137
|
+
return [{name, sourcePath: reached ?? folderSourceFor(folder, name)}];
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
/**
|
|
1142
|
+
* Each family doc under an integration's components root that names an
|
|
1143
|
+
* import, with the file the import resolves to: null when it resolves to none,
|
|
1144
|
+
* such as build output that does not exist yet.
|
|
1145
|
+
*
|
|
1146
|
+
* @param {{name: string, components?: string, issuesUrl?: string, __packageDir?: string, __packageExports?: Record<string, unknown>|null}} integration
|
|
1147
|
+
* @returns {Array<{docPath: string, module: string | null}>}
|
|
1148
|
+
*/
|
|
1149
|
+
export function familyImports(integration) {
|
|
1150
|
+
return discoverIntegrationComponents(integration).flatMap(record => {
|
|
1151
|
+
if (record.docPath == null || !isOwnRecord(record)) return [];
|
|
1152
|
+
const doc = docObject(record.docPath);
|
|
1153
|
+
if (!Array.isArray(doc?.components) || doc.components.length === 0) {
|
|
1154
|
+
return [];
|
|
1155
|
+
}
|
|
1156
|
+
const module = importedModule(integration, record.docPath, doc);
|
|
1157
|
+
return module === undefined ? [] : [{docPath: record.docPath, module}];
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
/**
|
|
1162
|
+
* Whether `record` is its doc's own component (under the doc's stem or the
|
|
1163
|
+
* export its `name` documents) rather than a member the doc lists.
|
|
1164
|
+
*
|
|
1165
|
+
* @param {{name: string, docPath: string|null}} record
|
|
1166
|
+
* @returns {boolean}
|
|
1167
|
+
*/
|
|
1168
|
+
function isOwnRecord(record) {
|
|
1169
|
+
if (record.docPath == null) return true;
|
|
1170
|
+
const stem = docStem(record.docPath);
|
|
1171
|
+
if (stem === record.name) return true;
|
|
1172
|
+
return (
|
|
1173
|
+
otherExportName({
|
|
1174
|
+
docPath: record.docPath,
|
|
1175
|
+
sourcePath: integrationSourceForDoc(record.docPath),
|
|
1176
|
+
stem,
|
|
1177
|
+
names: [],
|
|
1178
|
+
}) === record.name
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
/**
|
|
1183
|
+
* Whether a discovered integration record is a member its family doc lists,
|
|
1184
|
+
* decided from the files, not from whether the doc loads.
|
|
1185
|
+
*
|
|
1186
|
+
* @param {{name: string, docPath: string|null}} record
|
|
1187
|
+
* @returns {boolean}
|
|
1188
|
+
*/
|
|
1189
|
+
export function isIntegrationMember(record) {
|
|
1190
|
+
return !isOwnRecord(record);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
/**
|
|
1194
|
+
* The entry a family doc gives a member that discovery lists under its own
|
|
1195
|
+
* name, or null when the record is the doc's own component or the loaded doc
|
|
1196
|
+
* has no entry for it.
|
|
1197
|
+
*
|
|
1198
|
+
* @param {any} doc the doc loaded from `record.docPath`
|
|
1199
|
+
* @param {{name: string, docPath: string|null}} record
|
|
1200
|
+
* @returns {any}
|
|
1201
|
+
*/
|
|
1202
|
+
export function integrationMemberEntry(doc, record) {
|
|
1203
|
+
if (doc == null || typeof doc !== 'object' || isOwnRecord(record)) {
|
|
1204
|
+
return null;
|
|
1205
|
+
}
|
|
1206
|
+
if (!Array.isArray(doc.components)) return null;
|
|
1207
|
+
return (
|
|
1208
|
+
doc.components.find(
|
|
1209
|
+
(/** @type {any} */ entry) => entry?.name === record.name,
|
|
1210
|
+
) ?? null
|
|
1211
|
+
);
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
/**
|
|
1215
|
+
* Visit every component doc under an integration's components root, hidden
|
|
1216
|
+
* ones included, in discovery order, with its stem.
|
|
1217
|
+
* @param {string | undefined} componentsDir
|
|
1218
|
+
* @param {(docPath: string, stem: string) => void} visit
|
|
1219
|
+
*/
|
|
1220
|
+
export function forEachIntegrationDoc(componentsDir, visit) {
|
|
1221
|
+
if (!componentsDir || !pathExists(componentsDir)) return;
|
|
1222
|
+
/** @param {string} dirPath */
|
|
1223
|
+
function scanDir(dirPath) {
|
|
1224
|
+
for (const entry of listDirEntries(dirPath)) {
|
|
1225
|
+
if (entry.name === 'node_modules' || entry.name === '__tests__') continue;
|
|
1226
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
1227
|
+
if (entry.isDirectory()) {
|
|
1228
|
+
scanDir(fullPath);
|
|
1229
|
+
continue;
|
|
1230
|
+
}
|
|
1231
|
+
const suffix = INTEGRATION_DOC_SUFFIXES.find(s => entry.name.endsWith(s));
|
|
1232
|
+
if (suffix) visit(fullPath, entry.name.slice(0, -suffix.length));
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
scanDir(componentsDir);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
635
1238
|
/**
|
|
636
1239
|
* Given an integration component doc path, return the sibling component source
|
|
637
1240
|
* (`{Name}.tsx`) if one exists, else null.
|
|
@@ -639,7 +1242,7 @@ export function findExternalComponentDoc(docsDir, name) {
|
|
|
639
1242
|
* @param {string} docPath absolute path to a `{Name}.doc.{ts,mjs,js}` file
|
|
640
1243
|
* @returns {string|null}
|
|
641
1244
|
*/
|
|
642
|
-
function integrationSourceForDoc(docPath) {
|
|
1245
|
+
export function integrationSourceForDoc(docPath) {
|
|
643
1246
|
const dir = path.dirname(docPath);
|
|
644
1247
|
const base = path.basename(docPath).replace(/\.doc\.(ts|mjs|js)$/, '');
|
|
645
1248
|
const candidate = path.join(dir, `${base}.tsx`);
|
|
@@ -649,49 +1252,85 @@ function integrationSourceForDoc(docPath) {
|
|
|
649
1252
|
/**
|
|
650
1253
|
* Discover ownership records for the components contributed by a single loaded
|
|
651
1254
|
* integration. Scans the integration's resolved `components` dir for same-stem
|
|
652
|
-
* doc files and records each with owner package + issuesUrl + sourcePath.
|
|
1255
|
+
* doc files and records each with owner package + issuesUrl + sourcePath. A doc
|
|
1256
|
+
* that names a differently named export of its file is recorded under that
|
|
1257
|
+
* export, and each family member it lists, under the member's name, unless the
|
|
1258
|
+
* name is taken: every doc's file name (a hidden doc's too), a Core name a doc
|
|
1259
|
+
* in the package replaces, and a name an earlier doc records.
|
|
653
1260
|
*
|
|
654
|
-
* @param {{name: string, components?: string, issuesUrl?: string}} integration
|
|
1261
|
+
* @param {{name: string, components?: string, issuesUrl?: string, __packageDir?: string, __packageExports?: Record<string, unknown>|null}} integration
|
|
655
1262
|
* a single entry from `loadedIntegrations` (lib/integrations.mjs)
|
|
1263
|
+
* @param {{documentedNames?: boolean}} [options] `documentedNames: false`
|
|
1264
|
+
* records each doc under its file name alone, without reading the names
|
|
1265
|
+
* docs document: for a caller that needs only what each doc says
|
|
656
1266
|
* @returns {Array<{name: string, package: string, docPath: string, sourcePath: string|null, issuesUrl: string|undefined, group: string|null}>}
|
|
657
1267
|
*/
|
|
658
|
-
export function discoverIntegrationComponents(
|
|
1268
|
+
export function discoverIntegrationComponents(
|
|
1269
|
+
integration,
|
|
1270
|
+
{documentedNames = true} = {},
|
|
1271
|
+
) {
|
|
659
1272
|
const componentsDir = integration?.components;
|
|
660
1273
|
if (!componentsDir || !pathExists(componentsDir)) return [];
|
|
661
1274
|
|
|
662
1275
|
/** @type {Map<string, {name: string, package: string, docPath: string, sourcePath: string|null, issuesUrl: string|undefined, group: string|null}>} */
|
|
663
1276
|
const byName = new Map();
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
1277
|
+
/** @type {Map<string, {names: string[], hasMembers: boolean, replaces: string | null}>} stem → quick scan */
|
|
1278
|
+
const scans = new Map();
|
|
1279
|
+
/** @type {Set<string>} names no doc can record under another name */
|
|
1280
|
+
const taken = new Set();
|
|
1281
|
+
|
|
1282
|
+
forEachIntegrationDoc(componentsDir, (fullPath, name) => {
|
|
1283
|
+
taken.add(name);
|
|
1284
|
+
const {group, hidden, names, hasMembers, replaces} = readDocMeta(fullPath);
|
|
1285
|
+
if (hidden) return;
|
|
1286
|
+
// First doc wins per name (precedence matches INTEGRATION_DOC_SUFFIXES).
|
|
1287
|
+
if (byName.has(name)) return;
|
|
1288
|
+
byName.set(name, {
|
|
1289
|
+
name,
|
|
1290
|
+
package: integration.name,
|
|
1291
|
+
docPath: fullPath,
|
|
1292
|
+
sourcePath: integrationSourceForDoc(fullPath),
|
|
1293
|
+
issuesUrl: integration.issuesUrl,
|
|
1294
|
+
group: group ?? null,
|
|
1295
|
+
});
|
|
1296
|
+
scans.set(name, {names, hasMembers, replaces});
|
|
1297
|
+
});
|
|
1298
|
+
if (!documentedNames) return [...byName.values()];
|
|
1299
|
+
|
|
1300
|
+
// A doc that names a differently named export of its file is listed under
|
|
1301
|
+
// that export, and each member a family doc lists under the member's name,
|
|
1302
|
+
// unless the name is taken.
|
|
1303
|
+
for (const {replaces} of scans.values()) if (replaces) taken.add(replaces);
|
|
1304
|
+
const records = [...byName.values()];
|
|
1305
|
+
const byPath = [...records].sort((a, b) => byCodeUnit(a.docPath, b.docPath));
|
|
1306
|
+
/** @type {Map<string, string>} stem → export name */
|
|
1307
|
+
const exportNames = new Map();
|
|
1308
|
+
for (const record of byPath) {
|
|
1309
|
+
const exportName = otherExportName({
|
|
1310
|
+
...record,
|
|
1311
|
+
stem: record.name,
|
|
1312
|
+
names: scans.get(record.name)?.names ?? [],
|
|
1313
|
+
});
|
|
1314
|
+
if (exportName == null || taken.has(exportName)) continue;
|
|
1315
|
+
taken.add(exportName);
|
|
1316
|
+
exportNames.set(record.name, exportName);
|
|
1317
|
+
}
|
|
1318
|
+
/** @type {typeof records} */
|
|
1319
|
+
const members = [];
|
|
1320
|
+
for (const record of byPath) {
|
|
1321
|
+
if (!scans.get(record.name)?.hasMembers) continue;
|
|
1322
|
+
const ownName = exportNames.get(record.name);
|
|
1323
|
+
for (const member of familyMembers(integration, record, ownName)) {
|
|
1324
|
+
if (taken.has(member.name)) continue;
|
|
1325
|
+
taken.add(member.name);
|
|
1326
|
+
members.push({...record, ...member});
|
|
690
1327
|
}
|
|
691
1328
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
1329
|
+
const named = records.map(record => {
|
|
1330
|
+
const name = exportNames.get(record.name);
|
|
1331
|
+
return name ? {...record, name} : record;
|
|
1332
|
+
});
|
|
1333
|
+
return [...named, ...members];
|
|
695
1334
|
}
|
|
696
1335
|
|
|
697
1336
|
/**
|
|
@@ -699,24 +1338,33 @@ export function discoverIntegrationComponents(integration) {
|
|
|
699
1338
|
* Invalid metadata or a missing same-stem source removes only that component;
|
|
700
1339
|
* callers can report the returned errors while retaining valid siblings. A
|
|
701
1340
|
* component whose doc sets `replaces` carries the value as written; the
|
|
702
|
-
* component replacement resolver decides what it means.
|
|
1341
|
+
* component replacement resolver decides what it means. A family member is
|
|
1342
|
+
* read through its family's doc: it never carries the family's `replaces`, and
|
|
1343
|
+
* a family doc that fails to load is reported once. A doc that fails to load
|
|
1344
|
+
* is reported under its file name: what it says cannot be trusted.
|
|
703
1345
|
*
|
|
704
1346
|
* @param {{name: string, components?: string, issuesUrl?: string}} integration
|
|
1347
|
+
* @param {{documentedNames?: boolean}} [options] as for
|
|
1348
|
+
* {@link discoverIntegrationComponents}
|
|
705
1349
|
* @returns {Promise<{
|
|
706
|
-
* components: Array<{name: string, package: string, docPath: string, sourcePath: string, issuesUrl: string|undefined, group: string|null, replaces?: unknown}>,
|
|
1350
|
+
* components: Array<{name: string, package: string, docPath: string, sourcePath: string|null, issuesUrl: string|undefined, group: string|null, replaces?: unknown}>,
|
|
707
1351
|
* discovered: Array<{name: string, package: string, docPath: string, sourcePath: string|null, issuesUrl: string|undefined, group: string|null}>,
|
|
708
1352
|
* errors: Array<{name: string, message: string}>,
|
|
709
1353
|
* }>}
|
|
710
1354
|
*/
|
|
711
|
-
export async function discoverValidIntegrationComponents(integration) {
|
|
712
|
-
const discovered = discoverIntegrationComponents(integration);
|
|
713
|
-
/** @type {Array<{name: string, package: string, docPath: string, sourcePath: string, issuesUrl: string|undefined, group: string|null, replaces?: unknown}>} */
|
|
1355
|
+
export async function discoverValidIntegrationComponents(integration, options) {
|
|
1356
|
+
const discovered = discoverIntegrationComponents(integration, options);
|
|
1357
|
+
/** @type {Array<{name: string, package: string, docPath: string, sourcePath: string|null, issuesUrl: string|undefined, group: string|null, replaces?: unknown}>} */
|
|
714
1358
|
const components = [];
|
|
715
1359
|
/** @type {Array<{name: string, message: string}>} */
|
|
716
1360
|
const errors = [];
|
|
1361
|
+
/** @type {Set<string>} docs that failed to load */
|
|
1362
|
+
const failedDocs = new Set();
|
|
717
1363
|
|
|
718
1364
|
for (const record of discovered) {
|
|
719
|
-
|
|
1365
|
+
// A doc's own component needs its same-stem source. A member has none when
|
|
1366
|
+
// only a bundle or a file outside its family's folder declares it.
|
|
1367
|
+
if (record.sourcePath == null && isOwnRecord(record)) {
|
|
720
1368
|
errors.push({
|
|
721
1369
|
name: record.name,
|
|
722
1370
|
message: `Component "${record.name}" is missing its same-stem source file ${record.name}.tsx.`,
|
|
@@ -726,18 +1374,24 @@ export async function discoverValidIntegrationComponents(integration) {
|
|
|
726
1374
|
try {
|
|
727
1375
|
const doc = await loadComponentDoc(record.docPath);
|
|
728
1376
|
const valid =
|
|
729
|
-
/** @type {{name: string, package: string, docPath: string, sourcePath: string, issuesUrl: string|undefined, group: string|null}} */ (
|
|
1377
|
+
/** @type {{name: string, package: string, docPath: string, sourcePath: string|null, issuesUrl: string|undefined, group: string|null}} */ (
|
|
730
1378
|
record
|
|
731
1379
|
);
|
|
732
1380
|
components.push(
|
|
733
|
-
|
|
1381
|
+
isOwnRecord(record) &&
|
|
1382
|
+
doc != null &&
|
|
1383
|
+
typeof doc === 'object' &&
|
|
1384
|
+
doc.replaces !== undefined
|
|
734
1385
|
? {...valid, replaces: doc.replaces}
|
|
735
1386
|
: valid,
|
|
736
1387
|
);
|
|
737
1388
|
} catch (err) {
|
|
1389
|
+
if (failedDocs.has(record.docPath)) continue;
|
|
1390
|
+
failedDocs.add(record.docPath);
|
|
1391
|
+
const name = docStem(record.docPath);
|
|
738
1392
|
errors.push({
|
|
739
|
-
name
|
|
740
|
-
message: `Component "${
|
|
1393
|
+
name,
|
|
1394
|
+
message: `Component "${name}" has invalid metadata: ${
|
|
741
1395
|
err instanceof Error ? err.message : String(err)
|
|
742
1396
|
}`,
|
|
743
1397
|
});
|
|
@@ -747,19 +1401,158 @@ export async function discoverValidIntegrationComponents(integration) {
|
|
|
747
1401
|
return {components, discovered, errors};
|
|
748
1402
|
}
|
|
749
1403
|
|
|
1404
|
+
/**
|
|
1405
|
+
* How an owner holds a component name, which the owner rule
|
|
1406
|
+
* ({@link unscopedComponentOwners}) reads: `core` when Core documents it;
|
|
1407
|
+
* `file` when an integration doc named after its file is recorded under it;
|
|
1408
|
+
* `doc` when only a doc records it: an export name, a member, or the file name
|
|
1409
|
+
* of a doc recorded under another export.
|
|
1410
|
+
* @typedef {'core' | 'file' | 'doc'} NameHold
|
|
1411
|
+
*/
|
|
1412
|
+
|
|
1413
|
+
/**
|
|
1414
|
+
* How a discovered integration record holds its name.
|
|
1415
|
+
* @param {{name: string, docPath: string|null}} record
|
|
1416
|
+
* @returns {'file' | 'doc'}
|
|
1417
|
+
*/
|
|
1418
|
+
export function integrationRecordHold(record) {
|
|
1419
|
+
return record.docPath == null || docStem(record.docPath) === record.name
|
|
1420
|
+
? 'file'
|
|
1421
|
+
: 'doc';
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
/**
|
|
1425
|
+
* The doc that records `name` as its export or a member though its file is
|
|
1426
|
+
* named otherwise, with the source, reading only docs that write the name.
|
|
1427
|
+
*
|
|
1428
|
+
* @param {{name?: string, __packageDir?: string, __packageExports?: Record<string, unknown>|null}} integration
|
|
1429
|
+
* @param {string} componentsDir
|
|
1430
|
+
* @param {string} name
|
|
1431
|
+
* @returns {{docPath: string, sourcePath: string|null} | null}
|
|
1432
|
+
*/
|
|
1433
|
+
function findDocumentedComponent(integration, componentsDir, name) {
|
|
1434
|
+
if (!COMPONENT_NAME_RE.test(name)) return null;
|
|
1435
|
+
const literals = [`'${name}'`, `"${name}"`, `\`${name}\``];
|
|
1436
|
+
/** @type {Set<string>} */
|
|
1437
|
+
const stems = new Set();
|
|
1438
|
+
/** @type {Array<{docPath: string, stem: string, names: string[], hasMembers: boolean, replaces: string | null, sourcePath: string|null}>} */
|
|
1439
|
+
const candidates = [];
|
|
1440
|
+
forEachIntegrationDoc(componentsDir, (docPath, stem) => {
|
|
1441
|
+
let text;
|
|
1442
|
+
try {
|
|
1443
|
+
text = fs.readFileSync(docPath, 'utf-8');
|
|
1444
|
+
} catch {
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
// The docs discovery records: the first visible doc for each stem.
|
|
1448
|
+
if (HIDDEN_RE.test(text) || stems.has(stem)) return;
|
|
1449
|
+
stems.add(stem);
|
|
1450
|
+
if (!literals.some(literal => text.includes(literal))) return;
|
|
1451
|
+
candidates.push({
|
|
1452
|
+
docPath,
|
|
1453
|
+
stem,
|
|
1454
|
+
...scanDocText(text),
|
|
1455
|
+
sourcePath: integrationSourceForDoc(docPath),
|
|
1456
|
+
});
|
|
1457
|
+
});
|
|
1458
|
+
// A Core name a doc in the package replaces is the replacement's to answer.
|
|
1459
|
+
if (candidates.some(candidate => candidate.replaces === name)) return null;
|
|
1460
|
+
candidates.sort((a, b) => byCodeUnit(a.docPath, b.docPath));
|
|
1461
|
+
for (const candidate of candidates) {
|
|
1462
|
+
if (otherExportName(candidate) === name) {
|
|
1463
|
+
return {docPath: candidate.docPath, sourcePath: candidate.sourcePath};
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
for (const candidate of candidates) {
|
|
1467
|
+
if (!candidate.hasMembers) continue;
|
|
1468
|
+
const ownName = otherExportName(candidate);
|
|
1469
|
+
const [member] = familyMembers(integration, candidate, ownName, name);
|
|
1470
|
+
if (member) {
|
|
1471
|
+
return {docPath: candidate.docPath, sourcePath: member.sourcePath};
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
return null;
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
/**
|
|
1478
|
+
* The integration component a name finds, as discovery records it, and its
|
|
1479
|
+
* hold: the same-stem doc first, then a doc that records the name as an export
|
|
1480
|
+
* or a member.
|
|
1481
|
+
*
|
|
1482
|
+
* @param {{name?: string, components?: string, __packageDir?: string, __packageExports?: Record<string, unknown>|null}} integration
|
|
1483
|
+
* @param {string} name
|
|
1484
|
+
* @returns {{docPath: string, sourcePath: string|null, hold: 'file' | 'doc'} | null}
|
|
1485
|
+
*/
|
|
1486
|
+
export function findIntegrationComponent(integration, name) {
|
|
1487
|
+
const componentsDir = integration?.components;
|
|
1488
|
+
if (!componentsDir || !pathExists(componentsDir)) return null;
|
|
1489
|
+
const sameStem = findSameStemDoc(componentsDir, name);
|
|
1490
|
+
if (sameStem) {
|
|
1491
|
+
const exportName = otherExportName({
|
|
1492
|
+
docPath: sameStem,
|
|
1493
|
+
sourcePath: integrationSourceForDoc(sameStem),
|
|
1494
|
+
stem: name,
|
|
1495
|
+
names: readDocMeta(sameStem).names,
|
|
1496
|
+
});
|
|
1497
|
+
const renamed =
|
|
1498
|
+
exportName != null &&
|
|
1499
|
+
findSameStemDoc(componentsDir, exportName) == null &&
|
|
1500
|
+
findDocumentedComponent(integration, componentsDir, exportName)
|
|
1501
|
+
?.docPath === sameStem;
|
|
1502
|
+
return {
|
|
1503
|
+
docPath: sameStem,
|
|
1504
|
+
sourcePath: integrationSourceForDoc(sameStem),
|
|
1505
|
+
hold: renamed ? 'doc' : 'file',
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
const found = findDocumentedComponent(integration, componentsDir, name);
|
|
1509
|
+
return found && {...found, hold: 'doc'};
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
/**
|
|
1513
|
+
* The name discovery records a component doc under: the export its `name`
|
|
1514
|
+
* documents when the doc is recorded under it, else the doc's file name.
|
|
1515
|
+
*
|
|
1516
|
+
* @param {{name?: string, components?: string, __packageDir?: string, __packageExports?: Record<string, unknown>|null}} integration
|
|
1517
|
+
* @param {string} docPath
|
|
1518
|
+
* @returns {string}
|
|
1519
|
+
*/
|
|
1520
|
+
export function recordedDocName(integration, docPath) {
|
|
1521
|
+
const stem = docStem(docPath);
|
|
1522
|
+
const found = findIntegrationComponent(integration, stem);
|
|
1523
|
+
if (found?.docPath !== docPath || found.hold !== 'doc') return stem;
|
|
1524
|
+
return (
|
|
1525
|
+
otherExportName({
|
|
1526
|
+
docPath,
|
|
1527
|
+
sourcePath: integrationSourceForDoc(docPath),
|
|
1528
|
+
stem,
|
|
1529
|
+
names: readDocMeta(docPath).names,
|
|
1530
|
+
}) ?? stem
|
|
1531
|
+
);
|
|
1532
|
+
}
|
|
1533
|
+
|
|
750
1534
|
/**
|
|
751
1535
|
* Find an integration component's doc file by name within a loaded
|
|
752
1536
|
* integration's resolved `components` dir. Honors the same-stem convention
|
|
753
|
-
* (`{Name}.doc.{ts,mjs,js}`), preferring `.ts` → `.mjs` → `.js
|
|
1537
|
+
* (`{Name}.doc.{ts,mjs,js}`), preferring `.ts` → `.mjs` → `.js`, and finds a
|
|
1538
|
+
* doc that records the component under another name: a differently named
|
|
1539
|
+
* export of its file, or a member its family doc lists.
|
|
754
1540
|
*
|
|
755
|
-
* @param {{components?: string}} integration
|
|
1541
|
+
* @param {{name?: string, components?: string, __packageDir?: string, __packageExports?: Record<string, unknown>|null}} integration
|
|
756
1542
|
* @param {string} name bare component name (no `XDS`/`Astryx` prefix)
|
|
757
1543
|
* @returns {string|null}
|
|
758
1544
|
*/
|
|
759
1545
|
export function findIntegrationComponentDoc(integration, name) {
|
|
760
|
-
|
|
761
|
-
|
|
1546
|
+
return findIntegrationComponent(integration, name)?.docPath ?? null;
|
|
1547
|
+
}
|
|
762
1548
|
|
|
1549
|
+
/**
|
|
1550
|
+
* The first `{Name}.doc.{ts,mjs,js}` under `componentsDir`, shallowest first.
|
|
1551
|
+
* @param {string} componentsDir
|
|
1552
|
+
* @param {string} name
|
|
1553
|
+
* @returns {string|null}
|
|
1554
|
+
*/
|
|
1555
|
+
function findSameStemDoc(componentsDir, name) {
|
|
763
1556
|
/**
|
|
764
1557
|
* @param {string} dirPath
|
|
765
1558
|
* @returns {string | null}
|
|
@@ -785,18 +1578,33 @@ export function findIntegrationComponentDoc(integration, name) {
|
|
|
785
1578
|
}
|
|
786
1579
|
|
|
787
1580
|
/**
|
|
788
|
-
* Find an integration component's swizzleable source file
|
|
789
|
-
*
|
|
1581
|
+
* Find an integration component's swizzleable source file by name within a
|
|
1582
|
+
* loaded integration's resolved `components` dir: the same-stem `{Name}.tsx`
|
|
1583
|
+
* of its doc, or, for a family member, the file that declares it. Returns null
|
|
790
1584
|
* when the integration ships docs without source.
|
|
791
1585
|
*
|
|
792
|
-
* @param {{components?: string}} integration
|
|
1586
|
+
* @param {{name?: string, components?: string, __packageDir?: string, __packageExports?: Record<string, unknown>|null}} integration
|
|
793
1587
|
* @param {string} name bare component name
|
|
794
1588
|
* @returns {string|null}
|
|
795
1589
|
*/
|
|
796
1590
|
export function findIntegrationComponentSource(integration, name) {
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
1591
|
+
return findIntegrationComponent(integration, name)?.sourcePath ?? null;
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
/**
|
|
1595
|
+
* The owner rule: an owner that holds a name only because a doc records it
|
|
1596
|
+
* (`hold: 'doc'`) yields to Core and to every file-named owner; `--package`
|
|
1597
|
+
* still reaches it. Surfaces resolve a replacement first, then this rule, then
|
|
1598
|
+
* which docs load.
|
|
1599
|
+
*
|
|
1600
|
+
* @template {{hold?: string}} T
|
|
1601
|
+
* @param {T[]} owners each with its {@link NameHold}; an owner without one
|
|
1602
|
+
* counts as Core's or file-named
|
|
1603
|
+
* @returns {T[]}
|
|
1604
|
+
*/
|
|
1605
|
+
export function unscopedComponentOwners(owners) {
|
|
1606
|
+
const primary = owners.filter(owner => owner.hold !== 'doc');
|
|
1607
|
+
return primary.length > 0 ? primary : owners;
|
|
800
1608
|
}
|
|
801
1609
|
|
|
802
1610
|
/**
|