@astryxdesign/cli 0.7.2-canary.db3a2e7 → 0.7.2-canary.ebcc404
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 +27 -1
- package/api/component/_adapter.mjs +45 -7
- package/api/component/component.mjs +12 -3
- package/api/component/component.test.mjs +420 -167
- package/api/component/integration-export-name.test.mjs +419 -0
- package/api/component/list/list.mjs +42 -4
- package/api/gap-report/gap-report.mjs +66 -4
- package/api/integration/pack-check.mjs +13 -104
- package/api/integration/pack-check.test.mjs +112 -0
- package/api/search/search.d.mts +9 -0
- package/api/search/search.mjs +90 -10
- 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/component-imports.doc.mjs +8 -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 +3 -3
- 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 +95 -6
- package/foundation/discovery/component-discovery.export-names.test.mjs +293 -0
- package/foundation/discovery/component-discovery.mjs +524 -49
- package/foundation/discovery/component-discovery.test.mjs +96 -0
- package/foundation/discovery/component-replacement.mjs +18 -0
- package/foundation/discovery/component-replacement.test.mjs +20 -0
- package/foundation/discovery/module-exports.d.mts +21 -0
- package/foundation/discovery/module-exports.mjs +343 -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/xle/registry.mjs +4 -0
- package/foundation/xle/registry.test.mjs +28 -0
- package/package.json +10 -10
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Static reads of a module's exports, without running it: the file that
|
|
5
|
+
* declares an export, and an exported object literal as plain data.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {afterEach, beforeEach, describe, expect, it} from 'vitest';
|
|
9
|
+
import * as fs from 'node:fs';
|
|
10
|
+
import * as os from 'node:os';
|
|
11
|
+
import * as path from 'node:path';
|
|
12
|
+
import {moduleExportSource, staticExportObject} from './module-exports.mjs';
|
|
13
|
+
|
|
14
|
+
/** @type {string} */
|
|
15
|
+
let dir;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-export-field-'));
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
fs.rmSync(dir, {recursive: true, force: true});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} file
|
|
27
|
+
* @param {string} source
|
|
28
|
+
* @returns {string}
|
|
29
|
+
*/
|
|
30
|
+
function write(file, source) {
|
|
31
|
+
const filePath = path.join(dir, file);
|
|
32
|
+
fs.writeFileSync(filePath, source);
|
|
33
|
+
return filePath;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const DOC_EXPORTS = ['default', 'docs'];
|
|
37
|
+
|
|
38
|
+
describe('staticExportObject', () => {
|
|
39
|
+
it('reads a default-exported object literal as plain data', () => {
|
|
40
|
+
const file = write(
|
|
41
|
+
'A.doc.mjs',
|
|
42
|
+
"export default {type: 'component', name: 'AcmeCarousel', order: 2, hidden: false, extra: null};\n",
|
|
43
|
+
);
|
|
44
|
+
expect(staticExportObject(file, DOC_EXPORTS)).toEqual({
|
|
45
|
+
type: 'component',
|
|
46
|
+
name: 'AcmeCarousel',
|
|
47
|
+
order: 2,
|
|
48
|
+
hidden: false,
|
|
49
|
+
extra: null,
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('reads a named export, an export list, and the first export it looks for', () => {
|
|
54
|
+
const named = write(
|
|
55
|
+
'B.doc.mjs',
|
|
56
|
+
"/** @type {object} */\nexport const docs = {\n name: 'AcmeCarousel',\n};\n",
|
|
57
|
+
);
|
|
58
|
+
const both = write(
|
|
59
|
+
'C.doc.mjs',
|
|
60
|
+
"export const docs = {name: 'Legacy'};\nexport default {name: 'Stamped'};\n",
|
|
61
|
+
);
|
|
62
|
+
const list = write(
|
|
63
|
+
'E.doc.mjs',
|
|
64
|
+
"const local = {name: 'AcmeCarousel'};\nexport {local as docs};\n",
|
|
65
|
+
);
|
|
66
|
+
expect(staticExportObject(named, DOC_EXPORTS)?.name).toBe('AcmeCarousel');
|
|
67
|
+
expect(staticExportObject(both, DOC_EXPORTS)?.name).toBe('Stamped');
|
|
68
|
+
expect(staticExportObject(list, DOC_EXPORTS)?.name).toBe('AcmeCarousel');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('follows top-level bindings through TypeScript wrappers, at any depth', () => {
|
|
72
|
+
const file = write(
|
|
73
|
+
'D.doc.ts',
|
|
74
|
+
[
|
|
75
|
+
"const slide = {name: 'AcmeSlide', description: 'One slide.'} as const;",
|
|
76
|
+
"const doc = {name: 'AcmeCarousel', components: [slide, {name: 'AcmeDots', description: 'Dots.'}]} satisfies {name: string};",
|
|
77
|
+
'export default doc;',
|
|
78
|
+
'',
|
|
79
|
+
].join('\n'),
|
|
80
|
+
);
|
|
81
|
+
expect(staticExportObject(file, DOC_EXPORTS)).toEqual({
|
|
82
|
+
name: 'AcmeCarousel',
|
|
83
|
+
components: [
|
|
84
|
+
{name: 'AcmeSlide', description: 'One slide.'},
|
|
85
|
+
{name: 'AcmeDots', description: 'Dots.'},
|
|
86
|
+
],
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('leaves out what it cannot read without running the module', () => {
|
|
91
|
+
const file = write(
|
|
92
|
+
'F.doc.mjs',
|
|
93
|
+
[
|
|
94
|
+
"const prefix = 'Acme';",
|
|
95
|
+
'const shared = {group: "Media"};',
|
|
96
|
+
'export default {',
|
|
97
|
+
' // A computed string, a call and a spread: none is read.',
|
|
98
|
+
' name: `${prefix}Carousel`,',
|
|
99
|
+
' usage: makeUsage(),',
|
|
100
|
+
" group: 'Overridden',",
|
|
101
|
+
' ...shared,',
|
|
102
|
+
" category: 'Media',",
|
|
103
|
+
" components: [...more, {name: 'AcmeSlide'}],",
|
|
104
|
+
'};',
|
|
105
|
+
'',
|
|
106
|
+
].join('\n'),
|
|
107
|
+
);
|
|
108
|
+
expect(staticExportObject(file, DOC_EXPORTS)).toEqual({
|
|
109
|
+
category: 'Media',
|
|
110
|
+
components: [{name: 'AcmeSlide'}],
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('answers null when there is no exported object', () => {
|
|
115
|
+
const files = [
|
|
116
|
+
write(
|
|
117
|
+
'G.doc.mjs',
|
|
118
|
+
"export const docs = {name: 'Legacy'};\nexport default makeDoc();\n",
|
|
119
|
+
),
|
|
120
|
+
write('H.doc.mjs', 'export default {name: '),
|
|
121
|
+
write('I.doc.mjs', "export const other = {name: 'AcmeCarousel'};\n"),
|
|
122
|
+
write('J.doc.mjs', "export default ['AcmeCarousel'];\n"),
|
|
123
|
+
];
|
|
124
|
+
for (const file of files) {
|
|
125
|
+
expect(staticExportObject(file, DOC_EXPORTS)).toBeNull();
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe('moduleExportSource', () => {
|
|
131
|
+
it('names the file that declares an export, following local re-exports', () => {
|
|
132
|
+
const impl = write(
|
|
133
|
+
'impl.tsx',
|
|
134
|
+
'export function AcmeSlide() { return null; }\n',
|
|
135
|
+
);
|
|
136
|
+
const barrel = write(
|
|
137
|
+
'index.ts',
|
|
138
|
+
"export {AcmeSlide} from './impl';\nexport * from './dots';\nexport function AcmeCarousel() { return null; }\n",
|
|
139
|
+
);
|
|
140
|
+
const dots = write('dots.tsx', 'export const AcmeDots = () => null;\n');
|
|
141
|
+
expect(moduleExportSource(barrel, 'AcmeSlide')).toBe(impl);
|
|
142
|
+
expect(moduleExportSource(barrel, 'AcmeDots')).toBe(dots);
|
|
143
|
+
expect(moduleExportSource(barrel, 'AcmeCarousel')).toBe(barrel);
|
|
144
|
+
expect(moduleExportSource(barrel, 'AcmeMissing')).toBeNull();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('does not follow a re-export written in a comment', () => {
|
|
148
|
+
write('legacy.tsx', 'export function AcmeTab() { return null; }\n');
|
|
149
|
+
const barrel = write(
|
|
150
|
+
'tabs.ts',
|
|
151
|
+
"// export * from './legacy';\nexport function AcmeTabs() { return null; }\n",
|
|
152
|
+
);
|
|
153
|
+
expect(moduleExportSource(barrel, 'AcmeTab')).toBeNull();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('does not count a type-only export', () => {
|
|
157
|
+
const file = write('types.ts', 'export type AcmeShape = {id: string};\n');
|
|
158
|
+
expect(moduleExportSource(file, 'AcmeShape')).toBeNull();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('sees a file npm replaced with one of the same size and mtime', () => {
|
|
162
|
+
// npm stamps every installed file with one mtime and writes new files.
|
|
163
|
+
const npmTime = new Date('1985-10-26T08:15:00Z');
|
|
164
|
+
const file = write('Panel.tsx', 'export function AcmePanel() {}\n');
|
|
165
|
+
fs.utimesSync(file, npmTime, npmTime);
|
|
166
|
+
expect(moduleExportSource(file, 'AcmePanel')).toBe(file);
|
|
167
|
+
const next = write('Panel.next.tsx', 'export function AcmePanol() {}\n');
|
|
168
|
+
fs.utimesSync(next, npmTime, npmTime);
|
|
169
|
+
fs.renameSync(next, file);
|
|
170
|
+
expect(moduleExportSource(file, 'AcmePanel')).toBeNull();
|
|
171
|
+
});
|
|
172
|
+
});
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
* needs it: `Project` assembles templates from it. While this sat in api/, that
|
|
16
16
|
* made a cycle across the layer boundary — Project reached up for discovery and
|
|
17
17
|
* this module imported Project back. Nothing here depends on api/; every import
|
|
18
|
-
* is node:*, jiti, foundation/*, or authoring
|
|
18
|
+
* is node:*, jiti, foundation/*, or authoring/*, plus a lazily required
|
|
19
|
+
* @babel/parser for extractComponents' local-declaration scan.
|
|
19
20
|
*
|
|
20
21
|
* @position packages/cli/foundation/discovery — shared template discovery/IO;
|
|
21
22
|
* consumed by Project and by the api/template leaves.
|
|
@@ -512,6 +513,19 @@ function expressionUse(source, open, close) {
|
|
|
512
513
|
return null;
|
|
513
514
|
}
|
|
514
515
|
|
|
516
|
+
/** @type {typeof import('@babel/parser') | undefined} */
|
|
517
|
+
let babelParser;
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* The template source parser, loaded on first use so listing templates
|
|
521
|
+
* never pays for it.
|
|
522
|
+
* @returns {typeof import('@babel/parser')}
|
|
523
|
+
*/
|
|
524
|
+
function templateSourceParser() {
|
|
525
|
+
babelParser ??= require('@babel/parser');
|
|
526
|
+
return /** @type {typeof import('@babel/parser')} */ (babelParser);
|
|
527
|
+
}
|
|
528
|
+
|
|
515
529
|
/**
|
|
516
530
|
* The [start, end) range of every comment in `source`, or none when it does
|
|
517
531
|
* not parse as TSX.
|
|
@@ -520,8 +534,7 @@ function expressionUse(source, open, close) {
|
|
|
520
534
|
*/
|
|
521
535
|
function commentRanges(source) {
|
|
522
536
|
try {
|
|
523
|
-
const {
|
|
524
|
-
const {comments} = parse(source, {
|
|
537
|
+
const {comments} = templateSourceParser().parse(source, {
|
|
525
538
|
sourceType: 'module',
|
|
526
539
|
plugins: ['jsx', 'typescript'],
|
|
527
540
|
});
|
|
@@ -1267,7 +1280,8 @@ export async function discoverIntegrationTemplatesForOne(integration) {
|
|
|
1267
1280
|
template: id,
|
|
1268
1281
|
message: `Template "${id}" is missing a "type" of "page" or "block". Stamp the default export with type: 'page' or type: 'block'.`,
|
|
1269
1282
|
});
|
|
1270
|
-
const declared =
|
|
1283
|
+
const declared =
|
|
1284
|
+
doc?.replaces ?? (await declaredReplacement(docPath, id));
|
|
1271
1285
|
if (declared != null) {
|
|
1272
1286
|
errors.push({
|
|
1273
1287
|
code: 'invalid_template_replacement',
|
|
@@ -1423,6 +1437,51 @@ const UBIQUITOUS = new Set([
|
|
|
1423
1437
|
'Icon',
|
|
1424
1438
|
]);
|
|
1425
1439
|
|
|
1440
|
+
/**
|
|
1441
|
+
* Names bound by top-level `function`, `class`, `const`, `let` or `var`
|
|
1442
|
+
* declarations (exported or not). Returns an empty set when the source does
|
|
1443
|
+
* not parse, so a parse failure never drops a real component.
|
|
1444
|
+
*
|
|
1445
|
+
* @param {string} src
|
|
1446
|
+
* @returns {Set<string>}
|
|
1447
|
+
*/
|
|
1448
|
+
function topLevelBindingNames(src) {
|
|
1449
|
+
/** @type {Set<string>} */
|
|
1450
|
+
const names = new Set();
|
|
1451
|
+
let program;
|
|
1452
|
+
try {
|
|
1453
|
+
program = templateSourceParser().parse(src, {
|
|
1454
|
+
sourceType: 'module',
|
|
1455
|
+
plugins: ['jsx', 'typescript'],
|
|
1456
|
+
errorRecovery: true,
|
|
1457
|
+
}).program;
|
|
1458
|
+
} catch {
|
|
1459
|
+
return names;
|
|
1460
|
+
}
|
|
1461
|
+
for (const statement of program.body) {
|
|
1462
|
+
const node =
|
|
1463
|
+
statement.type === 'ExportNamedDeclaration' ||
|
|
1464
|
+
statement.type === 'ExportDefaultDeclaration'
|
|
1465
|
+
? statement.declaration
|
|
1466
|
+
: statement;
|
|
1467
|
+
if (node == null) continue;
|
|
1468
|
+
if (
|
|
1469
|
+
(node.type === 'FunctionDeclaration' ||
|
|
1470
|
+
node.type === 'ClassDeclaration') &&
|
|
1471
|
+
node.id
|
|
1472
|
+
) {
|
|
1473
|
+
names.add(node.id.name);
|
|
1474
|
+
} else if (node.type === 'VariableDeclaration') {
|
|
1475
|
+
for (const declarator of node.declarations) {
|
|
1476
|
+
if (declarator.id.type === 'Identifier') {
|
|
1477
|
+
names.add(declarator.id.name);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
return names;
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1426
1485
|
/**
|
|
1427
1486
|
* @param {string} pagePath
|
|
1428
1487
|
* @returns {string[]}
|
|
@@ -1449,14 +1508,24 @@ export function extractComponents(pagePath) {
|
|
|
1449
1508
|
while ((m = tagRegex.exec(src)) !== null) {
|
|
1450
1509
|
matches.push(m[2]);
|
|
1451
1510
|
}
|
|
1511
|
+
|
|
1512
|
+
// Exclude names bound by a top-level declaration in the same file: those are
|
|
1513
|
+
// page-local helpers (e.g. `function TimelineSection()`), not components a
|
|
1514
|
+
// builder can import. Read real bindings from the syntax tree so comments,
|
|
1515
|
+
// strings and type annotations cannot add or hide a name.
|
|
1516
|
+
const localDeclNames = topLevelBindingNames(src);
|
|
1517
|
+
|
|
1452
1518
|
return [
|
|
1453
1519
|
...new Set(
|
|
1454
1520
|
matches
|
|
1521
|
+
.filter(n => !localDeclNames.has(n))
|
|
1455
1522
|
.filter(n => !['Theme', 'ThemeProvider'].includes(n))
|
|
1456
1523
|
.filter(n => !UBIQUITOUS.has(n))
|
|
1457
1524
|
.map(n =>
|
|
1458
1525
|
n.replace(
|
|
1459
|
-
|
|
1526
|
+
// Strip only when a stem remains, so a bare `<Section>` keeps its
|
|
1527
|
+
// own name instead of emptying out.
|
|
1528
|
+
/(?<=.)(Item|Section|Header|Content|Footer|Panel|Heading|CollapseButton|Column|Sortable|Selection|Group|Source)$/,
|
|
1460
1529
|
'',
|
|
1461
1530
|
),
|
|
1462
1531
|
)
|
|
@@ -246,12 +246,16 @@ const RUNNERS = {
|
|
|
246
246
|
runs: 'astryx.config, through the config parser',
|
|
247
247
|
sites: ['loadModuleWithParser(..., parseConfig)'],
|
|
248
248
|
},
|
|
249
|
+
'foundation/discovery/component-discovery.mjs': {
|
|
250
|
+
runs: '@babel/parser, on first use, to read the members a family doc lists in its components without running the doc',
|
|
251
|
+
sites: ['require(@babel/parser)'],
|
|
252
|
+
},
|
|
249
253
|
'foundation/discovery/docs-discovery.mjs': {
|
|
250
254
|
runs: 'a contributed topic, for its catalog fields: name, title, description, category, and relationships',
|
|
251
255
|
sites: ['importDocModule'],
|
|
252
256
|
},
|
|
253
257
|
'foundation/discovery/template-adapter.mjs': {
|
|
254
|
-
runs: '@babel/parser, to find the fixture references in template source; and a template doc that does not validate, to read the replacement it declares',
|
|
258
|
+
runs: '@babel/parser, to find the fixture references and page-local bindings in template source; and a template doc that does not validate, to read the replacement it declares',
|
|
255
259
|
sites: ['importDocModule', 'require(@babel/parser)'],
|
|
256
260
|
},
|
|
257
261
|
'foundation/discovery/theme-discovery.mjs': {
|
|
@@ -81,8 +81,31 @@ const NON_DESCRIPTOR_DIRS = new Set([
|
|
|
81
81
|
export async function collectDocInputs(project) {
|
|
82
82
|
const list = new InputList();
|
|
83
83
|
|
|
84
|
+
// A component documented as a member of its family's doc (HStack and VStack
|
|
85
|
+
// in Stack's doc) is read through the family's own entry: one descriptor,
|
|
86
|
+
// one reader. Own docs go first so the family keeps its name.
|
|
87
|
+
/** @type {Array<{name: string, package: string, docPath: string}>} */
|
|
88
|
+
const ownDocs = [];
|
|
89
|
+
/** @type {Array<{name: string, package: string, docPath: string}>} */
|
|
90
|
+
const familyDocs = [];
|
|
84
91
|
for (const record of await project.components()) {
|
|
85
92
|
if (record.docPath == null) continue;
|
|
93
|
+
const stem = path
|
|
94
|
+
.basename(record.docPath)
|
|
95
|
+
.replace(/\.doc\.[cm]?[jt]s$/u, '')
|
|
96
|
+
.replace(/^XDS/u, '');
|
|
97
|
+
const entry = {
|
|
98
|
+
name: record.name,
|
|
99
|
+
package: record.package,
|
|
100
|
+
docPath: record.docPath,
|
|
101
|
+
};
|
|
102
|
+
(stem === record.name ? ownDocs : familyDocs).push(entry);
|
|
103
|
+
}
|
|
104
|
+
for (const record of ownDocs) {
|
|
105
|
+
list.add('components', record.name, record.package, record.docPath);
|
|
106
|
+
}
|
|
107
|
+
for (const record of familyDocs) {
|
|
108
|
+
if (list.claims(record.docPath)) continue;
|
|
86
109
|
list.add('components', record.name, record.package, record.docPath);
|
|
87
110
|
}
|
|
88
111
|
|
|
@@ -20,6 +20,7 @@ import path from 'node:path';
|
|
|
20
20
|
import {findCoreDir} from '../fs/paths.mjs';
|
|
21
21
|
import {
|
|
22
22
|
discoverComponents,
|
|
23
|
+
familyMember,
|
|
23
24
|
findComponentReadme,
|
|
24
25
|
resolveImportPath,
|
|
25
26
|
} from '../discovery/component-discovery.mjs';
|
|
@@ -126,6 +127,9 @@ export async function buildRegistry({cwd = process.cwd()} = {}) {
|
|
|
126
127
|
} catch {
|
|
127
128
|
continue; // a malformed doc must not take down the whole language
|
|
128
129
|
}
|
|
130
|
+
// A member documented in its family's doc (VStack in Stack's doc) is
|
|
131
|
+
// registered by the family's own folder, with every other member.
|
|
132
|
+
if (familyMember(docs, dirName)) continue;
|
|
129
133
|
const importPath = resolveImportPath(coreDir, dirName);
|
|
130
134
|
|
|
131
135
|
const register = (/** @type {string} */ rawName, /** @type {import('./xle-ast').DocProp[]} */ props) => {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file The layout-language registry over the real @astryxdesign/core tree:
|
|
5
|
+
* the import path it emits for each component.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {describe, it, expect} from 'vitest';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
import {fileURLToPath} from 'node:url';
|
|
11
|
+
import {buildRegistry} from './registry.mjs';
|
|
12
|
+
|
|
13
|
+
const REPO = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../..');
|
|
14
|
+
|
|
15
|
+
describe('buildRegistry import paths', () => {
|
|
16
|
+
it('keeps the Stack family on the paths it registers from Stack\'s folder', async () => {
|
|
17
|
+
// HStack and VStack are listed components whose docs live in Stack's doc.
|
|
18
|
+
// Listing them must not let their folders re-register the family under
|
|
19
|
+
// their own paths: a member's folder registers nothing, Stack's does.
|
|
20
|
+
const {components} = await buildRegistry({cwd: REPO});
|
|
21
|
+
const importOf = (/** @type {string} */ name) => components.get(name)?.importPath;
|
|
22
|
+
for (const name of ['Stack', 'StackItem', 'HStack', 'VStack']) {
|
|
23
|
+
expect(importOf(name), name).toBe('@astryxdesign/core/Stack');
|
|
24
|
+
}
|
|
25
|
+
expect(importOf('Heading')).toBe('@astryxdesign/core/Heading');
|
|
26
|
+
expect(importOf('Text')).toBe('@astryxdesign/core/Text');
|
|
27
|
+
}, 120_000);
|
|
28
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/cli",
|
|
3
|
-
"version": "0.7.2-canary.
|
|
3
|
+
"version": "0.7.2-canary.ebcc404",
|
|
4
4
|
"displayName": "CLI",
|
|
5
5
|
"description": "Scaffold projects, browse templates, generate themes, and get agent-ready docs from the command line.",
|
|
6
6
|
"author": "Meta Open Source",
|
|
@@ -96,15 +96,15 @@
|
|
|
96
96
|
"jscodeshift": "^17.3.0",
|
|
97
97
|
"node-diff3": "3.2.1",
|
|
98
98
|
"postcss": "^8.5.23",
|
|
99
|
-
"postcss-selector-parser": "^7.1.
|
|
99
|
+
"postcss-selector-parser": "^7.1.6",
|
|
100
100
|
"recast": "0.23.21",
|
|
101
101
|
"zod": "^4.4.3"
|
|
102
102
|
},
|
|
103
103
|
"peerDependencies": {
|
|
104
|
-
"@astryxdesign/charts": "0.7.2-canary.
|
|
105
|
-
"@astryxdesign/core": "0.7.2-canary.
|
|
106
|
-
"@astryxdesign/lab": "0.7.2-canary.
|
|
107
|
-
"@astryxdesign/theme-neutral": "0.7.2-canary.
|
|
104
|
+
"@astryxdesign/charts": "0.7.2-canary.ebcc404",
|
|
105
|
+
"@astryxdesign/core": "0.7.2-canary.ebcc404",
|
|
106
|
+
"@astryxdesign/lab": "0.7.2-canary.ebcc404",
|
|
107
|
+
"@astryxdesign/theme-neutral": "0.7.2-canary.ebcc404"
|
|
108
108
|
},
|
|
109
109
|
"peerDependenciesMeta": {
|
|
110
110
|
"@astryxdesign/charts": {
|
|
@@ -121,10 +121,10 @@
|
|
|
121
121
|
}
|
|
122
122
|
},
|
|
123
123
|
"devDependencies": {
|
|
124
|
-
"@astryxdesign/charts": "0.7.2-canary.
|
|
125
|
-
"@astryxdesign/core": "0.7.2-canary.
|
|
126
|
-
"@astryxdesign/lab": "0.7.2-canary.
|
|
127
|
-
"@astryxdesign/theme-neutral": "0.7.2-canary.
|
|
124
|
+
"@astryxdesign/charts": "0.7.2-canary.ebcc404",
|
|
125
|
+
"@astryxdesign/core": "0.7.2-canary.ebcc404",
|
|
126
|
+
"@astryxdesign/lab": "0.7.2-canary.ebcc404",
|
|
127
|
+
"@astryxdesign/theme-neutral": "0.7.2-canary.ebcc404",
|
|
128
128
|
"@heroicons/react": "^2.2.0",
|
|
129
129
|
"@stylexjs/stylex": "^0.19.0",
|
|
130
130
|
"@types/babel__core": "^7.20.5",
|