@astryxdesign/cli 0.6.4-canary.ed2e54e → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -64
- package/api/component/_adapter.d.mts +0 -25
- package/api/component/_adapter.mjs +5 -59
- package/api/component/component.d.mts +3 -6
- package/api/component/component.doc.mjs +10 -23
- package/api/component/component.mjs +9 -249
- package/api/component/component.type.d.mts +0 -25
- package/api/component/component.type.mjs +0 -44
- package/api/discover/_adapter.d.mts +6 -114
- package/api/discover/_adapter.mjs +17 -372
- package/api/discover/detail/detail.d.mts +6 -18
- package/api/discover/detail/detail.mjs +13 -67
- package/api/discover/detail/detail.test.mjs +0 -85
- package/api/discover/discover.d.mts +9 -3
- package/api/discover/discover.doc.mjs +18 -61
- package/api/discover/discover.mjs +36 -220
- package/api/discover/discover.test.mjs +2 -11
- package/api/discover/discover.type.d.mts +8 -147
- package/api/discover/discover.type.mjs +12 -102
- package/api/discover/list/list.d.mts +6 -20
- package/api/discover/list/list.mjs +12 -45
- package/api/discover/list/list.test.mjs +0 -46
- package/api/discover/search/search.d.mts +16 -18
- package/api/discover/search/search.mjs +56 -102
- package/api/discover/search/search.test.mjs +10 -144
- package/api/docs/docs.test.mjs +0 -2
- package/api/doctor/doctor.d.mts +3 -8
- package/api/doctor/doctor.mjs +9 -90
- package/api/doctor/doctor.test.mjs +10 -122
- package/api/index.d.mts +2 -1
- package/api/index.mjs +4 -4
- package/api/integration/add-helpers.d.mts +2 -5
- package/api/integration/add-helpers.mjs +9 -36
- package/api/integration/pack-check.mjs +3 -28
- package/api/json/index.ts +1 -0
- package/api/layout/_adapter.d.mts +34 -0
- package/api/layout/_adapter.mjs +148 -0
- package/api/layout/check/check.d.mts +16 -0
- package/api/layout/check/check.mjs +40 -0
- package/api/layout/expand/expand.d.mts +22 -0
- package/api/layout/expand/expand.mjs +155 -0
- package/api/layout/expand/expand.path-safety.test.mjs +53 -0
- package/api/layout/grammar/grammar.d.mts +13 -0
- package/api/layout/grammar/grammar.mjs +87 -0
- package/api/layout/layout.d.mts +6 -0
- package/api/layout/layout.mjs +17 -0
- package/api/layout/layout.test.mjs +297 -0
- package/api/layout/layout.type.d.mts +89 -0
- package/api/layout/layout.type.mjs +103 -0
- package/api/layout/layoutCheck.doc.d.mts +11 -0
- package/api/layout/layoutCheck.doc.mjs +85 -0
- package/api/layout/layoutExpand.doc.d.mts +11 -0
- package/api/layout/layoutExpand.doc.mjs +107 -0
- package/api/layout/layoutGrammar.doc.d.mts +11 -0
- package/api/layout/layoutGrammar.doc.mjs +57 -0
- package/api/search/search.test.mjs +0 -18
- package/api/template/template-integration.test.mjs +65 -1
- package/api/template/template.mjs +1 -1
- package/api/theme/add/add.mjs +25 -17
- package/api/theme/add/add.staging.test.mjs +23 -40
- package/api/theme/build/build.family.test.mjs +12 -7
- package/api/theme/build/build.mjs +18 -8
- package/api/upgrade/run/run.mjs +4 -6
- package/api/upgrade/upgrade.type.mjs +2 -2
- package/assets/codemods/__tests__/runner.test.mjs +1 -3
- package/assets/codemods/integration-runner.mjs +3 -3
- package/assets/codemods/runner.mjs +4 -5
- package/assets/docs/internationalization.doc.mjs +5 -7
- package/assets/docs/tree/integrations.doc.mjs +1 -20
- package/assets/templates/blocks/components/InternationalizationProvider/InternationalizationProvider01ShippedLocale.tsx +1 -1
- package/authoring/config/config.doc.mjs +1 -9
- package/authoring/config/parse.d.mts +0 -2
- package/authoring/config/parse.mjs +0 -19
- package/authoring/config/parse.test.mjs +0 -8
- package/authoring/config/type.ts +2 -13
- package/authoring/doctypes/command/command.doc.mjs +1 -1
- package/authoring/doctypes/command/type.ts +1 -1
- package/authoring/index.d.mts +0 -1
- package/authoring/index.d.ts +0 -10
- package/authoring/index.mjs +0 -1
- package/clients/cli/command-result-coverage.test.mjs +7 -7
- package/clients/cli/commands/component/index.mjs +55 -152
- package/clients/cli/commands/component-ownership.test.mjs +0 -89
- package/clients/cli/commands/component.doc.mjs +6 -23
- package/clients/cli/commands/debug-result-summary.test.mjs +2 -2
- package/clients/cli/commands/discover.doc.mjs +9 -53
- package/clients/cli/commands/discover.mjs +118 -393
- package/clients/cli/commands/docs.test.mjs +0 -29
- package/clients/cli/commands/layout-check.doc.mjs +65 -0
- package/clients/cli/commands/layout-expand.doc.mjs +83 -0
- package/clients/cli/commands/layout-grammar.doc.mjs +30 -0
- package/clients/cli/commands/layout.doc.mjs +34 -0
- package/clients/cli/commands/layout.error-codes.test.mjs +66 -0
- package/clients/cli/commands/layout.exit-parity.test.mjs +41 -0
- package/clients/cli/commands/layout.mjs +275 -0
- package/clients/cli/commands/layout.path-help.test.mjs +33 -0
- package/clients/cli/commands/layout.stdin-cap.test.mjs +47 -0
- package/clients/cli/commands/layout.text-fields.test.mjs +39 -0
- package/clients/cli/commands/text-json-parity.test.mjs +17 -0
- package/clients/cli/index.mjs +4 -0
- package/clients/cli/lib/exit-codes.test.mjs +8 -1
- package/clients/cli/lib/json-shim.mjs +14 -24
- package/clients/cli/lib/json-shim.test.mjs +20 -6
- package/clients/cli/lib/manifest.mjs +8 -3
- package/clients/cli/lib/manifest.test.mjs +2 -5
- package/foundation/discovery/authoring-self-docs.mjs +0 -1
- package/foundation/discovery/template-adapter.mjs +1 -1
- package/foundation/doc-compiler/doc-loads.test.mjs +12 -0
- package/foundation/doc-compiler/tree.test.mjs +1 -9
- package/foundation/integrations/integrations.d.mts +1 -14
- package/foundation/integrations/integrations.mjs +1 -41
- package/foundation/integrations/integrations.test.mjs +0 -31
- package/foundation/response/response-types.doc.mjs +21 -15
- package/foundation/response/response-types.doc.test.mjs +0 -23
- package/foundation/xle/browser.d.mts +3 -3
- package/foundation/xle/browser.mjs +3 -3
- package/foundation/xle/expand.mjs +2 -2
- package/foundation/xle/parse.mjs +1 -1
- package/foundation/xle/print.mjs +2 -2
- package/foundation/xle/splice.mjs +1 -1
- package/package.json +9 -9
- package/api/discover/_adapter.test.mjs +0 -215
- package/api/discover/_catalog-view.d.mts +0 -115
- package/api/discover/_catalog-view.mjs +0 -203
- package/api/discover/_catalog-view.test.mjs +0 -128
- package/api/discover/detail/item/item.d.mts +0 -26
- package/api/discover/detail/item/item.mjs +0 -78
- package/api/discover/detail/item/item.test.mjs +0 -73
- package/api/integration/pack-check.lifecycle-output.test.mjs +0 -105
- package/api/theme/add/add.rollback.test.mjs +0 -158
- package/api/theme/build/build.rollback.test.mjs +0 -148
- package/api/upgrade/run/files-changed.test.mjs +0 -111
- package/assets/codemods/file-count.test.mjs +0 -163
- package/assets/docs/tree/component-lookups.doc.mjs +0 -149
- package/authoring/discover/discover.doc.d.mts +0 -13
- package/authoring/discover/discover.doc.mjs +0 -138
- package/authoring/discover/parse.d.mts +0 -24
- package/authoring/discover/parse.mjs +0 -128
- package/authoring/discover/parse.test.mjs +0 -124
- package/authoring/discover/type.ts +0 -87
- package/clients/cli/commands/component-batch.test.mjs +0 -341
- package/clients/cli/commands/discover.sources.test.mjs +0 -267
- package/clients/cli/lib/parse-error-format.test.mjs +0 -81
- package/foundation/response/batch.type.d.mts +0 -33
- package/foundation/response/batch.type.mjs +0 -34
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file `layout expand` text output names its fields after the JSON keys, so
|
|
5
|
+
* the two views stay greppable against each other.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {describe, it, expect, beforeEach, afterEach} from 'vitest';
|
|
9
|
+
import * as fs from 'node:fs';
|
|
10
|
+
import * as path from 'node:path';
|
|
11
|
+
import {runCli} from '../../../test-utils/run-cli.mjs';
|
|
12
|
+
|
|
13
|
+
const SLOW = 60_000;
|
|
14
|
+
|
|
15
|
+
describe('layout expand text fields mirror the JSON keys', () => {
|
|
16
|
+
let cwd;
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
// Inside the workspace so @astryxdesign/core resolves.
|
|
19
|
+
cwd = fs.mkdtempSync(path.join(process.cwd(), '.xle-text-fields-'));
|
|
20
|
+
});
|
|
21
|
+
afterEach(() => fs.rmSync(cwd, {recursive: true, force: true}));
|
|
22
|
+
|
|
23
|
+
it('labels every record field with a key of the layout.expand data', async () => {
|
|
24
|
+
const expr = 'V > B + {not-a-real-block}';
|
|
25
|
+
const json = await runCli(['layout', 'expand', expr, './a', '--loose', '--json'], cwd);
|
|
26
|
+
expect(json.status).toBe(0);
|
|
27
|
+
const {data} = JSON.parse(json.stdout);
|
|
28
|
+
expect(data.todos.length).toBeGreaterThan(0);
|
|
29
|
+
|
|
30
|
+
const human = await runCli(['layout', 'expand', expr, './b', '--loose'], cwd);
|
|
31
|
+
expect(human.status).toBe(0);
|
|
32
|
+
const fields = human.stdout
|
|
33
|
+
.split('\n')
|
|
34
|
+
.map(line => /^([A-Za-z]+):\s/.exec(line)?.[1])
|
|
35
|
+
.filter(Boolean);
|
|
36
|
+
expect(fields).toEqual(['componentsUsed', 'todos']);
|
|
37
|
+
for (const field of fields) expect(Object.keys(data)).toContain(field);
|
|
38
|
+
}, SLOW);
|
|
39
|
+
});
|
|
@@ -377,6 +377,23 @@ const CASES = [
|
|
|
377
377
|
// Needs a packable project; pack itself may exit differently.
|
|
378
378
|
skipFieldChecks: true,
|
|
379
379
|
},
|
|
380
|
+
// ── Layout command: remove these cases when the layout command is deleted ──
|
|
381
|
+
{
|
|
382
|
+
name: 'layout check',
|
|
383
|
+
args: ['layout', 'check', 'Button'],
|
|
384
|
+
skipFieldChecks: true,
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
name: 'layout expand',
|
|
388
|
+
args: ['layout', 'expand', 'Button'],
|
|
389
|
+
skipFieldChecks: true,
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
name: 'layout grammar',
|
|
393
|
+
args: ['layout', 'grammar'],
|
|
394
|
+
skipFieldChecks: true,
|
|
395
|
+
},
|
|
396
|
+
// ── End layout cases ──
|
|
380
397
|
{
|
|
381
398
|
name: 'manifest',
|
|
382
399
|
args: ['manifest'],
|
package/clients/cli/index.mjs
CHANGED
|
@@ -103,6 +103,9 @@ export const JSON_SUPPORTED = new Set([
|
|
|
103
103
|
'doctor integration templates',
|
|
104
104
|
'doctor integration components',
|
|
105
105
|
'doctor integration docs',
|
|
106
|
+
'layout expand',
|
|
107
|
+
'layout check',
|
|
108
|
+
'layout grammar',
|
|
106
109
|
]);
|
|
107
110
|
|
|
108
111
|
/**
|
|
@@ -261,6 +264,7 @@ const commands = [
|
|
|
261
264
|
{name: 'gap-report', path: './commands/gap-report.mjs', register: 'registerGapReport'},
|
|
262
265
|
// agent-docs folded into init — functions still importable from agent-docs.mjs
|
|
263
266
|
{name: 'template', path: './commands/template.mjs', register: 'registerTemplate'},
|
|
267
|
+
{name: 'layout', path: './commands/layout.mjs', register: 'registerLayout'},
|
|
264
268
|
{name: 'upgrade', path: './commands/upgrade.mjs', register: 'registerUpgrade'},
|
|
265
269
|
{name: 'theme', path: './commands/build-theme.mjs', register: 'registerTheme'},
|
|
266
270
|
{name: 'integration', path: './commands/integration.mjs', register: 'registerIntegration'},
|
|
@@ -34,7 +34,7 @@ manifest.commands.forEach(walk);
|
|
|
34
34
|
|
|
35
35
|
describe('command exit codes', () => {
|
|
36
36
|
it('every CommandDoc documents its exit codes', () => {
|
|
37
|
-
expect(commandDocs.length).toBeGreaterThan(
|
|
37
|
+
expect(commandDocs.length).toBeGreaterThan(30);
|
|
38
38
|
for (const doc of commandDocs) {
|
|
39
39
|
expect(doc.exitCodes?.length, doc.name).toBeGreaterThan(0);
|
|
40
40
|
}
|
|
@@ -54,6 +54,13 @@ describe('command exit codes', () => {
|
|
|
54
54
|
},
|
|
55
55
|
);
|
|
56
56
|
|
|
57
|
+
it('bare `astryx layout` exits 1 in both modes, as documented', async () => {
|
|
58
|
+
const doc = commandDocs.find((d) => d.name === 'layout');
|
|
59
|
+
expect(doc.exitCodes.find((e) => e.code === 1)?.when).toMatch(/^no subcommand/);
|
|
60
|
+
expect((await runCli(['layout'])).status).toBe(1);
|
|
61
|
+
expect((await runCli(['layout', '--json'])).status).toBe(1);
|
|
62
|
+
});
|
|
63
|
+
|
|
57
64
|
it('`astryx discover` with a blank query exits 1 only when packages are discovered', async () => {
|
|
58
65
|
const doc = commandDocs.find((d) => d.name === 'discover');
|
|
59
66
|
expect(doc.exitCodes.find((e) => e.code === 1)?.when).toMatch(/blank query when packages are discovered/);
|
|
@@ -33,12 +33,10 @@
|
|
|
33
33
|
* shows help because the invocation failed (`help <unknown>`, or a
|
|
34
34
|
* command group with no subcommand), which exits 1.
|
|
35
35
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* a parse failure reads like every other CLI error. Other stderr output,
|
|
41
|
-
* such as help printed as the failure report, still passes through.
|
|
36
|
+
* Non-JSON behavior is preserved exactly: every code path that printed
|
|
37
|
+
* to stderr before still prints to stderr. Commander writes its
|
|
38
|
+
* "error: ..." line via configureOutput.writeErr, which we pass
|
|
39
|
+
* through verbatim outside of --json mode.
|
|
42
40
|
*/
|
|
43
41
|
|
|
44
42
|
import {API_VERSION, isJsonMode, toErrorEnvelope} from '../../../foundation/response/json.mjs';
|
|
@@ -251,20 +249,11 @@ function applyShimRecursively(cmd) {
|
|
|
251
249
|
});
|
|
252
250
|
cmd.configureOutput({
|
|
253
251
|
writeOut: (str) => process.stdout.write(str),
|
|
254
|
-
// Commander's own "error: ..." line never reaches the user. Under --json a
|
|
255
|
-
// consumer parsing both streams must not see it alongside the envelope;
|
|
256
|
-
// in text mode it is Commander's format, not Astryx's, so an invalid
|
|
257
|
-
// global option (`--lang zh-Hans`) printed `error: option '--lang
|
|
258
|
-
// <locale>' argument 'zh-Hans' is invalid…` where every other CLI error
|
|
259
|
-
// prints `Error: …`. handleCommanderError writes the Astryx line below,
|
|
260
|
-
// for both modes, from the same message.
|
|
261
|
-
//
|
|
262
|
-
// ONLY that line. Commander also writes HELP through this channel when it
|
|
263
|
-
// shows help because the invocation failed (a command group with no
|
|
264
|
-
// subcommand), and that output is still wanted in text mode.
|
|
265
252
|
writeErr: (str) => {
|
|
253
|
+
// Suppress Commander's "error: ..." stderr line when --json is
|
|
254
|
+
// active, so a JSON consumer parsing both streams doesn't see
|
|
255
|
+
// it alongside the envelope. Non-JSON callers are unaffected.
|
|
266
256
|
if (jsonActive()) return;
|
|
267
|
-
if (/^error:\s/i.test(str)) return;
|
|
268
257
|
process.stderr.write(str);
|
|
269
258
|
},
|
|
270
259
|
});
|
|
@@ -443,15 +432,16 @@ export function handleCommanderError(err) {
|
|
|
443
432
|
code: commanderCodeToErrorCode(code, message),
|
|
444
433
|
});
|
|
445
434
|
|
|
446
|
-
// Real error paths.
|
|
447
|
-
// the key is already `error`, and in text mode the Astryx prefix replaces it.
|
|
448
|
-
const cleaned = message.replace(/^error:\s*/i, '');
|
|
435
|
+
// Real error paths.
|
|
449
436
|
if (jsonActive()) {
|
|
437
|
+
// Strip Commander's "error: " prefix — the envelope key is `error`
|
|
438
|
+
// already, doubled "error" is noise.
|
|
439
|
+
const cleaned = message.replace(/^error:\s*/i, '');
|
|
450
440
|
emitJsonError(cleaned, undefined, commanderCodeToErrorCode(code, cleaned));
|
|
451
441
|
} else {
|
|
452
|
-
//
|
|
453
|
-
//
|
|
454
|
-
|
|
442
|
+
// Non-JSON mode: Commander already wrote the "error: ..." line
|
|
443
|
+
// to stderr via configureOutput.writeErr before throwing the
|
|
444
|
+
// CommanderError. Nothing to do — exit with the original code.
|
|
455
445
|
}
|
|
456
446
|
process.exit(exitCode || 1);
|
|
457
447
|
}
|
|
@@ -225,6 +225,23 @@ describe('--json shim: help shown for a failed invocation is an error envelope',
|
|
|
225
225
|
expect(parsed.error).toMatch(/bogus/);
|
|
226
226
|
});
|
|
227
227
|
|
|
228
|
+
it('astryx layout --json (group, no subcommand) emits ERR_MISSING_ARGUMENT, exit 1', async () => {
|
|
229
|
+
const {status, stdout, stderr} = await runCli(['layout', '--json']);
|
|
230
|
+
expect(status).toBe(1);
|
|
231
|
+
expect(stderr).toBe('');
|
|
232
|
+
const parsed = parseJson(stdout);
|
|
233
|
+
expect(parsed).not.toHaveProperty('type');
|
|
234
|
+
expect(parsed.code).toBe('ERR_MISSING_ARGUMENT');
|
|
235
|
+
expect(parsed.suggestions.map((s) => s.name)).toContain('layout expand');
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('astryx layout (no --json) still prints help to stderr, exit 1', async () => {
|
|
239
|
+
const {status, stdout, stderr} = await runCli(['layout']);
|
|
240
|
+
expect(status).toBe(1);
|
|
241
|
+
expect(stdout).toBe('');
|
|
242
|
+
expect(stderr).toMatch(/Usage: astryx layout/);
|
|
243
|
+
});
|
|
244
|
+
|
|
228
245
|
it('the real binary emits the same envelope for help bogus --json', () => {
|
|
229
246
|
// One program per process, so the root's own outputHelp patch is used here.
|
|
230
247
|
const bin = fileURLToPath(new URL('../bin/astryx.mjs', import.meta.url));
|
|
@@ -240,9 +257,6 @@ describe('--json shim: help shown for a failed invocation is an error envelope',
|
|
|
240
257
|
});
|
|
241
258
|
|
|
242
259
|
it('a group added after install gets the same error envelope', () => {
|
|
243
|
-
// This is the only coverage of the no-subcommand path: every group the CLI
|
|
244
|
-
// ships has an action of its own (see command-result-coverage.test.mjs), so
|
|
245
|
-
// a group without one has to be built here.
|
|
246
260
|
const program = new Command('astryx');
|
|
247
261
|
installJsonShim(program);
|
|
248
262
|
// Added later, so only the prototype-level patch covers its outputHelp.
|
|
@@ -268,11 +282,11 @@ describe('--json shim: help shown for a failed invocation is an error envelope',
|
|
|
268
282
|
expect(parsed.suggestions).toEqual([{name: 'late child', reason: 'available subcommand'}]);
|
|
269
283
|
});
|
|
270
284
|
|
|
271
|
-
it('astryx help
|
|
272
|
-
const {status, stdout} = await runCli(['help', '
|
|
285
|
+
it('astryx help layout --json still emits the help envelope, exit 0', async () => {
|
|
286
|
+
const {status, stdout} = await runCli(['help', 'layout', '--json']);
|
|
273
287
|
expect(status).toBe(0);
|
|
274
288
|
const parsed = parseJson(stdout);
|
|
275
289
|
expect(parsed.type).toBe('help');
|
|
276
|
-
expect(parsed.data.command).toBe('astryx
|
|
290
|
+
expect(parsed.data.command).toBe('astryx layout');
|
|
277
291
|
});
|
|
278
292
|
});
|
|
@@ -51,7 +51,6 @@ export const RESPONSE_TYPES = {
|
|
|
51
51
|
init: ['init.run', 'init.remove'],
|
|
52
52
|
component: [
|
|
53
53
|
'component.list',
|
|
54
|
-
'component.batch',
|
|
55
54
|
'component.detail',
|
|
56
55
|
'component.detail.props',
|
|
57
56
|
'component.detail.source',
|
|
@@ -70,7 +69,6 @@ export const RESPONSE_TYPES = {
|
|
|
70
69
|
'discover.list',
|
|
71
70
|
'discover.detail',
|
|
72
71
|
'discover.detail.doc',
|
|
73
|
-
'discover.item',
|
|
74
72
|
'discover.search',
|
|
75
73
|
],
|
|
76
74
|
search: ['search'],
|
|
@@ -100,6 +98,9 @@ export const RESPONSE_TYPES = {
|
|
|
100
98
|
'doctor integration templates': ['integration.template-conflicts'],
|
|
101
99
|
'doctor integration components': ['integration.component-conflicts'],
|
|
102
100
|
'doctor integration docs': ['integration.doc-conflicts'],
|
|
101
|
+
'layout expand': ['layout.expand'],
|
|
102
|
+
'layout check': ['layout.check'],
|
|
103
|
+
'layout grammar': ['layout.grammar'],
|
|
103
104
|
};
|
|
104
105
|
|
|
105
106
|
/**
|
|
@@ -110,7 +111,6 @@ const EXAMPLES = {
|
|
|
110
111
|
component: [
|
|
111
112
|
'astryx component',
|
|
112
113
|
'astryx component XDSButton',
|
|
113
|
-
'astryx component Button Badge Text',
|
|
114
114
|
'astryx component XDSButton --props --json',
|
|
115
115
|
],
|
|
116
116
|
docs: [
|
|
@@ -180,6 +180,11 @@ const EXAMPLES = {
|
|
|
180
180
|
'astryx doctor integration docs @acme/widgets --json',
|
|
181
181
|
],
|
|
182
182
|
init: ['astryx init', 'astryx init --all --json'],
|
|
183
|
+
'layout expand': [
|
|
184
|
+
`astryx layout expand 'V[g6] > C{card-callout}*4' ./src/Page.tsx`,
|
|
185
|
+
],
|
|
186
|
+
'layout check': [`astryx layout check 'A[cp6] > L > LC > S[p6]' --json`],
|
|
187
|
+
'layout grammar': ['astryx layout grammar'],
|
|
183
188
|
};
|
|
184
189
|
|
|
185
190
|
/**
|
|
@@ -156,10 +156,7 @@ describe('manifest: shape', () => {
|
|
|
156
156
|
|
|
157
157
|
it('derives arguments from Commander metadata', () => {
|
|
158
158
|
const component = allEntries.find((c) => c.name === 'component');
|
|
159
|
-
|
|
160
|
-
expect(names.required).toBe(false);
|
|
161
|
-
expect(names.variadic).toBe(true);
|
|
162
|
-
expect(names.description).toContain('Two or more return one ordered batch');
|
|
159
|
+
expect(component.arguments.map((a) => a.name)).toContain('name');
|
|
163
160
|
const themeBuild = allEntries.find((c) => c.name === 'theme build');
|
|
164
161
|
expect(themeBuild.arguments.map((a) => a.name)).toContain('files');
|
|
165
162
|
const files = themeBuild.arguments.find((a) => a.name === 'files');
|
|
@@ -196,7 +193,7 @@ describe('manifest: e2e', () => {
|
|
|
196
193
|
// Enriched: the full structured manifest is embedded.
|
|
197
194
|
expect(parsed.data.manifest).toBeDefined();
|
|
198
195
|
expect(parsed.data.manifest.commands.find((c) => c.name === 'component').responseTypes)
|
|
199
|
-
.
|
|
196
|
+
.toContain('component.list');
|
|
200
197
|
});
|
|
201
198
|
});
|
|
202
199
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @file Shared template discovery + IO.
|
|
5
5
|
*
|
|
6
6
|
* Owns everything the template leaves (list/show/skeleton/copy) AND other
|
|
7
|
-
* commands (component, search, init, discover, Doctor integration)
|
|
7
|
+
* commands (component, layout, search, init, discover, Doctor integration)
|
|
8
8
|
* share: template discovery across core/external/integration sources, the
|
|
9
9
|
* template-spec loaders, and the cross-command helpers (stripTemplateAssetRefs,
|
|
10
10
|
* findShowcase, findRelatedBlocks, extractComponents, listTemplates). The
|
|
@@ -191,6 +191,18 @@ const RUNNERS = {
|
|
|
191
191
|
'import ./integration.doc.mjs',
|
|
192
192
|
],
|
|
193
193
|
},
|
|
194
|
+
'clients/cli/commands/layout.mjs': {
|
|
195
|
+
runs: SELF_DOCS,
|
|
196
|
+
sites: [
|
|
197
|
+
'import ../../../api/layout/layoutCheck.doc.mjs',
|
|
198
|
+
'import ../../../api/layout/layoutExpand.doc.mjs',
|
|
199
|
+
'import ../../../api/layout/layoutGrammar.doc.mjs',
|
|
200
|
+
'import ./layout-check.doc.mjs',
|
|
201
|
+
'import ./layout-expand.doc.mjs',
|
|
202
|
+
'import ./layout-grammar.doc.mjs',
|
|
203
|
+
'import ./layout.doc.mjs',
|
|
204
|
+
],
|
|
205
|
+
},
|
|
194
206
|
'clients/cli/commands/search.mjs': {
|
|
195
207
|
runs: SELF_DOCS,
|
|
196
208
|
sites: [
|
|
@@ -497,14 +497,7 @@ describe("the CLI's own docs tree", () => {
|
|
|
497
497
|
expect(
|
|
498
498
|
tree.get('cli')?.slots.map(slot => [slot.name, slot.children]),
|
|
499
499
|
).toEqual([
|
|
500
|
-
[
|
|
501
|
-
'guides',
|
|
502
|
-
[
|
|
503
|
-
'cli/component-lookups',
|
|
504
|
-
'cli/integrations',
|
|
505
|
-
'cli/writing-docs',
|
|
506
|
-
],
|
|
507
|
-
],
|
|
500
|
+
['guides', ['cli/integrations', 'cli/writing-docs']],
|
|
508
501
|
['reference', ['cli/commands', 'cli/api']],
|
|
509
502
|
]);
|
|
510
503
|
expect(tree.get('cli/api')?.slots[0].children).toEqual([
|
|
@@ -573,7 +566,6 @@ describe("the CLI's own docs tree", () => {
|
|
|
573
566
|
'commands',
|
|
574
567
|
]);
|
|
575
568
|
expect(inputs.docs.map(d => [d.name, d.placement?.parent])).toEqual([
|
|
576
|
-
['component-lookups', 'namespace:cli'],
|
|
577
569
|
['integrations', 'namespace:cli'],
|
|
578
570
|
['writing-docs', 'namespace:cli'],
|
|
579
571
|
]);
|
|
@@ -45,7 +45,7 @@ export function findManifestPaths(dir: string): string[];
|
|
|
45
45
|
* @param {string} file absolute manifest path
|
|
46
46
|
* @param {string} [label] used in error messages
|
|
47
47
|
* @param {{fresh?: boolean}} [options]
|
|
48
|
-
* @returns {Promise<{manifest: import('../../authoring/integration/type').AstryxIntegration, unknownKeys: string[], debug?: import('../../authoring/debug/type').DebugEventHandler, gapReport?: import('../../authoring/gap-report/type').GapReportHandler, gapReportError?: string,
|
|
48
|
+
* @returns {Promise<{manifest: import('../../authoring/integration/type').AstryxIntegration, unknownKeys: string[], debug?: import('../../authoring/debug/type').DebugEventHandler, gapReport?: import('../../authoring/gap-report/type').GapReportHandler, gapReportError?: string, agentDocsError?: string}>}
|
|
49
49
|
*/
|
|
50
50
|
export function loadManifest(file: string, label?: string, { fresh }?: {
|
|
51
51
|
fresh?: boolean;
|
|
@@ -55,8 +55,6 @@ export function loadManifest(file: string, label?: string, { fresh }?: {
|
|
|
55
55
|
debug?: import("../../authoring/debug/type").DebugEventHandler;
|
|
56
56
|
gapReport?: import("../../authoring/gap-report/type").GapReportHandler;
|
|
57
57
|
gapReportError?: string;
|
|
58
|
-
discover?: import("../../authoring/discover/type").DiscoverSource;
|
|
59
|
-
discoverError?: string;
|
|
60
58
|
agentDocsError?: string;
|
|
61
59
|
}>;
|
|
62
60
|
/**
|
|
@@ -156,9 +154,6 @@ export function loadIntegrations(specs?: string[], { cwd, fresh, resolveProvider
|
|
|
156
154
|
* validated package-owned handler from the `gapReport` NAMED export.
|
|
157
155
|
* @property {string} [__gapReportError] isolated named-handler validation error.
|
|
158
156
|
* Named exports are not manifest keys — see {@link loadManifest}.
|
|
159
|
-
* @property {import('../../authoring/discover/type').DiscoverSource} [__discover]
|
|
160
|
-
* validated catalog source from the `discover` NAMED export.
|
|
161
|
-
* @property {string} [__discoverError] isolated discover-source validation error.
|
|
162
157
|
*/
|
|
163
158
|
/** Conventional manifest basenames, in load-precedence order. */
|
|
164
159
|
export const MANIFEST_BASENAMES: string[];
|
|
@@ -250,12 +245,4 @@ export type LoadedIntegration = {
|
|
|
250
245
|
* Named exports are not manifest keys — see {@link loadManifest}.
|
|
251
246
|
*/
|
|
252
247
|
__gapReportError?: string | undefined;
|
|
253
|
-
/**
|
|
254
|
-
* validated catalog source from the `discover` NAMED export.
|
|
255
|
-
*/
|
|
256
|
-
__discover?: import("../../authoring/discover/type").DiscoverSource | undefined;
|
|
257
|
-
/**
|
|
258
|
-
* isolated discover-source validation error.
|
|
259
|
-
*/
|
|
260
|
-
__discoverError?: string | undefined;
|
|
261
248
|
};
|
|
@@ -29,7 +29,6 @@ import {
|
|
|
29
29
|
} from '../../authoring/integration/schema.mjs';
|
|
30
30
|
import {importUserModule, findPresentFiles} from '../fs/module-loader.mjs';
|
|
31
31
|
import {parseGapReportHandler} from '../../authoring/gap-report/parse.mjs';
|
|
32
|
-
import {parseDiscoverSource} from '../../authoring/discover/parse.mjs';
|
|
33
32
|
import {resolveProviders} from './provider-resolution.mjs';
|
|
34
33
|
|
|
35
34
|
/**
|
|
@@ -81,9 +80,6 @@ import {resolveProviders} from './provider-resolution.mjs';
|
|
|
81
80
|
* validated package-owned handler from the `gapReport` NAMED export.
|
|
82
81
|
* @property {string} [__gapReportError] isolated named-handler validation error.
|
|
83
82
|
* Named exports are not manifest keys — see {@link loadManifest}.
|
|
84
|
-
* @property {import('../../authoring/discover/type').DiscoverSource} [__discover]
|
|
85
|
-
* validated catalog source from the `discover` NAMED export.
|
|
86
|
-
* @property {string} [__discoverError] isolated discover-source validation error.
|
|
87
83
|
*/
|
|
88
84
|
|
|
89
85
|
/** Conventional manifest basenames, in load-precedence order. */
|
|
@@ -171,23 +167,6 @@ function parseGapReportHandlerExport(value, label) {
|
|
|
171
167
|
}
|
|
172
168
|
}
|
|
173
169
|
|
|
174
|
-
/**
|
|
175
|
-
* Parse the optional named discover source the same way: a malformed one is
|
|
176
|
-
* isolated from every other contribution and reported by `astryx discover`.
|
|
177
|
-
*
|
|
178
|
-
* @param {unknown} value
|
|
179
|
-
* @param {string} label
|
|
180
|
-
* @returns {{source?: import('../../authoring/discover/type').DiscoverSource, error?: string}}
|
|
181
|
-
*/
|
|
182
|
-
function parseDiscoverSourceExport(value, label) {
|
|
183
|
-
if (value === undefined) return {};
|
|
184
|
-
try {
|
|
185
|
-
return {source: parseDiscoverSource(value, `${label} named export "discover"`)};
|
|
186
|
-
} catch (error) {
|
|
187
|
-
return {error: error instanceof Error ? error.message : String(error)};
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
170
|
/**
|
|
192
171
|
* Load and validate a manifest module's default export, while isolating the
|
|
193
172
|
* optional `agentDocs` contribution from the manifest's other fields.
|
|
@@ -204,7 +183,7 @@ function parseDiscoverSourceExport(value, label) {
|
|
|
204
183
|
* @param {string} file absolute manifest path
|
|
205
184
|
* @param {string} [label] used in error messages
|
|
206
185
|
* @param {{fresh?: boolean}} [options]
|
|
207
|
-
* @returns {Promise<{manifest: import('../../authoring/integration/type').AstryxIntegration, unknownKeys: string[], debug?: import('../../authoring/debug/type').DebugEventHandler, gapReport?: import('../../authoring/gap-report/type').GapReportHandler, gapReportError?: string,
|
|
186
|
+
* @returns {Promise<{manifest: import('../../authoring/integration/type').AstryxIntegration, unknownKeys: string[], debug?: import('../../authoring/debug/type').DebugEventHandler, gapReport?: import('../../authoring/gap-report/type').GapReportHandler, gapReportError?: string, agentDocsError?: string}>}
|
|
208
187
|
*/
|
|
209
188
|
export async function loadManifest(
|
|
210
189
|
file,
|
|
@@ -215,7 +194,6 @@ export async function loadManifest(
|
|
|
215
194
|
const raw = mod?.default;
|
|
216
195
|
const baseManifest = parseIntegrationBase(raw, label);
|
|
217
196
|
const gapReport = parseGapReportHandlerExport(mod?.gapReport, label);
|
|
218
|
-
const discover = parseDiscoverSourceExport(mod?.discover, label);
|
|
219
197
|
const hasAgentDocs =
|
|
220
198
|
raw != null &&
|
|
221
199
|
typeof raw === 'object' &&
|
|
@@ -250,8 +228,6 @@ export async function loadManifest(
|
|
|
250
228
|
: undefined,
|
|
251
229
|
gapReport: gapReport.handler,
|
|
252
230
|
gapReportError: gapReport.error,
|
|
253
|
-
discover: discover.source,
|
|
254
|
-
discoverError: discover.error,
|
|
255
231
|
agentDocsError,
|
|
256
232
|
};
|
|
257
233
|
}
|
|
@@ -369,10 +345,6 @@ export async function loadLocalIntegration(packageDir, {fresh = false} = {}) {
|
|
|
369
345
|
let gapReportHandler;
|
|
370
346
|
/** @type {string | undefined} */
|
|
371
347
|
let gapReportError;
|
|
372
|
-
/** @type {import('../../authoring/discover/type').DiscoverSource | undefined} */
|
|
373
|
-
let discoverSource;
|
|
374
|
-
/** @type {string | undefined} */
|
|
375
|
-
let discoverError;
|
|
376
348
|
/** @type {string | undefined} */
|
|
377
349
|
let agentDocsError;
|
|
378
350
|
try {
|
|
@@ -382,8 +354,6 @@ export async function loadLocalIntegration(packageDir, {fresh = false} = {}) {
|
|
|
382
354
|
debug: debugHandler,
|
|
383
355
|
gapReport: gapReportHandler,
|
|
384
356
|
gapReportError,
|
|
385
|
-
discover: discoverSource,
|
|
386
|
-
discoverError,
|
|
387
357
|
agentDocsError,
|
|
388
358
|
} = await loadManifest(manifestFile, `Integration ${spec}`, {fresh}));
|
|
389
359
|
} catch (err) {
|
|
@@ -427,8 +397,6 @@ export async function loadLocalIntegration(packageDir, {fresh = false} = {}) {
|
|
|
427
397
|
__debug: debugHandler,
|
|
428
398
|
__gapReport: gapReportHandler,
|
|
429
399
|
__gapReportError: gapReportError,
|
|
430
|
-
__discover: discoverSource,
|
|
431
|
-
__discoverError: discoverError,
|
|
432
400
|
__spec: spec,
|
|
433
401
|
__packageDir: packageDir,
|
|
434
402
|
__packageExports: pkg.exports ?? null,
|
|
@@ -480,10 +448,6 @@ export async function loadIntegrations(
|
|
|
480
448
|
let gapReportHandler;
|
|
481
449
|
/** @type {string | undefined} */
|
|
482
450
|
let gapReportError;
|
|
483
|
-
/** @type {import('../../authoring/discover/type').DiscoverSource | undefined} */
|
|
484
|
-
let discoverSource;
|
|
485
|
-
/** @type {string | undefined} */
|
|
486
|
-
let discoverError;
|
|
487
451
|
/** @type {string | undefined} */
|
|
488
452
|
let agentDocsError;
|
|
489
453
|
try {
|
|
@@ -493,8 +457,6 @@ export async function loadIntegrations(
|
|
|
493
457
|
debug: debugHandler,
|
|
494
458
|
gapReport: gapReportHandler,
|
|
495
459
|
gapReportError,
|
|
496
|
-
discover: discoverSource,
|
|
497
|
-
discoverError,
|
|
498
460
|
agentDocsError,
|
|
499
461
|
} = await loadManifest(manifestFile, `Integration ${spec}`, {fresh}));
|
|
500
462
|
} catch (err) {
|
|
@@ -545,8 +507,6 @@ export async function loadIntegrations(
|
|
|
545
507
|
__debug: debugHandler,
|
|
546
508
|
__gapReport: gapReportHandler,
|
|
547
509
|
__gapReportError: gapReportError,
|
|
548
|
-
__discover: discoverSource,
|
|
549
|
-
__discoverError: discoverError,
|
|
550
510
|
__spec: spec,
|
|
551
511
|
__packageDir: packageDir,
|
|
552
512
|
__packageExports: pkg.exports ?? null,
|
|
@@ -785,34 +785,3 @@ describe('the `gapReport` named export', () => {
|
|
|
785
785
|
expect(loaded.__unknownKeys).toEqual(['gapReport']);
|
|
786
786
|
});
|
|
787
787
|
});
|
|
788
|
-
|
|
789
|
-
describe('the `discover` named export', () => {
|
|
790
|
-
it('is carried out of the manifest module as __discover', async () => {
|
|
791
|
-
writeManifestPackage(tmpDir, {
|
|
792
|
-
body:
|
|
793
|
-
`export async function discover() { return {schemaVersion: 1}; }\n` +
|
|
794
|
-
`export default {issuesUrl: 'https://example.com/i'};\n`,
|
|
795
|
-
});
|
|
796
|
-
|
|
797
|
-
const [loaded] = await loadIntegrations(['@acme/widgets'], {cwd: tmpDir});
|
|
798
|
-
|
|
799
|
-
expect(typeof loaded.__discover).toBe('function');
|
|
800
|
-
expect(loaded.__discoverError).toBeUndefined();
|
|
801
|
-
expect(loaded.__unknownKeys).toEqual([]);
|
|
802
|
-
expect(loaded.issuesUrl).toBe('https://example.com/i');
|
|
803
|
-
});
|
|
804
|
-
|
|
805
|
-
it('keeps the manifest and records why when the export is not a function', async () => {
|
|
806
|
-
writeManifestPackage(tmpDir, {
|
|
807
|
-
body:
|
|
808
|
-
`export const discover = {url: 'https://example.com/catalog.json'};\n` +
|
|
809
|
-
`export default {issuesUrl: 'https://example.com/i'};\n`,
|
|
810
|
-
});
|
|
811
|
-
|
|
812
|
-
const [loaded] = await loadIntegrations(['@acme/widgets'], {cwd: tmpDir});
|
|
813
|
-
|
|
814
|
-
expect(loaded.__discover).toBeUndefined();
|
|
815
|
-
expect(loaded.__discoverError).toContain('must be a function');
|
|
816
|
-
expect(loaded.issuesUrl).toBe('https://example.com/i');
|
|
817
|
-
});
|
|
818
|
-
});
|
|
@@ -37,11 +37,6 @@ export const doc = {
|
|
|
37
37
|
description:
|
|
38
38
|
'The component catalog grouped by category: `detail` (the level: names | compact | full) and `components`, the grouped map of names entries ({name, package, and optional canonical import for integrations}), brief entries, or a full ComponentDoc per entry.',
|
|
39
39
|
},
|
|
40
|
-
{
|
|
41
|
-
value: 'component.batch',
|
|
42
|
-
description:
|
|
43
|
-
'The component specialization of the shared `BatchResponse` and `BatchRow` types. An explicit programmatic selector array, or two or more CLI selectors, returns one ordered receipt: `count` plus `results`, one row per selector including duplicates. Every row carries `selector` and `status` (found | not_found | ambiguous | error). A found row carries `result`, the same {type, data} response as one selector. An ambiguous row carries `code`, `error`, and `candidates` ({package, component, kind, installed}). Other failed rows carry `code`, `error`, and optional `suggestions` ({name, reason}).',
|
|
44
|
-
},
|
|
45
40
|
{
|
|
46
41
|
value: 'component.detail',
|
|
47
42
|
description:
|
|
@@ -109,27 +104,21 @@ export const doc = {
|
|
|
109
104
|
{
|
|
110
105
|
value: 'discover.list',
|
|
111
106
|
description:
|
|
112
|
-
'The
|
|
107
|
+
'The configured external packages (name, category, components, version, description); when empty it carries meta.configured to tell "nothing configured" from "nothing discovered".',
|
|
113
108
|
},
|
|
114
109
|
{
|
|
115
110
|
value: 'discover.detail',
|
|
116
|
-
description:
|
|
117
|
-
'One package, for an @scope/name or @scope/name@version query: what the shown version adds, whether the project has it, and, when a source knows the package, its versions, latest release, and the command that adds it.',
|
|
111
|
+
description: 'A single external package entry, for an @scope/name query.',
|
|
118
112
|
},
|
|
119
113
|
{
|
|
120
114
|
value: 'discover.detail.doc',
|
|
121
115
|
description:
|
|
122
|
-
'The validated ComponentDoc for one
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
value: 'discover.item',
|
|
126
|
-
description:
|
|
127
|
-
'One item that is not an installed component, for an @scope/name/<item> query: its kind, name, package, version, and whether the project has the package.',
|
|
116
|
+
'The validated ComponentDoc for one external component: an @scope/name/Component query, or a free-text term resolving to exactly one component.',
|
|
128
117
|
},
|
|
129
118
|
{
|
|
130
119
|
value: 'discover.search',
|
|
131
120
|
description:
|
|
132
|
-
'The echoed query plus
|
|
121
|
+
'The echoed query plus the matching {package, component} pairs, when a free-text term matches several components.',
|
|
133
122
|
},
|
|
134
123
|
|
|
135
124
|
// search
|
|
@@ -318,5 +307,22 @@ export const doc = {
|
|
|
318
307
|
description:
|
|
319
308
|
'The integration identity, structural issues, and Core doc overlaps. Each finding includes `severity` (`info` | `error`) and `relationship` (`replaces` | `extends` | `accidental`).',
|
|
320
309
|
},
|
|
310
|
+
|
|
311
|
+
// layout (XLE/XLO)
|
|
312
|
+
{
|
|
313
|
+
value: 'layout.expand',
|
|
314
|
+
description:
|
|
315
|
+
'The expansion: parsed form, generated TSX code, componentsUsed, states (count of useState hooks scaffolded), todos, blocksReferenced (each {name, mode}), warnings, and written (the output path, or null when nothing was written).',
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
value: 'layout.check',
|
|
319
|
+
description:
|
|
320
|
+
'The validation result: a valid flag, the detected form, errors (each with line/col, message, formatted text, and suggestions), warnings, and the expression re-printed in both canonical surfaces (compact and outline).',
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
value: 'layout.grammar',
|
|
324
|
+
description:
|
|
325
|
+
"The XLE/XLO grammar cheatsheet: a text field with the full reference plus an aliases map (short name → canonical component) generated from this install's registry.",
|
|
326
|
+
},
|
|
321
327
|
],
|
|
322
328
|
};
|
|
@@ -53,29 +53,6 @@ function expectNamed(type, fields) {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
describe('response-types EnumDoc names every field', () => {
|
|
56
|
-
it('component.batch names every row field and state', async () => {
|
|
57
|
-
const res = await data(['component', 'Button', 'Badge']);
|
|
58
|
-
expect(res.type).toBe('component.batch');
|
|
59
|
-
expect(describedAs('component.batch')).toMatch(/BatchResponse.*BatchRow/);
|
|
60
|
-
expectNamed('component.batch', [
|
|
61
|
-
'count',
|
|
62
|
-
'results',
|
|
63
|
-
'selector',
|
|
64
|
-
'status',
|
|
65
|
-
'result',
|
|
66
|
-
'code',
|
|
67
|
-
'error',
|
|
68
|
-
'candidates',
|
|
69
|
-
'package',
|
|
70
|
-
'component',
|
|
71
|
-
'kind',
|
|
72
|
-
'installed',
|
|
73
|
-
'suggestions',
|
|
74
|
-
'name',
|
|
75
|
-
'reason',
|
|
76
|
-
]);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
56
|
it('component.detail: the ownership fields and parentDoc', async () => {
|
|
80
57
|
const res = await data(['component', 'HStack']);
|
|
81
58
|
expect(res.type).toBe('component.detail');
|