@astryxdesign/cli 0.1.2-canary.b712653 → 0.1.2-canary.bbf77ea
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/CHANGELOG.md +0 -1
- package/README.md +49 -55
- package/docs/theme.doc.mjs +2 -2
- package/package.json +19 -7
- package/src/api/component.mjs +253 -12
- package/src/api/discover.mjs +14 -7
- package/src/api/doctor.mjs +10 -25
- package/src/api/layout.mjs +11 -10
- package/src/api/layout.test.mjs +4 -1
- package/src/api/template-integration.test.mjs +225 -0
- package/src/api/template.mjs +242 -84
- package/src/api/validate-integration.mjs +370 -0
- package/src/api/validate-integration.test.mjs +222 -0
- package/src/codemod.mjs +93 -0
- package/src/codemod.test.mjs +134 -0
- package/src/codemods/__tests__/registry.test.mjs +1 -0
- package/src/codemods/__tests__/runner.test.mjs +103 -0
- package/src/codemods/integration-discovery.mjs +168 -0
- package/src/codemods/integration-discovery.test.mjs +234 -0
- package/src/codemods/integration-runner.mjs +109 -0
- package/src/codemods/registry.mjs +1 -0
- package/src/codemods/run-codemod.mjs +207 -0
- package/src/codemods/runner.mjs +73 -96
- package/src/codemods/transforms/v0.0.15/index.mjs +0 -13
- package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-css-surfaces.test.mjs +67 -0
- package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-declare-module.test.mjs +61 -0
- package/src/codemods/transforms/v0.1.0/__tests__/v0.1.0-ordering.test.mjs +104 -0
- package/src/codemods/transforms/{v0.0.15 → v0.1.0}/drop-xds-prefix-imports.mjs +15 -4
- package/src/codemods/transforms/v0.1.0/index.mjs +43 -6
- package/src/codemods/transforms/v0.1.0/migrate-xds-css-surfaces.mjs +77 -0
- package/src/codemods/transforms/v0.1.0/migrate-xds-declare-module.mjs +78 -0
- package/src/codemods/transforms/v0.1.3/__tests__/migrate-layout-components-to-experimental.test.mjs +360 -0
- package/src/codemods/transforms/v0.1.3/index.mjs +19 -0
- package/src/codemods/transforms/v0.1.3/migrate-layout-components-to-experimental.mjs +260 -0
- package/src/commands/agent-docs.mjs +1 -1
- package/src/commands/component/index.mjs +44 -10
- package/src/commands/component-ownership.test.mjs +227 -0
- package/src/commands/discover.mjs +5 -16
- package/src/commands/doctor.test.mjs +3 -3
- package/src/commands/json-contract.test.mjs +0 -32
- package/src/commands/swizzle.mjs +224 -235
- package/src/commands/swizzle.path-safety.test.mjs +3 -3
- package/src/commands/swizzle.routing.test.mjs +279 -0
- package/src/commands/template.mjs +33 -37
- package/src/commands/upgrade.config-ordering.test.mjs +273 -0
- package/src/commands/upgrade.integration-policy.test.mjs +204 -0
- package/src/commands/upgrade.mjs +349 -170
- package/src/commands/validate-integration.mjs +110 -0
- package/src/commands/validate-integration.test.mjs +124 -0
- package/src/config.mjs +7 -20
- package/src/config.test.mjs +89 -13
- package/src/index.mjs +7 -3
- package/src/integration.mjs +19 -0
- package/src/lib/component-discovery.mjs +177 -0
- package/src/lib/config-cache.mjs +91 -0
- package/src/lib/config-cache.test.mjs +83 -0
- package/src/lib/config-schema.mjs +41 -55
- package/src/lib/error-codes.mjs +7 -12
- package/src/lib/integration-warnings.mjs +62 -0
- package/src/lib/integration-warnings.test.mjs +102 -0
- package/src/lib/integrations.mjs +92 -115
- package/src/lib/integrations.test.mjs +88 -107
- package/src/lib/manifest.mjs +5 -2
- package/src/lib/module-loader.mjs +80 -0
- package/src/lib/module-loader.test.mjs +106 -0
- package/src/lib/project.mjs +502 -0
- package/src/lib/project.test.mjs +308 -0
- package/src/template.mjs +73 -0
- package/src/template.test.mjs +127 -0
- package/src/types/api.d.ts +2 -6
- package/src/types/base.d.ts +3 -9
- package/src/types/codemod.d.ts +81 -0
- package/src/types/component.d.ts +27 -2
- package/src/types/config.d.ts +56 -85
- package/src/types/error-codes.d.ts +5 -3
- package/src/types/index.d.ts +0 -1
- package/src/types/integration.d.ts +29 -0
- package/src/types/swizzle.d.ts +9 -2
- package/src/types/template-api.d.ts +54 -0
- package/src/types/template.d.ts +10 -7
- package/src/types/upgrade.d.ts +29 -0
- package/src/types/validate-integration.d.ts +24 -0
- package/src/utils/github.mjs +0 -237
- package/src/utils/interactive.mjs +2 -2
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithPresets.doc.mjs +14 -0
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithPresets.tsx +46 -0
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithValidation.doc.mjs +14 -0
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithValidation.tsx +52 -0
- package/templates/blocks/components/DateTimeInput/DateTimeInputWithValidation.doc.mjs +14 -0
- package/templates/blocks/components/DateTimeInput/DateTimeInputWithValidation.tsx +43 -0
- package/templates/blocks/components/DropdownMenu/DropdownMenuShowcase.tsx +0 -1
- package/templates/blocks/components/DropdownMenuItem/DropdownMenuItemShowcase.tsx +0 -1
- package/templates/blocks/components/HoverCard/HoverCardInteractiveContent.doc.mjs +1 -1
- package/templates/blocks/components/HoverCard/HoverCardInteractiveContent.tsx +9 -4
- package/templates/blocks/components/LinkProvider/LinkProviderCustomLink.doc.mjs +1 -1
- package/templates/blocks/components/MoreMenu/MoreMenuShowcase.tsx +0 -1
- package/templates/blocks/components/Tab/TabWithSelectedIcon.doc.mjs +13 -0
- package/templates/blocks/components/Tab/TabWithSelectedIcon.tsx +39 -0
- package/templates/blocks/components/Table/StickyColumnsHookUsage.doc.mjs +1 -1
- package/templates/pages/shell-side-nav/page.tsx +0 -1
- package/templates/pages/shell-side-nav/template.doc.mjs +1 -1
- package/templates/pages/shell-top-nav/template.doc.mjs +1 -1
- package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-config-surfaces.test.mjs +0 -116
- package/src/codemods/transforms/v0.1.0/migrate-xds-config-surfaces.mjs +0 -230
- package/src/commands/gap-report.mjs +0 -464
- package/src/commands/gap-report.test.mjs +0 -168
- package/src/commands/swizzle-gap-safety.test.mjs +0 -273
- package/src/lib/config.mjs +0 -113
- package/src/lib/config.test.mjs +0 -91
- package/src/types/gap-report.d.ts +0 -29
- /package/src/codemods/transforms/{v0.0.15 → v0.1.0}/__tests__/drop-xds-prefix-imports.test.mjs +0 -0
package/src/api/discover.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @file Programmatic API for the discover command.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {Project} from '../lib/project.mjs';
|
|
8
8
|
import {
|
|
9
9
|
scanAllPackages,
|
|
10
10
|
findComponentInPackages,
|
|
@@ -40,7 +40,7 @@ function validateDocs(docs) {
|
|
|
40
40
|
*/
|
|
41
41
|
export async function discover(query, options = {}) {
|
|
42
42
|
const {lang = null, zh = false} = options;
|
|
43
|
-
const
|
|
43
|
+
const project = await Project.load();
|
|
44
44
|
const toEntry = pkg => ({
|
|
45
45
|
name: pkg.name,
|
|
46
46
|
category: pkg.category,
|
|
@@ -50,14 +50,21 @@ export async function discover(query, options = {}) {
|
|
|
50
50
|
displayName: pkg.displayName,
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
// External packages come from configured integrations that declare a
|
|
54
|
+
// components root. Each becomes a scannable package keyed by its docsDir.
|
|
55
|
+
const explicitPackages = project.loadedIntegrations
|
|
56
|
+
.filter(integration => integration.components)
|
|
57
|
+
.map(integration => ({
|
|
58
|
+
name: integration.name,
|
|
59
|
+
version: integration.version,
|
|
60
|
+
category: integration.name,
|
|
61
|
+
docsDir: integration.components,
|
|
62
|
+
}));
|
|
63
|
+
if (explicitPackages.length === 0) {
|
|
57
64
|
return {type: 'discover.list', data: [], meta: {configured: false}};
|
|
58
65
|
}
|
|
59
66
|
|
|
60
|
-
const packages = scanAllPackages(
|
|
67
|
+
const packages = scanAllPackages([], explicitPackages);
|
|
61
68
|
|
|
62
69
|
if (packages.length === 0) {
|
|
63
70
|
return {type: 'discover.list', data: [], meta: {configured: true}};
|
package/src/api/doctor.mjs
CHANGED
|
@@ -27,7 +27,7 @@ import {createRequire} from 'node:module';
|
|
|
27
27
|
import {MIN_NODE_VERSION, isNodeVersionSupported} from '../lib/node-version.mjs';
|
|
28
28
|
import {CLI_ROOT, findCoreDir} from '../utils/paths.mjs';
|
|
29
29
|
import {detectPackageManager} from '../utils/package-manager.mjs';
|
|
30
|
-
import {findConfigPath,
|
|
30
|
+
import {findConfigPath, Project} from '../lib/project.mjs';
|
|
31
31
|
import {semverCompare} from '../utils/semver.mjs';
|
|
32
32
|
|
|
33
33
|
const _require = createRequire(import.meta.url);
|
|
@@ -240,7 +240,7 @@ export function checkThemes(ctx) {
|
|
|
240
240
|
label: 'Theme packages',
|
|
241
241
|
status: 'warn',
|
|
242
242
|
message: 'No @astryxdesign/theme-* packages are installed.',
|
|
243
|
-
fix: 'Install a theme, e.g. `npm install @astryxdesign/theme-neutral`, then import its CSS or set
|
|
243
|
+
fix: 'Install a theme, e.g. `npm install @astryxdesign/theme-neutral`, then import its CSS or set astryx.theme.',
|
|
244
244
|
};
|
|
245
245
|
}
|
|
246
246
|
|
|
@@ -279,8 +279,9 @@ export async function checkConfig(ctx) {
|
|
|
279
279
|
};
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
-
//
|
|
283
|
-
//
|
|
282
|
+
// Project.load swallows nothing — it surfaces a genuine load failure — but
|
|
283
|
+
// the config check wants to report a bad default export precisely, so we
|
|
284
|
+
// re-import directly to surface a genuine load failure as a FAIL.
|
|
284
285
|
try {
|
|
285
286
|
const {pathToFileURL} = await import('node:url');
|
|
286
287
|
const mod = await import(pathToFileURL(ctx.configPath).href);
|
|
@@ -291,25 +292,9 @@ export async function checkConfig(ctx) {
|
|
|
291
292
|
label: 'astryx.config.mjs',
|
|
292
293
|
status: 'fail',
|
|
293
294
|
message: `astryx.config.mjs default export is not an object (got ${typeof config}).`,
|
|
294
|
-
fix: 'Export a default object from astryx.config.mjs, e.g. `export default {
|
|
295
|
+
fix: 'Export a default object from astryx.config.mjs, e.g. `export default { integrations: [] };`.',
|
|
295
296
|
};
|
|
296
297
|
}
|
|
297
|
-
// Validate that `packages`, if present, is a string or array of strings.
|
|
298
|
-
if (config?.packages !== undefined) {
|
|
299
|
-
const arr = Array.isArray(config.packages)
|
|
300
|
-
? config.packages
|
|
301
|
-
: [config.packages];
|
|
302
|
-
const bad = arr.some(p => typeof p !== 'string');
|
|
303
|
-
if (bad) {
|
|
304
|
-
return {
|
|
305
|
-
id: 'config',
|
|
306
|
-
label: 'astryx.config.mjs',
|
|
307
|
-
status: 'fail',
|
|
308
|
-
message: 'astryx.config.mjs `packages` must be a string or array of strings.',
|
|
309
|
-
fix: 'Set `packages` to a path string or array of path strings.',
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
298
|
return {
|
|
314
299
|
id: 'config',
|
|
315
300
|
label: 'astryx.config.mjs',
|
|
@@ -369,7 +354,7 @@ export function checkAgentDocs(ctx) {
|
|
|
369
354
|
label: 'AI agent docs',
|
|
370
355
|
status: 'warn',
|
|
371
356
|
message: `Agent docs present (${present.join(', ')}) but no Astryx section markers found.`,
|
|
372
|
-
fix: 'Add the
|
|
357
|
+
fix: 'Add the Astryx section to your agent docs with `astryx init --features agents`.',
|
|
373
358
|
};
|
|
374
359
|
}
|
|
375
360
|
|
|
@@ -377,7 +362,7 @@ export function checkAgentDocs(ctx) {
|
|
|
377
362
|
id: 'agent-docs',
|
|
378
363
|
label: 'AI agent docs',
|
|
379
364
|
status: 'pass',
|
|
380
|
-
message: `
|
|
365
|
+
message: `Astryx agent docs section present in ${withMarkers.join(', ')}.`,
|
|
381
366
|
};
|
|
382
367
|
}
|
|
383
368
|
|
|
@@ -493,8 +478,8 @@ export async function runChecks(options = {}) {
|
|
|
493
478
|
// Resolve a possible theme key from config (best-effort; never throws).
|
|
494
479
|
let configTheme = null;
|
|
495
480
|
try {
|
|
496
|
-
const
|
|
497
|
-
configTheme =
|
|
481
|
+
const project = await Project.load(cwd);
|
|
482
|
+
configTheme = project.config?.theme ?? null;
|
|
498
483
|
} catch {
|
|
499
484
|
// Best-effort: a missing/invalid config leaves configTheme null.
|
|
500
485
|
}
|
package/src/api/layout.mjs
CHANGED
|
@@ -25,13 +25,14 @@ import {expand} from '../lib/xle/expand.mjs';
|
|
|
25
25
|
import {toCompact, toOutline} from '../lib/xle/print.mjs';
|
|
26
26
|
import {buildRegistry, ALIAS_TABLE} from '../lib/xle/registry.mjs';
|
|
27
27
|
import {discoverTemplates, stripTemplateAssetRefs} from './template.mjs';
|
|
28
|
-
import {
|
|
28
|
+
import {Project} from '../lib/project.mjs';
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* The catalog a `{hint}` can resolve to: template blocks (spliced inline) plus
|
|
32
|
-
* any app-registered local components from astryx.config.mjs
|
|
33
|
-
* (imported by name). App components are how XLE
|
|
34
|
-
* KpiCard/chart/drawer set that the
|
|
32
|
+
* any app-registered local components from astryx.config.mjs
|
|
33
|
+
* `experimental.xle.components` (imported by name). App components are how XLE
|
|
34
|
+
* reaches domain pieces — the KpiCard/chart/drawer set that the
|
|
35
|
+
* @astryxdesign/core registry can't see.
|
|
35
36
|
*/
|
|
36
37
|
async function loadBlocks(cwd) {
|
|
37
38
|
const blocks = [];
|
|
@@ -42,20 +43,20 @@ async function loadBlocks(cwd) {
|
|
|
42
43
|
// discovery is best-effort
|
|
43
44
|
}
|
|
44
45
|
try {
|
|
45
|
-
const
|
|
46
|
-
const components = config.
|
|
46
|
+
const project = await Project.load(cwd);
|
|
47
|
+
const components = project.config.experimental?.xle?.components ?? {};
|
|
47
48
|
for (const [name, spec] of Object.entries(components)) {
|
|
48
|
-
const importPath =
|
|
49
|
+
const importPath = spec.from;
|
|
49
50
|
if (!importPath) continue;
|
|
50
51
|
blocks.push({
|
|
51
52
|
type: 'block',
|
|
52
53
|
kind: 'component',
|
|
53
54
|
dirName: name,
|
|
54
55
|
name,
|
|
55
|
-
description:
|
|
56
|
+
description: spec.description ?? '',
|
|
56
57
|
category: 'app',
|
|
57
58
|
importPath,
|
|
58
|
-
isDefault:
|
|
59
|
+
isDefault: Boolean(spec.default),
|
|
59
60
|
});
|
|
60
61
|
}
|
|
61
62
|
} catch {
|
|
@@ -281,7 +282,7 @@ TEMPLATE REFERENCING ({hint} pulls in real content — this is how XLE reaches
|
|
|
281
282
|
{kpi-card} standalone reference (no wrapper element) — place a component directly
|
|
282
283
|
{kpi-card}*4 repeat a reference; the definition/import is emitted once
|
|
283
284
|
app components register local ones in astryx.config.mjs to import them by name:
|
|
284
|
-
export default {
|
|
285
|
+
export default {experimental: {xle: {components: {KpiCard: {from: '@/components/KpiCard'}}}}}
|
|
285
286
|
then {kpi-card} → import {KpiCard} + <KpiCard /> (kebab ↔ Pascal)
|
|
286
287
|
|
|
287
288
|
STRUCTURE THE EXPANDER HANDLES
|
package/src/api/layout.test.mjs
CHANGED
|
@@ -196,11 +196,14 @@ describe('template referencing', () => {
|
|
|
196
196
|
|
|
197
197
|
it('imports app-registered local components (the local-component bridge)', async () => {
|
|
198
198
|
// Inside the workspace so @astryxdesign/core resolves; cleaned up after.
|
|
199
|
+
// A package.json beside the config makes Project.load resolve it as the
|
|
200
|
+
// sibling-of-nearest-package.json (the standard config resolution).
|
|
199
201
|
const cwd = mkdtempSync(join(process.cwd(), '.xle-imp-test-'));
|
|
200
202
|
try {
|
|
203
|
+
writeFileSync(join(cwd, 'package.json'), '{"name": "xle-imp-fixture"}\n');
|
|
201
204
|
writeFileSync(
|
|
202
205
|
join(cwd, 'astryx.config.mjs'),
|
|
203
|
-
`export default {
|
|
206
|
+
`export default {experimental: {xle: {components: {KpiCard: {from: '@/components/KpiCard'}, TimeRangePicker: {from: '@/components/TimeRangePicker'}}}}};\n`,
|
|
204
207
|
);
|
|
205
208
|
const result = await layoutExpand('S[p6] > (G[c4 g4] > {kpi-card}*4) + {time-range-picker}', {
|
|
206
209
|
name: 'Demo',
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Integration-provided template discovery (same-stem + type-driven).
|
|
5
|
+
*
|
|
6
|
+
* These tests stand up a temp consumer project with an astryx.config and an
|
|
7
|
+
* installed integration package that contributes templates, then exercise the
|
|
8
|
+
* public `template()` API to verify discovery, package/type scoping, ambiguity
|
|
9
|
+
* errors, and copy-to-dir naming.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {afterEach, beforeEach, describe, expect, it} from 'vitest';
|
|
13
|
+
import * as fs from 'node:fs';
|
|
14
|
+
import * as path from 'node:path';
|
|
15
|
+
import {template} from './template.mjs';
|
|
16
|
+
|
|
17
|
+
let tmpDir;
|
|
18
|
+
let originalCwd;
|
|
19
|
+
|
|
20
|
+
/** Absolute path to the CLI package so integrations can import /template. */
|
|
21
|
+
const CLI_PKG = path.resolve(import.meta.dirname, '..', '..');
|
|
22
|
+
|
|
23
|
+
function makeConsumer() {
|
|
24
|
+
const dir = fs.mkdtempSync(
|
|
25
|
+
path.join(process.cwd(), '.astryx-template-it-'),
|
|
26
|
+
);
|
|
27
|
+
fs.writeFileSync(
|
|
28
|
+
path.join(dir, 'package.json'),
|
|
29
|
+
JSON.stringify({name: 'consumer'}),
|
|
30
|
+
);
|
|
31
|
+
fs.writeFileSync(
|
|
32
|
+
path.join(dir, 'astryx.config.mjs'),
|
|
33
|
+
`export default { integrations: ['@acme/widgets'] };\n`,
|
|
34
|
+
);
|
|
35
|
+
return dir;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Install an @acme/widgets integration package that declares a templates root.
|
|
40
|
+
* @returns the package dir.
|
|
41
|
+
*/
|
|
42
|
+
function installWidgets(consumerDir) {
|
|
43
|
+
const pkgDir = path.join(consumerDir, 'node_modules', '@acme', 'widgets');
|
|
44
|
+
fs.mkdirSync(pkgDir, {recursive: true});
|
|
45
|
+
fs.writeFileSync(
|
|
46
|
+
path.join(pkgDir, 'package.json'),
|
|
47
|
+
JSON.stringify({name: '@acme/widgets', version: '2.0.0'}),
|
|
48
|
+
);
|
|
49
|
+
fs.writeFileSync(
|
|
50
|
+
path.join(pkgDir, 'astryx.integration.mjs'),
|
|
51
|
+
`export default { templates: './templates' };\n`,
|
|
52
|
+
);
|
|
53
|
+
fs.mkdirSync(path.join(pkgDir, 'templates'));
|
|
54
|
+
return pkgDir;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Write a template doc + same-stem source under the templates root. */
|
|
58
|
+
function writeTemplate(pkgDir, id, {kind, body, withSource = true}) {
|
|
59
|
+
const docPath = path.join(pkgDir, 'templates', `${id}.doc.mjs`);
|
|
60
|
+
fs.mkdirSync(path.dirname(docPath), {recursive: true});
|
|
61
|
+
const create = kind === 'page' ? 'createPageTemplate' : 'createBlockTemplate';
|
|
62
|
+
fs.writeFileSync(
|
|
63
|
+
docPath,
|
|
64
|
+
body ??
|
|
65
|
+
`import {${create}} from '${CLI_PKG}/src/template.mjs';\n` +
|
|
66
|
+
`export default ${create}({name: '${id} name', description: '${id} desc'});\n`,
|
|
67
|
+
);
|
|
68
|
+
if (withSource) {
|
|
69
|
+
fs.writeFileSync(
|
|
70
|
+
path.join(pkgDir, 'templates', `${id}.tsx`),
|
|
71
|
+
`export default function ${id.replace(/[^a-zA-Z0-9]/g, '')}() { return null; }\n`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
beforeEach(() => {
|
|
77
|
+
originalCwd = process.cwd();
|
|
78
|
+
tmpDir = makeConsumer();
|
|
79
|
+
process.chdir(tmpDir);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
afterEach(() => {
|
|
83
|
+
process.chdir(originalCwd);
|
|
84
|
+
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('integration template discovery', () => {
|
|
88
|
+
it('discovers and lists an integration template with package + type', async () => {
|
|
89
|
+
const pkgDir = installWidgets(tmpDir);
|
|
90
|
+
writeTemplate(pkgDir, 'pricing', {kind: 'page'});
|
|
91
|
+
|
|
92
|
+
const result = await template(undefined, {list: true, cwd: tmpDir});
|
|
93
|
+
expect(result.type).toBe('template.list');
|
|
94
|
+
const entry = result.data.find(t => t.id === 'pricing');
|
|
95
|
+
expect(entry).toBeTruthy();
|
|
96
|
+
expect(entry.type).toBe('page');
|
|
97
|
+
expect(entry.package).toBe('@acme/widgets');
|
|
98
|
+
expect(entry.name).toBe('pricing name');
|
|
99
|
+
expect(entry.description).toBe('pricing desc');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('lists nested-id templates (kebab path under root)', async () => {
|
|
103
|
+
const pkgDir = installWidgets(tmpDir);
|
|
104
|
+
writeTemplate(pkgDir, 'marketing/hero', {kind: 'block'});
|
|
105
|
+
|
|
106
|
+
const result = await template(undefined, {list: true, cwd: tmpDir});
|
|
107
|
+
const entry = result.data.find(t => t.id === 'marketing/hero');
|
|
108
|
+
expect(entry).toBeTruthy();
|
|
109
|
+
expect(entry.type).toBe('block');
|
|
110
|
+
expect(entry.package).toBe('@acme/widgets');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('always reports core templates under @astryxdesign/core', async () => {
|
|
114
|
+
const result = await template(undefined, {list: true, cwd: tmpDir});
|
|
115
|
+
const core = result.data.filter(t => t.package === '@astryxdesign/core');
|
|
116
|
+
expect(core.length).toBeGreaterThan(0);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('--package narrows the listing', async () => {
|
|
120
|
+
const pkgDir = installWidgets(tmpDir);
|
|
121
|
+
writeTemplate(pkgDir, 'pricing', {kind: 'page'});
|
|
122
|
+
|
|
123
|
+
const result = await template(undefined, {
|
|
124
|
+
list: true,
|
|
125
|
+
package: '@acme/widgets',
|
|
126
|
+
cwd: tmpDir,
|
|
127
|
+
});
|
|
128
|
+
expect(result.data.length).toBe(1);
|
|
129
|
+
expect(result.data[0].id).toBe('pricing');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('skips a template whose same-stem source is missing', async () => {
|
|
133
|
+
const pkgDir = installWidgets(tmpDir);
|
|
134
|
+
writeTemplate(pkgDir, 'orphan', {kind: 'page', withSource: false});
|
|
135
|
+
|
|
136
|
+
const result = await template(undefined, {list: true, cwd: tmpDir});
|
|
137
|
+
expect(result.data.find(t => t.id === 'orphan')).toBeUndefined();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('skips a raw doc that is missing a type', async () => {
|
|
141
|
+
const pkgDir = installWidgets(tmpDir);
|
|
142
|
+
writeTemplate(pkgDir, 'untyped', {
|
|
143
|
+
kind: 'page',
|
|
144
|
+
body: `export default {name: 'Untyped', description: 'no type'};\n`,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const result = await template(undefined, {list: true, cwd: tmpDir});
|
|
148
|
+
expect(result.data.find(t => t.id === 'untyped')).toBeUndefined();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('errors with candidates when an id is ambiguous across type/package', async () => {
|
|
152
|
+
const pkgDir = installWidgets(tmpDir);
|
|
153
|
+
// Same id "hero" as both a page and a block within the integration.
|
|
154
|
+
writeTemplate(pkgDir, 'hero', {kind: 'page'});
|
|
155
|
+
// Add a sibling block doc with the same stem in a different file is not
|
|
156
|
+
// possible (same file). Instead install a second package with a "hero".
|
|
157
|
+
const pkg2 = path.join(tmpDir, 'node_modules', '@acme', 'extra');
|
|
158
|
+
fs.mkdirSync(path.join(pkg2, 'templates'), {recursive: true});
|
|
159
|
+
fs.writeFileSync(
|
|
160
|
+
path.join(pkg2, 'package.json'),
|
|
161
|
+
JSON.stringify({name: '@acme/extra', version: '1.0.0'}),
|
|
162
|
+
);
|
|
163
|
+
fs.writeFileSync(
|
|
164
|
+
path.join(pkg2, 'astryx.integration.mjs'),
|
|
165
|
+
`export default { templates: './templates' };\n`,
|
|
166
|
+
);
|
|
167
|
+
fs.writeFileSync(
|
|
168
|
+
path.join(pkg2, 'templates', 'hero.doc.mjs'),
|
|
169
|
+
`import {createBlockTemplate} from '${CLI_PKG}/src/template.mjs';\n` +
|
|
170
|
+
`export default createBlockTemplate({name: 'Hero block', description: 'b'});\n`,
|
|
171
|
+
);
|
|
172
|
+
fs.writeFileSync(
|
|
173
|
+
path.join(pkg2, 'templates', 'hero.tsx'),
|
|
174
|
+
`export default function Hero() { return null; }\n`,
|
|
175
|
+
);
|
|
176
|
+
fs.writeFileSync(
|
|
177
|
+
path.join(tmpDir, 'astryx.config.mjs'),
|
|
178
|
+
`export default { integrations: ['@acme/widgets', '@acme/extra'] };\n`,
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
await expect(
|
|
182
|
+
template('hero', {targetPath: './out', cwd: tmpDir}),
|
|
183
|
+
).rejects.toMatchObject({code: 'ERR_AMBIGUOUS_TEMPLATE'});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('--type and --package narrow an ambiguous id to a single match', async () => {
|
|
187
|
+
const pkgDir = installWidgets(tmpDir);
|
|
188
|
+
writeTemplate(pkgDir, 'pricing', {kind: 'page'});
|
|
189
|
+
|
|
190
|
+
const result = await template('pricing', {
|
|
191
|
+
type: 'page',
|
|
192
|
+
package: '@acme/widgets',
|
|
193
|
+
show: true,
|
|
194
|
+
cwd: tmpDir,
|
|
195
|
+
});
|
|
196
|
+
expect(result.type).toBe('template.show');
|
|
197
|
+
expect(result.data.type).toBe('page');
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('copies a page template into a directory as page.tsx', async () => {
|
|
201
|
+
const pkgDir = installWidgets(tmpDir);
|
|
202
|
+
writeTemplate(pkgDir, 'pricing', {kind: 'page'});
|
|
203
|
+
|
|
204
|
+
const result = await template('pricing', {
|
|
205
|
+
targetPath: './dest',
|
|
206
|
+
cwd: tmpDir,
|
|
207
|
+
});
|
|
208
|
+
expect(result.type).toBe('template.copy');
|
|
209
|
+
expect(result.data.fileName).toBe('page.tsx');
|
|
210
|
+
expect(fs.existsSync(path.join(tmpDir, 'dest', 'page.tsx'))).toBe(true);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('copies a block template into a directory as <id-basename>.tsx', async () => {
|
|
214
|
+
const pkgDir = installWidgets(tmpDir);
|
|
215
|
+
writeTemplate(pkgDir, 'marketing/hero', {kind: 'block'});
|
|
216
|
+
|
|
217
|
+
const result = await template('marketing/hero', {
|
|
218
|
+
targetPath: './dest',
|
|
219
|
+
cwd: tmpDir,
|
|
220
|
+
});
|
|
221
|
+
expect(result.type).toBe('template.copy');
|
|
222
|
+
expect(result.data.fileName).toBe('hero.tsx');
|
|
223
|
+
expect(fs.existsSync(path.join(tmpDir, 'dest', 'hero.tsx'))).toBe(true);
|
|
224
|
+
});
|
|
225
|
+
});
|