@astryxdesign/cli 0.7.2-canary.ebcc404 → 0.7.2-canary.f8f7cf1
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 +9 -8
- package/api/build/help/help.mjs +1 -1
- package/api/component/_adapter.d.mts +14 -0
- package/api/component/_adapter.mjs +48 -0
- package/api/component/component.mjs +10 -2
- package/api/component/integration-export-name.test.mjs +233 -8
- 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/theme-checks.mjs +1 -1
- package/api/gap-report/gap-report.mjs +2 -1
- 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 +48 -3
- package/api/integration/pack-check.test.mjs +247 -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.integration-check.test.mjs +24 -0
- package/api/search/search.mjs +20 -8
- package/api/theme/build/build.family.test.mjs +54 -6
- package/api/theme/build/build.mjs +114 -17
- package/api/theme/themeBuild.doc.mjs +4 -4
- 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/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 +10 -0
- package/assets/docs/tree/block-template.doc.mjs +2 -2
- package/assets/docs/tree/check-the-props.doc.mjs +196 -0
- package/assets/docs/tree/check-your-docs.doc.mjs +44 -3
- package/assets/docs/tree/checks.doc.mjs +21 -13
- package/assets/docs/tree/component-family.doc.mjs +5 -1
- package/assets/docs/tree/component-imports.doc.mjs +1 -1
- package/assets/docs/tree/describe-the-component.doc.mjs +1 -1
- package/assets/docs/tree/links.doc.mjs +2 -2
- package/assets/docs/tree/page-template.doc.mjs +1 -1
- package/assets/docs/tree/quick-start.doc.mjs +1 -1
- package/assets/docs/tree/replace-a-core-template.doc.mjs +1 -1
- package/assets/docs/tree/single-component.doc.mjs +3 -2
- package/assets/docs/tree/troubleshooting.doc.mjs +9 -1
- package/assets/docs/tree/type-scale.doc.mjs +3 -3
- package/assets/docs/tree/versioning.doc.mjs +1 -0
- package/assets/docs/tree/write-the-template-file.doc.mjs +2 -1
- package/assets/docs/use-a-theme.doc.mjs +2 -2
- package/assets/theme.template.ts +1 -1
- package/authoring/doctypes/component/component.doc.mjs +1 -1
- 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 +1 -1
- package/authoring/doctypes/template/type.ts +22 -0
- package/authoring/index.d.ts +1 -0
- package/authoring/integration/integration.doc.mjs +8 -1
- 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 +155 -21
- 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/text-json-parity.test.mjs +5 -0
- package/clients/cli/commands/theme-build.doc.mjs +2 -2
- package/clients/cli/index.mjs +1 -0
- package/foundation/config/project.mjs +82 -0
- package/foundation/discovery/authoring-self-docs.mjs +1 -0
- package/foundation/discovery/component-discovery.d.mts +108 -17
- package/foundation/discovery/component-discovery.export-names.test.mjs +469 -2
- package/foundation/discovery/component-discovery.mjs +380 -47
- package/foundation/discovery/component-replacement.test.mjs +20 -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 +6 -0
- package/foundation/discovery/module-exports.mjs +15 -2
- 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/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 +6 -0
- 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/package.json +9 -9
|
@@ -12,7 +12,13 @@ import {listDir, listDirEntries, pathExists} from '../fs/listing-cache.mjs';
|
|
|
12
12
|
import {existsCaseExact} from '../fs/paths.mjs';
|
|
13
13
|
import {CORE_PROVIDER_ID} from '../identity/providers.mjs';
|
|
14
14
|
import {loadComponentDoc} from './component-loader.mjs';
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
HAS_COMPUTED,
|
|
17
|
+
moduleExportSource,
|
|
18
|
+
moduleExportsName,
|
|
19
|
+
staticExportObject,
|
|
20
|
+
} from './module-exports.mjs';
|
|
21
|
+
import {inspectPackageExport} from './package-exports.mjs';
|
|
16
22
|
|
|
17
23
|
const require = createRequire(import.meta.url);
|
|
18
24
|
|
|
@@ -87,22 +93,27 @@ const HIDDEN_RE = /(?:^|\n) {0,2}hidden:\s*true/;
|
|
|
87
93
|
// Every top-level `name: '...'` (or `"name": "..."`) in the file: a quick check
|
|
88
94
|
// that a doc is named after its file before anything parses it.
|
|
89
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/;
|
|
90
100
|
// A top-level `replaces: '...'`: the Core name a doc claims for its package.
|
|
91
101
|
const REPLACES_FIELD_RE =
|
|
92
102
|
/(?:^|\n) {0,2}['"]?replaces['"]?\s*:\s*['"]([^'"\n]+)['"]/;
|
|
93
103
|
|
|
94
104
|
/**
|
|
95
105
|
* What a quick scan of a doc's text finds before anything parses it: the
|
|
96
|
-
* top-level `name` values it writes (none when a spread could set the name)
|
|
97
|
-
* and the Core name it replaces.
|
|
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.
|
|
98
108
|
* @param {string} text
|
|
99
|
-
* @returns {{names: string[], replaces: string | null}}
|
|
109
|
+
* @returns {{names: string[], hasMembers: boolean, replaces: string | null}}
|
|
100
110
|
*/
|
|
101
111
|
function scanDocText(text) {
|
|
102
112
|
return {
|
|
103
113
|
names: text.includes('...')
|
|
104
114
|
? []
|
|
105
115
|
: [...text.matchAll(NAME_FIELD_RE)].map(match => match[1]),
|
|
116
|
+
hasMembers: MEMBERS_FIELD_RE.test(text),
|
|
106
117
|
replaces: REPLACES_FIELD_RE.exec(text)?.[1] ?? null,
|
|
107
118
|
};
|
|
108
119
|
}
|
|
@@ -112,7 +123,7 @@ function scanDocText(text) {
|
|
|
112
123
|
* component's .doc.mjs file (synchronous), with what {@link scanDocText}
|
|
113
124
|
* finds in it.
|
|
114
125
|
* @param {string} docPath
|
|
115
|
-
* @returns {{group: string | null, hiddenComponents: Set<string>, hidden: boolean, names: string[], replaces: string | null}}
|
|
126
|
+
* @returns {{group: string | null, hiddenComponents: Set<string>, hidden: boolean, names: string[], hasMembers: boolean, replaces: string | null}}
|
|
116
127
|
*/
|
|
117
128
|
function readDocMeta(docPath) {
|
|
118
129
|
try {
|
|
@@ -139,6 +150,7 @@ function readDocMeta(docPath) {
|
|
|
139
150
|
hiddenComponents: new Set(),
|
|
140
151
|
hidden: false,
|
|
141
152
|
names: [],
|
|
153
|
+
hasMembers: false,
|
|
142
154
|
replaces: null,
|
|
143
155
|
};
|
|
144
156
|
}
|
|
@@ -180,9 +192,12 @@ function docMemberNames(source) {
|
|
|
180
192
|
let ast;
|
|
181
193
|
try {
|
|
182
194
|
babelParser ??= require('@babel/parser');
|
|
183
|
-
ast = /** @type {typeof import('@babel/parser')} */ (babelParser).parse(
|
|
184
|
-
|
|
185
|
-
|
|
195
|
+
ast = /** @type {typeof import('@babel/parser')} */ (babelParser).parse(
|
|
196
|
+
source,
|
|
197
|
+
{
|
|
198
|
+
sourceType: 'module',
|
|
199
|
+
},
|
|
200
|
+
);
|
|
186
201
|
} catch {
|
|
187
202
|
return [];
|
|
188
203
|
}
|
|
@@ -197,12 +212,16 @@ function docMemberNames(source) {
|
|
|
197
212
|
for (const declarator of declaration.declarations) {
|
|
198
213
|
if (declarator.id.type !== 'Identifier' || !declarator.init) continue;
|
|
199
214
|
constants.set(declarator.id.name, declarator.init);
|
|
200
|
-
if (
|
|
215
|
+
if (
|
|
216
|
+
node.type === 'ExportNamedDeclaration' &&
|
|
217
|
+
declarator.id.name === 'docs'
|
|
218
|
+
) {
|
|
201
219
|
docsExport = declarator.init;
|
|
202
220
|
}
|
|
203
221
|
}
|
|
204
222
|
}
|
|
205
|
-
if (node.type === 'ExportDefaultDeclaration')
|
|
223
|
+
if (node.type === 'ExportDefaultDeclaration')
|
|
224
|
+
defaultExport = node.declaration;
|
|
206
225
|
}
|
|
207
226
|
let doc = defaultExport ?? docsExport;
|
|
208
227
|
if (doc?.type === 'Identifier') doc = constants.get(doc.name);
|
|
@@ -420,7 +439,9 @@ export function discoverComponents(coreDir) {
|
|
|
420
439
|
if (!entry.isDirectory() || SKIP_DIRS.has(entry.name)) continue;
|
|
421
440
|
const name = entry.name;
|
|
422
441
|
if (componentGroups.has(name) || name.endsWith('Context')) continue;
|
|
423
|
-
if (
|
|
442
|
+
if (
|
|
443
|
+
listDir(path.join(srcDir, name)).some(file => file.endsWith('.doc.mjs'))
|
|
444
|
+
) {
|
|
424
445
|
continue;
|
|
425
446
|
}
|
|
426
447
|
if (!hasOwnFolder(srcDir, name)) continue;
|
|
@@ -875,8 +896,8 @@ export function findExternalComponentDoc(docsDir, name) {
|
|
|
875
896
|
// commands — and the future integration-component swizzle — can disambiguate
|
|
876
897
|
// by package and route source/issues correctly.
|
|
877
898
|
|
|
878
|
-
// A doc's `name` can name another export of its file
|
|
879
|
-
// before.
|
|
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.
|
|
880
901
|
const COMPONENT_NAME_RE = /^[A-Z][\w$]*$/;
|
|
881
902
|
|
|
882
903
|
/** @type {Map<string, {stamp: string, value: Record<string, any> | null}>} doc reads, reused while the doc file is unchanged */
|
|
@@ -940,6 +961,256 @@ function otherExportName({docPath, sourcePath, stem, names}) {
|
|
|
940
961
|
: null;
|
|
941
962
|
}
|
|
942
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
|
+
|
|
943
1214
|
/**
|
|
944
1215
|
* Visit every component doc under an integration's components root, hidden
|
|
945
1216
|
* ones included, in discovery order, with its stem.
|
|
@@ -971,7 +1242,7 @@ export function forEachIntegrationDoc(componentsDir, visit) {
|
|
|
971
1242
|
* @param {string} docPath absolute path to a `{Name}.doc.{ts,mjs,js}` file
|
|
972
1243
|
* @returns {string|null}
|
|
973
1244
|
*/
|
|
974
|
-
function integrationSourceForDoc(docPath) {
|
|
1245
|
+
export function integrationSourceForDoc(docPath) {
|
|
975
1246
|
const dir = path.dirname(docPath);
|
|
976
1247
|
const base = path.basename(docPath).replace(/\.doc\.(ts|mjs|js)$/, '');
|
|
977
1248
|
const candidate = path.join(dir, `${base}.tsx`);
|
|
@@ -983,10 +1254,11 @@ function integrationSourceForDoc(docPath) {
|
|
|
983
1254
|
* integration. Scans the integration's resolved `components` dir for same-stem
|
|
984
1255
|
* doc files and records each with owner package + issuesUrl + sourcePath. A doc
|
|
985
1256
|
* that names a differently named export of its file is recorded under that
|
|
986
|
-
* export
|
|
987
|
-
*
|
|
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.
|
|
988
1260
|
*
|
|
989
|
-
* @param {{name: string, components?: string, issuesUrl?: string}} integration
|
|
1261
|
+
* @param {{name: string, components?: string, issuesUrl?: string, __packageDir?: string, __packageExports?: Record<string, unknown>|null}} integration
|
|
990
1262
|
* a single entry from `loadedIntegrations` (lib/integrations.mjs)
|
|
991
1263
|
* @param {{documentedNames?: boolean}} [options] `documentedNames: false`
|
|
992
1264
|
* records each doc under its file name alone, without reading the names
|
|
@@ -1002,14 +1274,14 @@ export function discoverIntegrationComponents(
|
|
|
1002
1274
|
|
|
1003
1275
|
/** @type {Map<string, {name: string, package: string, docPath: string, sourcePath: string|null, issuesUrl: string|undefined, group: string|null}>} */
|
|
1004
1276
|
const byName = new Map();
|
|
1005
|
-
/** @type {Map<string, {names: string[], replaces: string | null}>} stem → quick scan */
|
|
1277
|
+
/** @type {Map<string, {names: string[], hasMembers: boolean, replaces: string | null}>} stem → quick scan */
|
|
1006
1278
|
const scans = new Map();
|
|
1007
1279
|
/** @type {Set<string>} names no doc can record under another name */
|
|
1008
1280
|
const taken = new Set();
|
|
1009
1281
|
|
|
1010
1282
|
forEachIntegrationDoc(componentsDir, (fullPath, name) => {
|
|
1011
1283
|
taken.add(name);
|
|
1012
|
-
const {group, hidden, names, replaces} = readDocMeta(fullPath);
|
|
1284
|
+
const {group, hidden, names, hasMembers, replaces} = readDocMeta(fullPath);
|
|
1013
1285
|
if (hidden) return;
|
|
1014
1286
|
// First doc wins per name (precedence matches INTEGRATION_DOC_SUFFIXES).
|
|
1015
1287
|
if (byName.has(name)) return;
|
|
@@ -1021,12 +1293,13 @@ export function discoverIntegrationComponents(
|
|
|
1021
1293
|
issuesUrl: integration.issuesUrl,
|
|
1022
1294
|
group: group ?? null,
|
|
1023
1295
|
});
|
|
1024
|
-
scans.set(name, {names, replaces});
|
|
1296
|
+
scans.set(name, {names, hasMembers, replaces});
|
|
1025
1297
|
});
|
|
1026
1298
|
if (!documentedNames) return [...byName.values()];
|
|
1027
1299
|
|
|
1028
1300
|
// A doc that names a differently named export of its file is listed under
|
|
1029
|
-
// that export,
|
|
1301
|
+
// that export, and each member a family doc lists under the member's name,
|
|
1302
|
+
// unless the name is taken.
|
|
1030
1303
|
for (const {replaces} of scans.values()) if (replaces) taken.add(replaces);
|
|
1031
1304
|
const records = [...byName.values()];
|
|
1032
1305
|
const byPath = [...records].sort((a, b) => byCodeUnit(a.docPath, b.docPath));
|
|
@@ -1042,10 +1315,22 @@ export function discoverIntegrationComponents(
|
|
|
1042
1315
|
taken.add(exportName);
|
|
1043
1316
|
exportNames.set(record.name, exportName);
|
|
1044
1317
|
}
|
|
1045
|
-
|
|
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});
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
const named = records.map(record => {
|
|
1046
1330
|
const name = exportNames.get(record.name);
|
|
1047
1331
|
return name ? {...record, name} : record;
|
|
1048
1332
|
});
|
|
1333
|
+
return [...named, ...members];
|
|
1049
1334
|
}
|
|
1050
1335
|
|
|
1051
1336
|
/**
|
|
@@ -1053,27 +1338,33 @@ export function discoverIntegrationComponents(
|
|
|
1053
1338
|
* Invalid metadata or a missing same-stem source removes only that component;
|
|
1054
1339
|
* callers can report the returned errors while retaining valid siblings. A
|
|
1055
1340
|
* component whose doc sets `replaces` carries the value as written; the
|
|
1056
|
-
* component replacement resolver decides what it means. A
|
|
1057
|
-
*
|
|
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.
|
|
1058
1345
|
*
|
|
1059
1346
|
* @param {{name: string, components?: string, issuesUrl?: string}} integration
|
|
1060
1347
|
* @param {{documentedNames?: boolean}} [options] as for
|
|
1061
1348
|
* {@link discoverIntegrationComponents}
|
|
1062
1349
|
* @returns {Promise<{
|
|
1063
|
-
* 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}>,
|
|
1064
1351
|
* discovered: Array<{name: string, package: string, docPath: string, sourcePath: string|null, issuesUrl: string|undefined, group: string|null}>,
|
|
1065
1352
|
* errors: Array<{name: string, message: string}>,
|
|
1066
1353
|
* }>}
|
|
1067
1354
|
*/
|
|
1068
1355
|
export async function discoverValidIntegrationComponents(integration, options) {
|
|
1069
1356
|
const discovered = discoverIntegrationComponents(integration, options);
|
|
1070
|
-
/** @type {Array<{name: string, package: string, docPath: string, sourcePath: string, issuesUrl: string|undefined, group: string|null, replaces?: unknown}>} */
|
|
1357
|
+
/** @type {Array<{name: string, package: string, docPath: string, sourcePath: string|null, issuesUrl: string|undefined, group: string|null, replaces?: unknown}>} */
|
|
1071
1358
|
const components = [];
|
|
1072
1359
|
/** @type {Array<{name: string, message: string}>} */
|
|
1073
1360
|
const errors = [];
|
|
1361
|
+
/** @type {Set<string>} docs that failed to load */
|
|
1362
|
+
const failedDocs = new Set();
|
|
1074
1363
|
|
|
1075
1364
|
for (const record of discovered) {
|
|
1076
|
-
|
|
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)) {
|
|
1077
1368
|
errors.push({
|
|
1078
1369
|
name: record.name,
|
|
1079
1370
|
message: `Component "${record.name}" is missing its same-stem source file ${record.name}.tsx.`,
|
|
@@ -1083,15 +1374,20 @@ export async function discoverValidIntegrationComponents(integration, options) {
|
|
|
1083
1374
|
try {
|
|
1084
1375
|
const doc = await loadComponentDoc(record.docPath);
|
|
1085
1376
|
const valid =
|
|
1086
|
-
/** @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}} */ (
|
|
1087
1378
|
record
|
|
1088
1379
|
);
|
|
1089
1380
|
components.push(
|
|
1090
|
-
|
|
1381
|
+
isOwnRecord(record) &&
|
|
1382
|
+
doc != null &&
|
|
1383
|
+
typeof doc === 'object' &&
|
|
1384
|
+
doc.replaces !== undefined
|
|
1091
1385
|
? {...valid, replaces: doc.replaces}
|
|
1092
1386
|
: valid,
|
|
1093
1387
|
);
|
|
1094
1388
|
} catch (err) {
|
|
1389
|
+
if (failedDocs.has(record.docPath)) continue;
|
|
1390
|
+
failedDocs.add(record.docPath);
|
|
1095
1391
|
const name = docStem(record.docPath);
|
|
1096
1392
|
errors.push({
|
|
1097
1393
|
name,
|
|
@@ -1109,8 +1405,8 @@ export async function discoverValidIntegrationComponents(integration, options) {
|
|
|
1109
1405
|
* How an owner holds a component name, which the owner rule
|
|
1110
1406
|
* ({@link unscopedComponentOwners}) reads: `core` when Core documents it;
|
|
1111
1407
|
* `file` when an integration doc named after its file is recorded under it;
|
|
1112
|
-
* `doc` when only a doc records it: an export name, or the file name
|
|
1113
|
-
* recorded under another export.
|
|
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.
|
|
1114
1410
|
* @typedef {'core' | 'file' | 'doc'} NameHold
|
|
1115
1411
|
*/
|
|
1116
1412
|
|
|
@@ -1126,19 +1422,20 @@ export function integrationRecordHold(record) {
|
|
|
1126
1422
|
}
|
|
1127
1423
|
|
|
1128
1424
|
/**
|
|
1129
|
-
* The doc that records `name`
|
|
1130
|
-
* source, reading only docs that write the name
|
|
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.
|
|
1131
1427
|
*
|
|
1428
|
+
* @param {{name?: string, __packageDir?: string, __packageExports?: Record<string, unknown>|null}} integration
|
|
1132
1429
|
* @param {string} componentsDir
|
|
1133
1430
|
* @param {string} name
|
|
1134
1431
|
* @returns {{docPath: string, sourcePath: string|null} | null}
|
|
1135
1432
|
*/
|
|
1136
|
-
function findDocumentedComponent(componentsDir, name) {
|
|
1433
|
+
function findDocumentedComponent(integration, componentsDir, name) {
|
|
1137
1434
|
if (!COMPONENT_NAME_RE.test(name)) return null;
|
|
1138
1435
|
const literals = [`'${name}'`, `"${name}"`, `\`${name}\``];
|
|
1139
1436
|
/** @type {Set<string>} */
|
|
1140
1437
|
const stems = new Set();
|
|
1141
|
-
/** @type {Array<{docPath: string, stem: string, names: string[], replaces: string | null, sourcePath: string|null}>} */
|
|
1438
|
+
/** @type {Array<{docPath: string, stem: string, names: string[], hasMembers: boolean, replaces: string | null, sourcePath: string|null}>} */
|
|
1142
1439
|
const candidates = [];
|
|
1143
1440
|
forEachIntegrationDoc(componentsDir, (docPath, stem) => {
|
|
1144
1441
|
let text;
|
|
@@ -1161,17 +1458,28 @@ function findDocumentedComponent(componentsDir, name) {
|
|
|
1161
1458
|
// A Core name a doc in the package replaces is the replacement's to answer.
|
|
1162
1459
|
if (candidates.some(candidate => candidate.replaces === name)) return null;
|
|
1163
1460
|
candidates.sort((a, b) => byCodeUnit(a.docPath, b.docPath));
|
|
1164
|
-
const
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
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;
|
|
1168
1475
|
}
|
|
1169
1476
|
|
|
1170
1477
|
/**
|
|
1171
1478
|
* The integration component a name finds, as discovery records it, and its
|
|
1172
|
-
* hold: the same-stem doc first, then a doc that records the name
|
|
1479
|
+
* hold: the same-stem doc first, then a doc that records the name as an export
|
|
1480
|
+
* or a member.
|
|
1173
1481
|
*
|
|
1174
|
-
* @param {{components?: string}} integration
|
|
1482
|
+
* @param {{name?: string, components?: string, __packageDir?: string, __packageExports?: Record<string, unknown>|null}} integration
|
|
1175
1483
|
* @param {string} name
|
|
1176
1484
|
* @returns {{docPath: string, sourcePath: string|null, hold: 'file' | 'doc'} | null}
|
|
1177
1485
|
*/
|
|
@@ -1189,24 +1497,48 @@ export function findIntegrationComponent(integration, name) {
|
|
|
1189
1497
|
const renamed =
|
|
1190
1498
|
exportName != null &&
|
|
1191
1499
|
findSameStemDoc(componentsDir, exportName) == null &&
|
|
1192
|
-
findDocumentedComponent(componentsDir, exportName)
|
|
1500
|
+
findDocumentedComponent(integration, componentsDir, exportName)
|
|
1501
|
+
?.docPath === sameStem;
|
|
1193
1502
|
return {
|
|
1194
1503
|
docPath: sameStem,
|
|
1195
1504
|
sourcePath: integrationSourceForDoc(sameStem),
|
|
1196
1505
|
hold: renamed ? 'doc' : 'file',
|
|
1197
1506
|
};
|
|
1198
1507
|
}
|
|
1199
|
-
const found = findDocumentedComponent(componentsDir, name);
|
|
1508
|
+
const found = findDocumentedComponent(integration, componentsDir, name);
|
|
1200
1509
|
return found && {...found, hold: 'doc'};
|
|
1201
1510
|
}
|
|
1202
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
|
+
|
|
1203
1534
|
/**
|
|
1204
1535
|
* Find an integration component's doc file by name within a loaded
|
|
1205
1536
|
* integration's resolved `components` dir. Honors the same-stem convention
|
|
1206
1537
|
* (`{Name}.doc.{ts,mjs,js}`), preferring `.ts` → `.mjs` → `.js`, and finds a
|
|
1207
|
-
* doc that records the component under a differently named
|
|
1538
|
+
* doc that records the component under another name: a differently named
|
|
1539
|
+
* export of its file, or a member its family doc lists.
|
|
1208
1540
|
*
|
|
1209
|
-
* @param {{components?: string}} integration
|
|
1541
|
+
* @param {{name?: string, components?: string, __packageDir?: string, __packageExports?: Record<string, unknown>|null}} integration
|
|
1210
1542
|
* @param {string} name bare component name (no `XDS`/`Astryx` prefix)
|
|
1211
1543
|
* @returns {string|null}
|
|
1212
1544
|
*/
|
|
@@ -1248,9 +1580,10 @@ function findSameStemDoc(componentsDir, name) {
|
|
|
1248
1580
|
/**
|
|
1249
1581
|
* Find an integration component's swizzleable source file by name within a
|
|
1250
1582
|
* loaded integration's resolved `components` dir: the same-stem `{Name}.tsx`
|
|
1251
|
-
* of its doc
|
|
1583
|
+
* of its doc, or, for a family member, the file that declares it. Returns null
|
|
1584
|
+
* when the integration ships docs without source.
|
|
1252
1585
|
*
|
|
1253
|
-
* @param {{components?: string}} integration
|
|
1586
|
+
* @param {{name?: string, components?: string, __packageDir?: string, __packageExports?: Record<string, unknown>|null}} integration
|
|
1254
1587
|
* @param {string} name bare component name
|
|
1255
1588
|
* @returns {string|null}
|
|
1256
1589
|
*/
|
|
@@ -279,6 +279,26 @@ describe('resolveComponentReplacements', () => {
|
|
|
279
279
|
});
|
|
280
280
|
});
|
|
281
281
|
|
|
282
|
+
it("counts a doc's replaces once when the doc lists its own name as a member", async () => {
|
|
283
|
+
// Chip.doc.mjs documents AcmeChip beside a Chip.tsx that exports both, and
|
|
284
|
+
// lists itself first, as the component-family guide asks.
|
|
285
|
+
const acme = integrationWithDocSource(
|
|
286
|
+
'@acme/chips',
|
|
287
|
+
'Chip',
|
|
288
|
+
`export default {type: 'component', name: 'AcmeChip', replaces: 'Badge', usage: {description: 'Acme chip.'}, components: [{name: 'AcmeChip', description: 'A chip.', props: []}]};\n`,
|
|
289
|
+
);
|
|
290
|
+
fs.writeFileSync(
|
|
291
|
+
path.join(acme.components, 'Chip.tsx'),
|
|
292
|
+
'export function Chip() { return null; }\nexport function AcmeChip() { return null; }\n',
|
|
293
|
+
);
|
|
294
|
+
const result = await resolveComponentReplacements(CORE_DIR, [acme]);
|
|
295
|
+
expect(result.findings).toEqual([]);
|
|
296
|
+
expect(result.forTarget('Badge')).toMatchObject({
|
|
297
|
+
name: 'Chip',
|
|
298
|
+
package: '@acme/chips',
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
282
302
|
it('lets a replacement keep the Core name it replaces', async () => {
|
|
283
303
|
const acme = integration(
|
|
284
304
|
'@acme/nav',
|