@astryxdesign/cli 0.4.5-canary.5d8ece1 → 0.4.5-canary.6f36dda
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.
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* @property {any[]} [components]
|
|
13
13
|
* @property {{description?: string}} [usage]
|
|
14
14
|
* @property {any} [theming]
|
|
15
|
+
* @property {string} [import] set when the doc states its own import specifier
|
|
15
16
|
*/
|
|
16
17
|
/**
|
|
17
18
|
* Options object for `loadDocs`, matching its declared parameter shape (used
|
|
@@ -29,6 +30,12 @@
|
|
|
29
30
|
* @property {string|undefined} issuesUrl
|
|
30
31
|
* @property {import('../../foundation/integrations/integrations.mjs').LoadedIntegration|null} integration
|
|
31
32
|
*/
|
|
33
|
+
/**
|
|
34
|
+
* What ownership shaping needs from an owner. The core and legacy-external
|
|
35
|
+
* paths synthesize a bare `{package, sourcePath}` rather than resolving a full
|
|
36
|
+
* {@link ComponentOwner}, so everything past those two is optional here.
|
|
37
|
+
* @typedef {Partial<ComponentOwner> & {package: string, sourcePath: string|null}} OwnershipSubject
|
|
38
|
+
*/
|
|
32
39
|
/**
|
|
33
40
|
* A back-compat external package discovered via `pkg.astryx.docs`.
|
|
34
41
|
* @typedef {{name: string, category: string, docsDir: string}} ExternalPackageRef
|
|
@@ -152,15 +159,12 @@ export function extractProps(docs: LoadedComponentDoc): any[];
|
|
|
152
159
|
* swizzleable source file exists for the owner). Existing doc fields (name,
|
|
153
160
|
* usage, props, …) are preserved.
|
|
154
161
|
* @param {LoadedComponentDoc} docs
|
|
155
|
-
* @param {
|
|
162
|
+
* @param {OwnershipSubject} owner
|
|
156
163
|
* @param {string} componentName
|
|
157
164
|
* @param {string} coreDir
|
|
158
165
|
* @returns {import('./component.type.mjs').ComponentDetailResponse['data']}
|
|
159
166
|
*/
|
|
160
|
-
export function withOwnership(docs: LoadedComponentDoc, owner:
|
|
161
|
-
package: string;
|
|
162
|
-
sourcePath: string | null;
|
|
163
|
-
}, componentName: string, coreDir: string): import("./component.type.mjs").ComponentDetailResponse["data"];
|
|
167
|
+
export function withOwnership(docs: LoadedComponentDoc, owner: OwnershipSubject, componentName: string, coreDir: string): import("./component.type.mjs").ComponentDetailResponse["data"];
|
|
164
168
|
/**
|
|
165
169
|
* When the caller asked for "Code" but the resolved doc is for "CodeBlock"
|
|
166
170
|
* (parent), scope the response to just the matching sub-component. Returns the
|
|
@@ -193,6 +197,10 @@ export type LoadedComponentDoc = {
|
|
|
193
197
|
description?: string;
|
|
194
198
|
} | undefined;
|
|
195
199
|
theming?: any;
|
|
200
|
+
/**
|
|
201
|
+
* set when the doc states its own import specifier
|
|
202
|
+
*/
|
|
203
|
+
import?: string | undefined;
|
|
196
204
|
};
|
|
197
205
|
/**
|
|
198
206
|
* Options object for `loadDocs`, matching its declared parameter shape (used
|
|
@@ -215,6 +223,15 @@ export type ComponentOwner = {
|
|
|
215
223
|
issuesUrl: string | undefined;
|
|
216
224
|
integration: import("../../foundation/integrations/integrations.mjs").LoadedIntegration | null;
|
|
217
225
|
};
|
|
226
|
+
/**
|
|
227
|
+
* What ownership shaping needs from an owner. The core and legacy-external
|
|
228
|
+
* paths synthesize a bare `{package, sourcePath}` rather than resolving a full
|
|
229
|
+
* {@link ComponentOwner}, so everything past those two is optional here.
|
|
230
|
+
*/
|
|
231
|
+
export type OwnershipSubject = Partial<ComponentOwner> & {
|
|
232
|
+
package: string;
|
|
233
|
+
sourcePath: string | null;
|
|
234
|
+
};
|
|
218
235
|
/**
|
|
219
236
|
* A back-compat external package discovered via `pkg.astryx.docs`.
|
|
220
237
|
*/
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
* deduped, so each leaf stays a thin projection.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
+
import * as fs from 'node:fs';
|
|
22
|
+
import * as path from 'node:path';
|
|
21
23
|
import {ERROR_CODES} from '../../foundation/response/error-codes.mjs';
|
|
22
24
|
import {findCoreDir, discoverExternalPackages} from '../../foundation/fs/paths.mjs';
|
|
23
25
|
import {
|
|
@@ -48,6 +50,7 @@ export {CORE_PACKAGE};
|
|
|
48
50
|
* @property {any[]} [components]
|
|
49
51
|
* @property {{description?: string}} [usage]
|
|
50
52
|
* @property {any} [theming]
|
|
53
|
+
* @property {string} [import] set when the doc states its own import specifier
|
|
51
54
|
*/
|
|
52
55
|
|
|
53
56
|
/**
|
|
@@ -68,6 +71,13 @@ export {CORE_PACKAGE};
|
|
|
68
71
|
* @property {import('../../foundation/integrations/integrations.mjs').LoadedIntegration|null} integration
|
|
69
72
|
*/
|
|
70
73
|
|
|
74
|
+
/**
|
|
75
|
+
* What ownership shaping needs from an owner. The core and legacy-external
|
|
76
|
+
* paths synthesize a bare `{package, sourcePath}` rather than resolving a full
|
|
77
|
+
* {@link ComponentOwner}, so everything past those two is optional here.
|
|
78
|
+
* @typedef {Partial<ComponentOwner> & {package: string, sourcePath: string|null}} OwnershipSubject
|
|
79
|
+
*/
|
|
80
|
+
|
|
71
81
|
/**
|
|
72
82
|
* A back-compat external package discovered via `pkg.astryx.docs`.
|
|
73
83
|
* @typedef {{name: string, category: string, docsDir: string}} ExternalPackageRef
|
|
@@ -357,7 +367,7 @@ export function extractProps(docs) {
|
|
|
357
367
|
* swizzleable source file exists for the owner). Existing doc fields (name,
|
|
358
368
|
* usage, props, …) are preserved.
|
|
359
369
|
* @param {LoadedComponentDoc} docs
|
|
360
|
-
* @param {
|
|
370
|
+
* @param {OwnershipSubject} owner
|
|
361
371
|
* @param {string} componentName
|
|
362
372
|
* @param {string} coreDir
|
|
363
373
|
* @returns {import('./component.type.mjs').ComponentDetailResponse['data']}
|
|
@@ -366,15 +376,53 @@ export function withOwnership(docs, owner, componentName, coreDir) {
|
|
|
366
376
|
const importSpec =
|
|
367
377
|
owner.package === CORE_PACKAGE
|
|
368
378
|
? resolveImportPath(coreDir, componentName)
|
|
369
|
-
:
|
|
379
|
+
: resolveIntegrationImportPath(owner, componentName);
|
|
370
380
|
return /** @type {any} */ ({
|
|
371
381
|
...docs,
|
|
372
382
|
package: owner.package,
|
|
373
|
-
|
|
383
|
+
// A doc file may state its own specifier, e.g. when one entry point exports
|
|
384
|
+
// several components. Only fall back to a resolved one when it does not.
|
|
385
|
+
import: docs.import ?? importSpec,
|
|
374
386
|
sourceAvailable: owner.sourcePath != null,
|
|
375
387
|
});
|
|
376
388
|
}
|
|
377
389
|
|
|
390
|
+
/**
|
|
391
|
+
* Resolve the specifier an integration component is imported from, against the
|
|
392
|
+
* owning package's `exports` map.
|
|
393
|
+
*
|
|
394
|
+
* A component lives in a directory that need not share its name — several
|
|
395
|
+
* components can be exported from one entry point — so the specifier has to
|
|
396
|
+
* come from the directory the doc file sits in, checked against `exports`,
|
|
397
|
+
* rather than from the component name. Falls back to the package root when the
|
|
398
|
+
* directory is not an exported subpath, matching what a consumer would have to
|
|
399
|
+
* write by hand.
|
|
400
|
+
*
|
|
401
|
+
* @param {OwnershipSubject} owner
|
|
402
|
+
* @param {string} componentName
|
|
403
|
+
* @returns {string}
|
|
404
|
+
*/
|
|
405
|
+
function resolveIntegrationImportPath(owner, componentName) {
|
|
406
|
+
const packageDir = owner.integration?.__packageDir;
|
|
407
|
+
const directory = owner.docPath
|
|
408
|
+
? path.basename(path.dirname(owner.docPath))
|
|
409
|
+
: componentName;
|
|
410
|
+
if (!packageDir) {
|
|
411
|
+
return owner.package;
|
|
412
|
+
}
|
|
413
|
+
try {
|
|
414
|
+
const manifest = JSON.parse(
|
|
415
|
+
fs.readFileSync(path.join(packageDir, 'package.json'), 'utf-8'),
|
|
416
|
+
);
|
|
417
|
+
if (manifest.exports?.[`./${directory}`]) {
|
|
418
|
+
return `${owner.package}/${directory}`;
|
|
419
|
+
}
|
|
420
|
+
} catch {
|
|
421
|
+
// An unreadable or malformed manifest is not worth failing a lookup over.
|
|
422
|
+
}
|
|
423
|
+
return owner.package;
|
|
424
|
+
}
|
|
425
|
+
|
|
378
426
|
/**
|
|
379
427
|
* When the caller asked for "Code" but the resolved doc is for "CodeBlock"
|
|
380
428
|
* (parent), scope the response to just the matching sub-component. Returns the
|
|
@@ -47,7 +47,12 @@ const INTEGRATION_ISSUES = 'https://example.com/meta/issues';
|
|
|
47
47
|
* Returns the absolute `components` dir so the Project.load mock can hand back a
|
|
48
48
|
* resolved integration entry.
|
|
49
49
|
*/
|
|
50
|
-
function createFixture({
|
|
50
|
+
function createFixture({
|
|
51
|
+
withSource = true,
|
|
52
|
+
extraComponent = null,
|
|
53
|
+
packageExports = null,
|
|
54
|
+
entryPoint = null,
|
|
55
|
+
} = {}) {
|
|
51
56
|
const realCoreDir = path.resolve(import.meta.dirname, '..', '..', '..', '..', 'core');
|
|
52
57
|
const coreDir = path.join(tmpDir, 'packages', 'core');
|
|
53
58
|
fs.mkdirSync(path.dirname(coreDir), {recursive: true});
|
|
@@ -58,7 +63,11 @@ function createFixture({withSource = true, extraComponent = null} = {}) {
|
|
|
58
63
|
fs.mkdirSync(compDir, {recursive: true});
|
|
59
64
|
fs.writeFileSync(
|
|
60
65
|
path.join(intDir, 'package.json'),
|
|
61
|
-
JSON.stringify({
|
|
66
|
+
JSON.stringify({
|
|
67
|
+
name: INTEGRATION_NAME,
|
|
68
|
+
version: '1.2.3',
|
|
69
|
+
...(packageExports ? {exports: packageExports} : {}),
|
|
70
|
+
}),
|
|
62
71
|
);
|
|
63
72
|
fs.writeFileSync(
|
|
64
73
|
path.join(compDir, 'MetaAppShell.doc.mjs'),
|
|
@@ -81,6 +90,20 @@ function createFixture({withSource = true, extraComponent = null} = {}) {
|
|
|
81
90
|
);
|
|
82
91
|
}
|
|
83
92
|
|
|
93
|
+
// A component whose directory is an entry point exporting several
|
|
94
|
+
// components, so the directory name and the component name differ.
|
|
95
|
+
if (entryPoint) {
|
|
96
|
+
const entryDir = path.join(compDir, entryPoint.directory);
|
|
97
|
+
fs.mkdirSync(entryDir, {recursive: true});
|
|
98
|
+
const ownSpecifier = entryPoint.importSpec
|
|
99
|
+
? `\n import: '${entryPoint.importSpec}',`
|
|
100
|
+
: '';
|
|
101
|
+
fs.writeFileSync(
|
|
102
|
+
path.join(entryDir, `${entryPoint.component}.doc.mjs`),
|
|
103
|
+
`export const docs = {\n name: '${entryPoint.component}',${ownSpecifier}\n usage: { description: '${entryPoint.component} from an entry point.' },\n};\n`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
84
107
|
const integration = {
|
|
85
108
|
name: INTEGRATION_NAME,
|
|
86
109
|
version: '1.2.3',
|
|
@@ -88,6 +111,7 @@ function createFixture({withSource = true, extraComponent = null} = {}) {
|
|
|
88
111
|
templates: undefined,
|
|
89
112
|
codemods: undefined,
|
|
90
113
|
issuesUrl: INTEGRATION_ISSUES,
|
|
114
|
+
__packageDir: intDir,
|
|
91
115
|
};
|
|
92
116
|
projectLoadMock.mockResolvedValue({
|
|
93
117
|
integrations: [INTEGRATION_NAME],
|
|
@@ -162,7 +186,9 @@ describe('component() — integration ownership via config', () => {
|
|
|
162
186
|
expect(result.data.name).toBe('MetaAppShell');
|
|
163
187
|
expect(result.data.package).toBe(INTEGRATION_NAME);
|
|
164
188
|
expect(result.data.sourceAvailable).toBe(true);
|
|
165
|
-
|
|
189
|
+
// This fixture declares no `exports`, so there is no subpath to import
|
|
190
|
+
// from and the specifier is the package root.
|
|
191
|
+
expect(result.data.import).toBe(INTEGRATION_NAME);
|
|
166
192
|
});
|
|
167
193
|
|
|
168
194
|
it('--package resolves the integration component', async () => {
|
|
@@ -217,6 +243,40 @@ describe('component() — integration ownership via config', () => {
|
|
|
217
243
|
expect(result.data.package).toBe(INTEGRATION_NAME);
|
|
218
244
|
});
|
|
219
245
|
|
|
246
|
+
it('resolves the import specifier against the package exports map', async () => {
|
|
247
|
+
// The doc sits in a `Toolbar` directory but the component is
|
|
248
|
+
// `ToolbarSearch`, so a specifier built from the component name would
|
|
249
|
+
// point at a subpath the package does not export.
|
|
250
|
+
createFixture({
|
|
251
|
+
packageExports: {'.': './index.js', './Toolbar': './components/Toolbar/index.js'},
|
|
252
|
+
entryPoint: {directory: 'Toolbar', component: 'ToolbarSearch'},
|
|
253
|
+
});
|
|
254
|
+
const result = await component('ToolbarSearch', {cwd: tmpDir});
|
|
255
|
+
expect(result.data.import).toBe(`${INTEGRATION_NAME}/Toolbar`);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('falls back to the package root when the directory is not an exported subpath', async () => {
|
|
259
|
+
createFixture({
|
|
260
|
+
packageExports: {'.': './index.js'},
|
|
261
|
+
entryPoint: {directory: 'Toolbar', component: 'ToolbarSearch'},
|
|
262
|
+
});
|
|
263
|
+
const result = await component('ToolbarSearch', {cwd: tmpDir});
|
|
264
|
+
expect(result.data.import).toBe(INTEGRATION_NAME);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('keeps a specifier the doc file states for itself', async () => {
|
|
268
|
+
createFixture({
|
|
269
|
+
packageExports: {'.': './index.js', './Toolbar': './components/Toolbar/index.js'},
|
|
270
|
+
entryPoint: {
|
|
271
|
+
directory: 'Toolbar',
|
|
272
|
+
component: 'ToolbarSearch',
|
|
273
|
+
importSpec: `${INTEGRATION_NAME}/Toolbar/Search`,
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
const result = await component('ToolbarSearch', {cwd: tmpDir});
|
|
277
|
+
expect(result.data.import).toBe(`${INTEGRATION_NAME}/Toolbar/Search`);
|
|
278
|
+
});
|
|
279
|
+
|
|
220
280
|
it('JSON list includes integration components as {name, package} objects', async () => {
|
|
221
281
|
createFixture();
|
|
222
282
|
const result = await component(undefined, {cwd: tmpDir, list: true});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/cli",
|
|
3
|
-
"version": "0.4.5-canary.
|
|
3
|
+
"version": "0.4.5-canary.6f36dda",
|
|
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",
|
|
@@ -87,10 +87,10 @@
|
|
|
87
87
|
"zod": "^4.4.3"
|
|
88
88
|
},
|
|
89
89
|
"peerDependencies": {
|
|
90
|
-
"@astryxdesign/charts": "0.4.5-canary.
|
|
91
|
-
"@astryxdesign/core": "0.4.5-canary.
|
|
92
|
-
"@astryxdesign/lab": "0.4.5-canary.
|
|
93
|
-
"@astryxdesign/theme-neutral": "0.4.5-canary.
|
|
90
|
+
"@astryxdesign/charts": "0.4.5-canary.6f36dda",
|
|
91
|
+
"@astryxdesign/core": "0.4.5-canary.6f36dda",
|
|
92
|
+
"@astryxdesign/lab": "0.4.5-canary.6f36dda",
|
|
93
|
+
"@astryxdesign/theme-neutral": "0.4.5-canary.6f36dda",
|
|
94
94
|
"gpt-tokenizer": "^3.4.0"
|
|
95
95
|
},
|
|
96
96
|
"peerDependenciesMeta": {
|
|
@@ -108,10 +108,10 @@
|
|
|
108
108
|
}
|
|
109
109
|
},
|
|
110
110
|
"devDependencies": {
|
|
111
|
-
"@astryxdesign/charts": "0.4.5-canary.
|
|
112
|
-
"@astryxdesign/core": "0.4.5-canary.
|
|
113
|
-
"@astryxdesign/lab": "0.4.5-canary.
|
|
114
|
-
"@astryxdesign/theme-neutral": "0.4.5-canary.
|
|
111
|
+
"@astryxdesign/charts": "0.4.5-canary.6f36dda",
|
|
112
|
+
"@astryxdesign/core": "0.4.5-canary.6f36dda",
|
|
113
|
+
"@astryxdesign/lab": "0.4.5-canary.6f36dda",
|
|
114
|
+
"@astryxdesign/theme-neutral": "0.4.5-canary.6f36dda",
|
|
115
115
|
"gpt-tokenizer": "^3.4.0"
|
|
116
116
|
},
|
|
117
117
|
"scripts": {
|