@astryxdesign/cli 0.7.2-canary.c9f2659 → 0.7.2-canary.cbb7fa3
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/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/gap-report/gap-report.mjs +67 -4
- package/api/integration/add-contribution.component-names.test.mjs +66 -0
- package/api/integration/add-contribution.mjs +7 -3
- package/api/integration/pack-check.mjs +61 -107
- package/api/integration/pack-check.test.mjs +359 -0
- package/api/search/search.d.mts +9 -0
- package/api/search/search.mjs +108 -16
- 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 +16 -0
- package/api/theme/build/build.mjs +77 -18
- package/api/theme/themeBuild.doc.mjs +3 -3
- 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/tree/add-a-component.doc.mjs +10 -0
- package/assets/docs/tree/checks.doc.mjs +1 -1
- package/assets/docs/tree/component-family.doc.mjs +5 -1
- package/assets/docs/tree/component-imports.doc.mjs +8 -0
- package/assets/docs/tree/single-component.doc.mjs +1 -1
- package/assets/docs/tree/troubleshooting.doc.mjs +9 -1
- package/assets/docs/tree/versioning.doc.mjs +1 -0
- 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/authoring/doctypes/base/type.ts +6 -0
- package/authoring/doctypes/component/component.doc.mjs +4 -4
- package/authoring/doctypes/component/type.ts +6 -3
- package/clients/cli/commands/build-theme.path-safety.test.mjs +84 -0
- package/clients/cli/commands/build-theme.registry.test.mjs +5 -2
- package/clients/cli/commands/theme-build.doc.mjs +2 -2
- package/clients/cli/lib/component-format.mjs +5 -1
- package/foundation/discovery/component-discovery.d.mts +172 -10
- package/foundation/discovery/component-discovery.export-names.test.mjs +760 -0
- package/foundation/discovery/component-discovery.mjs +863 -55
- 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/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/template-adapter.mjs +74 -5
- package/foundation/doc-compiler/doc-loads.test.mjs +5 -1
- package/foundation/doc-compiler/inputs.mjs +23 -0
- package/foundation/doc-compiler/inputs.test.mjs +0 -1
- package/foundation/integrations/contribution-inventory.mjs +6 -1
- package/foundation/xle/registry.mjs +4 -0
- package/foundation/xle/registry.test.mjs +28 -0
- package/package.json +10 -10
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
discoverComponents,
|
|
17
17
|
discoverExternalComponentsGrouped,
|
|
18
18
|
discoverValidIntegrationComponents,
|
|
19
|
+
familyMember,
|
|
19
20
|
findComponentReadme,
|
|
20
21
|
findExternalComponentDoc,
|
|
21
22
|
resolveImportPath,
|
|
@@ -66,6 +67,33 @@ async function legacyImport(ext, name, coreDir, docOpts) {
|
|
|
66
67
|
*/
|
|
67
68
|
const coreEntry = doc => ({name: doc.name, package: CORE_PACKAGE, ...doc});
|
|
68
69
|
|
|
70
|
+
/**
|
|
71
|
+
* The full-level entry for a component documented as a member of its family's
|
|
72
|
+
* doc (VStack in Stack's doc): a component doc built from the member's own
|
|
73
|
+
* entry, under its own name, display name and import, shaped like every other
|
|
74
|
+
* full entry. Null when the doc is the component's own.
|
|
75
|
+
* @param {any} docs
|
|
76
|
+
* @param {string} name
|
|
77
|
+
* @param {string} coreDir
|
|
78
|
+
* @returns {any}
|
|
79
|
+
*/
|
|
80
|
+
function memberEntry(docs, name, coreDir) {
|
|
81
|
+
const member = familyMember(docs, name);
|
|
82
|
+
if (!member) return null;
|
|
83
|
+
return {
|
|
84
|
+
name,
|
|
85
|
+
package: CORE_PACKAGE,
|
|
86
|
+
displayName: member.displayName ?? name,
|
|
87
|
+
...(docs.group ? {group: docs.group} : {}),
|
|
88
|
+
...(docs.category ? {category: docs.category} : {}),
|
|
89
|
+
usage: member.usage ?? {description: member.description ?? ''},
|
|
90
|
+
props: member.props ?? [],
|
|
91
|
+
...(member.examples ? {examples: member.examples} : {}),
|
|
92
|
+
...(docs.theming ? {theming: docs.theming} : {}),
|
|
93
|
+
import: resolveImportPath(coreDir, name),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
69
97
|
/**
|
|
70
98
|
* The entry for a Core slot an integration component replaces
|
|
71
99
|
* (spec:AST-035 FR11), at the list's detail level: the replacement's own name,
|
|
@@ -177,7 +205,11 @@ export async function componentList(
|
|
|
177
205
|
entries.push({
|
|
178
206
|
name: comp,
|
|
179
207
|
package: CORE_PACKAGE,
|
|
180
|
-
description:
|
|
208
|
+
description:
|
|
209
|
+
familyMember(docs, comp)?.description ||
|
|
210
|
+
docs.usage?.description ||
|
|
211
|
+
docs.description ||
|
|
212
|
+
'',
|
|
181
213
|
import: resolveImportPath(coreDir, comp),
|
|
182
214
|
});
|
|
183
215
|
} catch {
|
|
@@ -215,7 +247,8 @@ export async function componentList(
|
|
|
215
247
|
const readme = findComponentReadme(coreDir, comp);
|
|
216
248
|
if (readme && readme.endsWith('.doc.mjs')) {
|
|
217
249
|
try {
|
|
218
|
-
|
|
250
|
+
const docs = await loadComponentDoc(readme, {zh, lang, dense});
|
|
251
|
+
entries.push(memberEntry(docs, comp, coreDir) ?? coreEntry(docs));
|
|
219
252
|
} catch {
|
|
220
253
|
entries.push({name: `XDS${comp}`, package: CORE_PACKAGE, description: ''});
|
|
221
254
|
}
|
|
@@ -269,7 +302,11 @@ export async function componentList(
|
|
|
269
302
|
result[cat].push({
|
|
270
303
|
name: comp,
|
|
271
304
|
package: CORE_PACKAGE,
|
|
272
|
-
description:
|
|
305
|
+
description:
|
|
306
|
+
familyMember(docs, comp)?.description ||
|
|
307
|
+
docs.usage?.description ||
|
|
308
|
+
docs.description ||
|
|
309
|
+
'',
|
|
273
310
|
import: resolveImportPath(coreDir, comp),
|
|
274
311
|
});
|
|
275
312
|
} catch {
|
|
@@ -310,7 +347,8 @@ export async function componentList(
|
|
|
310
347
|
const readme = findComponentReadme(coreDir, comp);
|
|
311
348
|
if (readme && readme.endsWith('.doc.mjs')) {
|
|
312
349
|
try {
|
|
313
|
-
|
|
350
|
+
const docs = await loadComponentDoc(readme, {zh, lang, dense});
|
|
351
|
+
result[cat].push(memberEntry(docs, comp, coreDir) ?? coreEntry(docs));
|
|
314
352
|
} catch {
|
|
315
353
|
result[cat].push({name: `XDS${comp}`, package: CORE_PACKAGE, description: ''});
|
|
316
354
|
}
|
|
@@ -17,7 +17,18 @@
|
|
|
17
17
|
import {Worker} from 'node:worker_threads';
|
|
18
18
|
import {AstryxError} from '../error.mjs';
|
|
19
19
|
import {Project} from '../../foundation/config/project.mjs';
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
CORE_PACKAGE,
|
|
22
|
+
discoverIntegrationComponents,
|
|
23
|
+
docStem,
|
|
24
|
+
findComponentReadme,
|
|
25
|
+
findIntegrationComponent,
|
|
26
|
+
integrationRecordHold,
|
|
27
|
+
isIntegrationMember,
|
|
28
|
+
unscopedComponentOwners,
|
|
29
|
+
} from '../../foundation/discovery/component-discovery.mjs';
|
|
30
|
+
import {legacyExternalKeeps} from '../component/_adapter.mjs';
|
|
31
|
+
import {findCoreDir} from '../../foundation/fs/paths.mjs';
|
|
21
32
|
import {captureEnv} from '../../foundation/debug/event.mjs';
|
|
22
33
|
import {ERROR_CODES} from '../../foundation/response/error-codes.mjs';
|
|
23
34
|
import {parseGapReportReceipt} from '../../authoring/gap-report/parse.mjs';
|
|
@@ -178,13 +189,65 @@ async function selectTarget(project, component, packageName) {
|
|
|
178
189
|
coreName,
|
|
179
190
|
);
|
|
180
191
|
if (replacement) return targetForPackage(project, replacement.package);
|
|
192
|
+
// The owner rule runs over every owner discovery finds, loadable or not
|
|
193
|
+
// (a doc recorded under another export still answers to its file's name);
|
|
194
|
+
// then only owners whose docs load route a report.
|
|
195
|
+
const wanted = bareComponent.toLowerCase();
|
|
196
|
+
const loads = new Set(records.map(r => `${r.package}\0${r.docPath}`));
|
|
197
|
+
const recorded = project.loadedIntegrations.flatMap(integration =>
|
|
198
|
+
discoverIntegrationComponents(integration).flatMap(r =>
|
|
199
|
+
r.name.toLowerCase() === wanted
|
|
200
|
+
? [{...r, hold: integrationRecordHold(r)}]
|
|
201
|
+
: docStem(r.docPath).toLowerCase() === wanted && !isIntegrationMember(r)
|
|
202
|
+
? [{...r, hold: 'doc'}]
|
|
203
|
+
: [],
|
|
204
|
+
),
|
|
205
|
+
);
|
|
206
|
+
// The checks below find files by name, so they run on every spelling the
|
|
207
|
+
// case-blind match above found, as well as the one typed.
|
|
208
|
+
const spellings = new Set([
|
|
209
|
+
bareComponent,
|
|
210
|
+
...recorded.flatMap(r =>
|
|
211
|
+
[r.name, docStem(r.docPath)].filter(n => n.toLowerCase() === wanted),
|
|
212
|
+
),
|
|
213
|
+
]);
|
|
214
|
+
// Core also holds a name only its doc lookup finds, as in detail: a name a
|
|
215
|
+
// doc records yields to it, though it routes no report itself.
|
|
216
|
+
const coreDir = findCoreDir(project.cwd);
|
|
217
|
+
const coreDocs =
|
|
218
|
+
coreDir != null &&
|
|
219
|
+
[...spellings].some(name => findComponentReadme(coreDir, name) != null);
|
|
220
|
+
const ruled = unscopedComponentOwners([
|
|
221
|
+
...records
|
|
222
|
+
.filter(
|
|
223
|
+
r => r.package === CORE_PACKAGE && r.name.toLowerCase() === wanted,
|
|
224
|
+
)
|
|
225
|
+
.map(r => ({...r, hold: 'core'})),
|
|
226
|
+
...recorded,
|
|
227
|
+
// A hidden doc named after its file still holds that name, as the
|
|
228
|
+
// component lookup finds it; it routes no report.
|
|
229
|
+
...project.loadedIntegrations.flatMap(integration =>
|
|
230
|
+
[...spellings].flatMap(spelling => {
|
|
231
|
+
const found = findIntegrationComponent(integration, spelling);
|
|
232
|
+
return found?.hold === 'file'
|
|
233
|
+
? [{...found, package: integration.name}]
|
|
234
|
+
: [];
|
|
235
|
+
}),
|
|
236
|
+
),
|
|
237
|
+
]).filter(r => !coreDocs || r.hold !== 'doc');
|
|
238
|
+
// A legacy docs package keeps an export name a doc records, as in detail.
|
|
181
239
|
const owners = [
|
|
182
240
|
...new Set(
|
|
183
|
-
|
|
241
|
+
([...spellings].some(name =>
|
|
242
|
+
legacyExternalKeeps(project.cwd, name, ruled),
|
|
243
|
+
)
|
|
244
|
+
? []
|
|
245
|
+
: ruled
|
|
246
|
+
)
|
|
184
247
|
.filter(
|
|
185
|
-
|
|
248
|
+
r => r.hold === 'core' || loads.has(`${r.package}\0${r.docPath}`),
|
|
186
249
|
)
|
|
187
|
-
.map(
|
|
250
|
+
.map(r => r.package),
|
|
188
251
|
),
|
|
189
252
|
];
|
|
190
253
|
|
|
@@ -98,6 +98,72 @@ describe('integrationAddComponent name collisions', () => {
|
|
|
98
98
|
expect(snapshot()).toEqual(before);
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
+
it('adds a name a family doc lists but the module it imports does not export', async () => {
|
|
102
|
+
fs.writeFileSync(
|
|
103
|
+
path.join(tmpDir, 'package.json'),
|
|
104
|
+
`${JSON.stringify({name: '@acme/widgets', version: '1.0.0', files: ['dist'], exports: {'./tabs': './components/tabs/index.ts'}}, null, 2)}\n`,
|
|
105
|
+
);
|
|
106
|
+
const tabs = path.join(tmpDir, 'components', 'tabs');
|
|
107
|
+
fs.mkdirSync(tabs, {recursive: true});
|
|
108
|
+
fs.writeFileSync(
|
|
109
|
+
path.join(tabs, 'AcmeTabs.doc.mjs'),
|
|
110
|
+
"export default {type: 'component', name: 'AcmeTabs', import: '@acme/widgets/tabs', description: 'x', components: [{name: 'AcmeTabExtra', description: 'x'}]};\n",
|
|
111
|
+
);
|
|
112
|
+
fs.writeFileSync(
|
|
113
|
+
path.join(tabs, 'AcmeTabs.tsx'),
|
|
114
|
+
'export function AcmeTabs() { return null; }\nexport function AcmeTabExtra() { return null; }\n',
|
|
115
|
+
);
|
|
116
|
+
fs.writeFileSync(
|
|
117
|
+
path.join(tabs, 'index.ts'),
|
|
118
|
+
"export {AcmeTabs} from './AcmeTabs';\n",
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const result = await integrationAdd('component', 'AcmeTabExtra', {
|
|
122
|
+
cwd: tmpDir,
|
|
123
|
+
dryRun: true,
|
|
124
|
+
});
|
|
125
|
+
expect(result.data.files).toContain('components/AcmeTabExtra.doc.mjs');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('refuses a member a family exports through built output', async () => {
|
|
129
|
+
fs.writeFileSync(
|
|
130
|
+
path.join(tmpDir, 'package.json'),
|
|
131
|
+
`${JSON.stringify({name: '@acme/widgets', version: '1.0.0', files: ['dist'], exports: {'./tabs': './dist/tabs/index.js'}}, null, 2)}\n`,
|
|
132
|
+
);
|
|
133
|
+
const tabs = path.join(tmpDir, 'components', 'tabs');
|
|
134
|
+
fs.mkdirSync(tabs, {recursive: true});
|
|
135
|
+
fs.writeFileSync(
|
|
136
|
+
path.join(tabs, 'AcmeTabs.doc.mjs'),
|
|
137
|
+
"export default {type: 'component', name: 'AcmeTabs', import: '@acme/widgets/tabs', description: 'x', components: [{name: 'AcmeTab', description: 'x'}]};\n",
|
|
138
|
+
);
|
|
139
|
+
fs.writeFileSync(
|
|
140
|
+
path.join(tabs, 'AcmeTabs.tsx'),
|
|
141
|
+
'export function AcmeTabs() { return null; }\n',
|
|
142
|
+
);
|
|
143
|
+
fs.writeFileSync(
|
|
144
|
+
path.join(tabs, 'AcmeTab.tsx'),
|
|
145
|
+
'export function AcmeTab() { return null; }\n',
|
|
146
|
+
);
|
|
147
|
+
const dist = path.join(tmpDir, 'dist', 'tabs');
|
|
148
|
+
fs.mkdirSync(dist, {recursive: true});
|
|
149
|
+
fs.writeFileSync(
|
|
150
|
+
path.join(dist, 'index.js'),
|
|
151
|
+
"export {AcmeTabs} from './tabs.js';\nexport {AcmeTab} from './tab.js';\n",
|
|
152
|
+
);
|
|
153
|
+
fs.writeFileSync(
|
|
154
|
+
path.join(dist, 'tabs.js'),
|
|
155
|
+
'export function AcmeTabs() { return null; }\n',
|
|
156
|
+
);
|
|
157
|
+
fs.writeFileSync(
|
|
158
|
+
path.join(dist, 'tab.js'),
|
|
159
|
+
'export function AcmeTab() { return null; }\n',
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
await expect(
|
|
163
|
+
integrationAdd('component', 'AcmeTab', {cwd: tmpDir, dryRun: true}),
|
|
164
|
+
).rejects.toThrow('Refusing to add component "AcmeTab"');
|
|
165
|
+
});
|
|
166
|
+
|
|
101
167
|
it('still adds a component whose name is free', async () => {
|
|
102
168
|
writeAuthoredComponent('components/AcmeWidget/AcmeWidget.doc.mjs');
|
|
103
169
|
|
|
@@ -264,9 +264,13 @@ async function addComponent(name, options) {
|
|
|
264
264
|
ERROR_CODES.ERR_FILE_EXISTS,
|
|
265
265
|
);
|
|
266
266
|
}
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
|
|
267
|
+
// A second doc under a name discovery already records, by a doc's file name
|
|
268
|
+
// anywhere under the root or by a name a doc documents, would shadow one of
|
|
269
|
+
// the two.
|
|
270
|
+
const existingDoc = findIntegrationComponentDoc(
|
|
271
|
+
{name: owner, components: root, __packageDir: packageDir},
|
|
272
|
+
name,
|
|
273
|
+
);
|
|
270
274
|
if (existingDoc != null) {
|
|
271
275
|
throw new AstryxError(
|
|
272
276
|
`Refusing to add component "${name}": ${projectPath(path.relative(packageDir, existingDoc))} already defines it.`,
|
|
@@ -24,7 +24,6 @@ import * as path from 'node:path';
|
|
|
24
24
|
import * as os from 'node:os';
|
|
25
25
|
import {fileURLToPath} from 'node:url';
|
|
26
26
|
import {spawnSync} from 'node:child_process';
|
|
27
|
-
import jscodeshift from 'jscodeshift';
|
|
28
27
|
import {assertWithin} from '../../foundation/fs/path-safety.mjs';
|
|
29
28
|
import {resolvePackageDir} from '../../foundation/integrations/integrations.mjs';
|
|
30
29
|
import {
|
|
@@ -41,6 +40,7 @@ import {
|
|
|
41
40
|
} from '../../foundation/discovery/docs-discovery.mjs';
|
|
42
41
|
import {discoverIntegrationThemes} from '../../foundation/discovery/theme-discovery.mjs';
|
|
43
42
|
import {inspectPackageExport} from '../../foundation/discovery/package-exports.mjs';
|
|
43
|
+
import {moduleExportsName} from '../../foundation/discovery/module-exports.mjs';
|
|
44
44
|
import {
|
|
45
45
|
ThemeImportError,
|
|
46
46
|
resolveThemeImports,
|
|
@@ -51,6 +51,9 @@ import {
|
|
|
51
51
|
} from '../theme/build/build.mjs';
|
|
52
52
|
import {
|
|
53
53
|
discoverIntegrationComponents,
|
|
54
|
+
docStem,
|
|
55
|
+
familyImports,
|
|
56
|
+
isIntegrationMember,
|
|
54
57
|
resolveIntegrationImportPath,
|
|
55
58
|
} from '../../foundation/discovery/component-discovery.mjs';
|
|
56
59
|
import {loadComponentReplacements} from '../component/_adapter.mjs';
|
|
@@ -307,98 +310,6 @@ function extractTarball(tgzPath, extractDir) {
|
|
|
307
310
|
}
|
|
308
311
|
}
|
|
309
312
|
|
|
310
|
-
const MODULE_EXTENSIONS = ['.mjs', '.js', '.mts', '.ts', '.tsx', '.jsx'];
|
|
311
|
-
const moduleParser = jscodeshift.withParser('tsx');
|
|
312
|
-
|
|
313
|
-
/** @param {unknown} specifier @param {string} fromFile */
|
|
314
|
-
function resolveLocalModule(specifier, fromFile) {
|
|
315
|
-
if (typeof specifier !== 'string' || !specifier.startsWith('.')) return null;
|
|
316
|
-
const base = path.resolve(path.dirname(fromFile), specifier);
|
|
317
|
-
const candidates = [
|
|
318
|
-
base,
|
|
319
|
-
...MODULE_EXTENSIONS.map(extension => `${base}${extension}`),
|
|
320
|
-
...MODULE_EXTENSIONS.map(extension => path.join(base, `index${extension}`)),
|
|
321
|
-
];
|
|
322
|
-
return candidates.find(candidate => fs.existsSync(candidate)) ?? null;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/** @param {any} declaration @param {string} exportName */
|
|
326
|
-
function declarationExports(declaration, exportName) {
|
|
327
|
-
if (!declaration) return false;
|
|
328
|
-
if (['FunctionDeclaration', 'ClassDeclaration'].includes(declaration.type)) {
|
|
329
|
-
return declaration.id?.name === exportName;
|
|
330
|
-
}
|
|
331
|
-
if (declaration.type !== 'VariableDeclaration') return false;
|
|
332
|
-
return declaration.declarations.some(
|
|
333
|
-
(/** @type {any} */ item) =>
|
|
334
|
-
item.id?.type === 'Identifier' && item.id.name === exportName,
|
|
335
|
-
);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* Check one resolved module's static export surface without executing package
|
|
340
|
-
* code. Local ESM re-exports are followed recursively.
|
|
341
|
-
*
|
|
342
|
-
* @param {string} file
|
|
343
|
-
* @param {string} exportName
|
|
344
|
-
* @param {Set<string>} [seen]
|
|
345
|
-
*/
|
|
346
|
-
function moduleExportsName(file, exportName, seen = new Set()) {
|
|
347
|
-
const identity = `${file}\0${exportName}`;
|
|
348
|
-
if (seen.has(identity) || !fs.existsSync(file)) return false;
|
|
349
|
-
seen.add(identity);
|
|
350
|
-
|
|
351
|
-
let root;
|
|
352
|
-
try {
|
|
353
|
-
root = moduleParser(fs.readFileSync(file, 'utf-8'));
|
|
354
|
-
} catch {
|
|
355
|
-
return false;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
let found = false;
|
|
359
|
-
root
|
|
360
|
-
.find(moduleParser.ExportNamedDeclaration)
|
|
361
|
-
.forEach((/** @type {any} */ exportPath) => {
|
|
362
|
-
if (found) return;
|
|
363
|
-
const node = exportPath.node;
|
|
364
|
-
if (declarationExports(node.declaration, exportName)) {
|
|
365
|
-
found = true;
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
for (const specifier of node.specifiers ?? []) {
|
|
369
|
-
const exported = specifier.exported?.name ?? specifier.exported?.value;
|
|
370
|
-
if (exported !== exportName) continue;
|
|
371
|
-
if (!node.source) {
|
|
372
|
-
found = true;
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
const target = resolveLocalModule(node.source.value, file);
|
|
376
|
-
const imported =
|
|
377
|
-
specifier.local?.name ?? specifier.local?.value ?? exportName;
|
|
378
|
-
if (target && moduleExportsName(target, imported, seen)) {
|
|
379
|
-
found = true;
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
});
|
|
384
|
-
if (found) return true;
|
|
385
|
-
if (
|
|
386
|
-
exportName === 'default' &&
|
|
387
|
-
root.find(moduleParser.ExportDefaultDeclaration).size() > 0
|
|
388
|
-
) {
|
|
389
|
-
return true;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
root
|
|
393
|
-
.find(moduleParser.ExportAllDeclaration)
|
|
394
|
-
.forEach((/** @type {any} */ exportPath) => {
|
|
395
|
-
if (found) return;
|
|
396
|
-
const target = resolveLocalModule(exportPath.node.source?.value, file);
|
|
397
|
-
if (target && moduleExportsName(target, exportName, seen)) found = true;
|
|
398
|
-
});
|
|
399
|
-
return found;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
313
|
/**
|
|
403
314
|
* Whether any of these doc files has a section that sets `id`.
|
|
404
315
|
* @param {string[]} files
|
|
@@ -472,15 +383,20 @@ async function validatePackedComponentExports(integration, scratchBase) {
|
|
|
472
383
|
const issues = [];
|
|
473
384
|
/** @type {Array<{name: string, specifier: string}>} */
|
|
474
385
|
const components = [];
|
|
386
|
+
// A family doc backs several records: load it, and report it, once.
|
|
387
|
+
const loads = new Map();
|
|
475
388
|
for (const record of records) {
|
|
389
|
+
const first = !loads.has(record.docPath);
|
|
390
|
+
if (first) loads.set(record.docPath, loadComponentDoc(record.docPath));
|
|
476
391
|
let docs;
|
|
477
392
|
try {
|
|
478
|
-
docs = await
|
|
393
|
+
docs = await loads.get(record.docPath);
|
|
479
394
|
} catch (err) {
|
|
395
|
+
if (!first) continue;
|
|
480
396
|
issues.push(
|
|
481
397
|
error(
|
|
482
398
|
'component_doc_unloadable',
|
|
483
|
-
`Component "${record.
|
|
399
|
+
`Component "${docStem(record.docPath)}" doc at "${record.docPath}" could not be loaded: ${err instanceof Error ? err.message : String(err)}`,
|
|
484
400
|
),
|
|
485
401
|
);
|
|
486
402
|
continue;
|
|
@@ -660,11 +576,10 @@ async function validateIncompleteThemeExports(
|
|
|
660
576
|
const expectedCss = path.join(expectedDir, `${localTheme.slug}.css`);
|
|
661
577
|
try {
|
|
662
578
|
if (outputs.some(output => output.kind !== 'font stylesheet')) {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
);
|
|
579
|
+
// The scratch directory is the write root; the package stays the cwd,
|
|
580
|
+
// so the build selects the package's Core and its paths as before.
|
|
581
|
+
const scratchBuild = {out: expectedCss, __writeRoot: expectedDir};
|
|
582
|
+
const receipt = await themeBuild(source, scratchBuild, {cwd: packageDir});
|
|
668
583
|
if (receipt == null || receipt.type !== 'theme.build') {
|
|
669
584
|
throw new Error('the theme build produced no complete receipt');
|
|
670
585
|
}
|
|
@@ -847,11 +762,12 @@ async function validatePackedThemeExports(
|
|
|
847
762
|
);
|
|
848
763
|
const expectedCss = path.join(expectedDir, `${localTheme.slug}.css`);
|
|
849
764
|
try {
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
765
|
+
// The scratch directory is the write root; the package stays the cwd,
|
|
766
|
+
// so the build selects the package's Core and its paths as before.
|
|
767
|
+
const scratchBuild = {out: expectedCss, __writeRoot: expectedDir};
|
|
768
|
+
const receipt = await themeBuild(source, scratchBuild, {
|
|
769
|
+
cwd: localIntegration.__packageDir,
|
|
770
|
+
});
|
|
855
771
|
if (receipt == null || receipt.type !== 'theme.build') {
|
|
856
772
|
issues.push(
|
|
857
773
|
error(
|
|
@@ -1085,6 +1001,24 @@ export async function integrationPackCheck(options = {}) {
|
|
|
1085
1001
|
const fileInv = computeRequiredFiles(loaded);
|
|
1086
1002
|
const {identities: localIdentities, errors: localDiscoveryErrors} =
|
|
1087
1003
|
await collectIdentities(loaded);
|
|
1004
|
+
// A family lists its members from the module its import names, which the
|
|
1005
|
+
// pack lifecycle may build or rebuild, so they are compared as local
|
|
1006
|
+
// discovery lists them after the pack.
|
|
1007
|
+
const familiesBefore = familyImports(loaded).map(family => family.docPath);
|
|
1008
|
+
/**
|
|
1009
|
+
* @param {Array<{name: string, docPath: string|null}>} records
|
|
1010
|
+
* @param {Set<string>} families
|
|
1011
|
+
*/
|
|
1012
|
+
const membersOf = (records, families) =>
|
|
1013
|
+
records
|
|
1014
|
+
.filter(
|
|
1015
|
+
r =>
|
|
1016
|
+
r.docPath != null &&
|
|
1017
|
+
families.has(r.docPath) &&
|
|
1018
|
+
isIntegrationMember(r),
|
|
1019
|
+
)
|
|
1020
|
+
.map(r => r.name);
|
|
1021
|
+
const recordsBefore = discoverIntegrationComponents(loaded);
|
|
1088
1022
|
for (const discoveryError of localDiscoveryErrors) {
|
|
1089
1023
|
issues.push(
|
|
1090
1024
|
error(
|
|
@@ -1345,7 +1279,27 @@ export async function integrationPackCheck(options = {}) {
|
|
|
1345
1279
|
);
|
|
1346
1280
|
}
|
|
1347
1281
|
|
|
1348
|
-
|
|
1282
|
+
const families = new Set([
|
|
1283
|
+
...familiesBefore,
|
|
1284
|
+
...familyImports(loaded).map(family => family.docPath),
|
|
1285
|
+
]);
|
|
1286
|
+
let localComponents = localIdentities.components;
|
|
1287
|
+
if (families.size > 0) {
|
|
1288
|
+
const stale = new Set(membersOf(recordsBefore, families));
|
|
1289
|
+
const built = new Set(
|
|
1290
|
+
membersOf(discoverIntegrationComponents(loaded), families),
|
|
1291
|
+
);
|
|
1292
|
+
const {identities: afterPack} = await collectIdentities(loaded);
|
|
1293
|
+
localComponents = [
|
|
1294
|
+
...new Set([
|
|
1295
|
+
...localComponents.filter(name => !stale.has(name)),
|
|
1296
|
+
...afterPack.components.filter(name => built.has(name)),
|
|
1297
|
+
]),
|
|
1298
|
+
].sort();
|
|
1299
|
+
}
|
|
1300
|
+
// The receipt reports the local inventory that was compared.
|
|
1301
|
+
const comparedLocal = {...localIdentities, components: localComponents};
|
|
1302
|
+
issues.push(...compareIdentities(comparedLocal, packedIdentities));
|
|
1349
1303
|
issues.push(
|
|
1350
1304
|
...(await validatePackedThemeExports(
|
|
1351
1305
|
loaded,
|
|
@@ -1367,7 +1321,7 @@ export async function integrationPackCheck(options = {}) {
|
|
|
1367
1321
|
pkgVersion,
|
|
1368
1322
|
fileInv,
|
|
1369
1323
|
packResult,
|
|
1370
|
-
|
|
1324
|
+
comparedLocal,
|
|
1371
1325
|
packedIdentities,
|
|
1372
1326
|
issues,
|
|
1373
1327
|
);
|