@astryxdesign/cli 0.6.4-canary.06c8fa3 → 0.6.4-canary.0e1fbdb
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 +49 -40
- package/api/build/build.doc.mjs +6 -1
- package/api/build/build.test.mjs +22 -0
- package/api/build/kit/kit.mjs +44 -5
- package/api/component/component.doc.mjs +14 -7
- package/api/docs/_adapter.d.mts +8 -3
- package/api/docs/_adapter.mjs +14 -6
- package/api/docs/docOverlays.test.mjs +27 -1
- package/api/docs/docs.doc.mjs +2 -2
- package/api/doctor/doctor.doc.mjs +17 -8
- package/api/doctor/doctor.type.d.mts +1 -1
- package/api/doctor/doctor.type.mjs +1 -1
- package/api/gap-report/gap-report.doc.mjs +19 -10
- package/api/hook/hook.doc.mjs +6 -3
- package/api/index.d.mts +2 -0
- package/api/index.mjs +3 -1
- package/api/init/init.doc.mjs +17 -12
- package/api/integration/add-theme.mjs +22 -1
- package/api/integration/add-theme.test.mjs +34 -0
- package/api/integration/authoring-checks.mjs +2 -2
- package/api/integration/integrationPackCheck.doc.mjs +3 -3
- package/api/integration/pack-check.lifecycle-output.test.mjs +2 -0
- package/api/integration/pack-check.mjs +54 -6
- package/api/integration/pack-check.test.mjs +90 -0
- package/api/integration/pack-check.type.mjs +1 -1
- package/api/json/assertResponse.doc.mjs +1 -1
- package/api/json/index.ts +1 -0
- package/api/json/isError.doc.mjs +1 -1
- 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.d.mts +27 -1
- package/api/search/search.doc.mjs +2 -2
- package/api/search/search.mjs +228 -16
- package/api/swizzle/swizzle.doc.mjs +7 -5
- package/api/template/copy/copy.mjs +1 -1
- package/api/template/copy/copy.test.mjs +9 -0
- package/api/template/template-integration.test.mjs +65 -1
- package/api/template/template.doc.mjs +2 -1
- package/api/template/template.mjs +1 -1
- package/api/theme/generateTonalPalette.doc.mjs +1 -2
- package/api/theme/listThemes.doc.mjs +1 -1
- package/api/theme/themeAdd.doc.mjs +9 -10
- package/api/theme/themeBuild.doc.mjs +13 -13
- package/api/theme/themeList.doc.mjs +1 -1
- package/api/theme/themeListAvailable.doc.mjs +2 -1
- package/api/theme/themePaletteGenerate.doc.mjs +15 -8
- package/api/theme/themeTargets.doc.mjs +3 -2
- package/api/theme/themeTemplate.doc.mjs +2 -1
- package/api/upgrade/run/run.mjs +1 -1
- package/api/upgrade/upgrade.doc.mjs +24 -22
- package/assets/docs/README.md +4 -2
- package/assets/docs/browser-support.doc.mjs +11 -11
- package/assets/docs/color.doc.mjs +8 -2
- package/assets/docs/elevation.doc.mjs +6 -4
- package/assets/docs/getting-started.doc.mjs +5 -16
- package/assets/docs/icons.doc.mjs +2 -21
- package/assets/docs/illustrations.doc.mjs +7 -15
- package/assets/docs/layout.doc.dense.mjs +130 -82
- package/assets/docs/layout.doc.mjs +133 -77
- package/assets/docs/migration.doc.mjs +19 -21
- package/assets/docs/motion.doc.mjs +16 -3
- package/assets/docs/principles.doc.dense.mjs +5 -5
- package/assets/docs/principles.doc.mjs +8 -0
- package/assets/docs/principles.doc.zh.mjs +6 -6
- package/assets/docs/shape.doc.mjs +8 -3
- package/assets/docs/spacing.doc.mjs +7 -2
- package/assets/docs/styling-libraries.doc.mjs +6 -2
- package/assets/docs/styling.doc.mjs +19 -23
- package/assets/docs/theme.doc.dense.mjs +58 -18
- package/assets/docs/theme.doc.mjs +56 -46
- package/assets/docs/theme.doc.zh.mjs +9 -8
- package/assets/docs/tokens.doc.dense.mjs +2 -2
- package/assets/docs/tokens.doc.mjs +389 -8
- package/assets/docs/tokens.doc.zh.mjs +2 -2
- package/assets/docs/tree/add-a-component.doc.mjs +75 -0
- package/assets/docs/tree/add-a-theme.doc.mjs +85 -0
- package/assets/docs/tree/add-a-topic.doc.mjs +144 -0
- package/assets/docs/tree/agent-guidance.doc.mjs +138 -0
- package/assets/docs/tree/block-template.doc.mjs +130 -0
- package/assets/docs/tree/build-the-template.doc.mjs +28 -0
- package/assets/docs/tree/building-blocks.doc.mjs +46 -0
- package/assets/docs/tree/check-your-docs.doc.mjs +137 -0
- package/assets/docs/tree/checks.doc.mjs +119 -0
- package/assets/docs/tree/codemods.doc.mjs +147 -0
- package/assets/docs/tree/component-family.doc.mjs +113 -0
- package/assets/docs/tree/component-imports.doc.mjs +69 -0
- package/assets/docs/tree/components.doc.mjs +23 -0
- package/assets/docs/tree/configuration.doc.mjs +23 -0
- package/assets/docs/tree/debug-and-gap-reports.doc.mjs +182 -0
- package/assets/docs/tree/define-the-theme.doc.mjs +118 -0
- package/assets/docs/tree/describe-the-component.doc.mjs +57 -0
- package/assets/docs/tree/docs.doc.mjs +21 -0
- package/assets/docs/tree/document-the-template.doc.mjs +28 -0
- package/assets/docs/tree/document-the-theme.doc.mjs +68 -0
- package/assets/docs/tree/export-template-assets.doc.mjs +147 -0
- package/assets/docs/tree/extend-or-replace.doc.mjs +103 -0
- package/assets/docs/tree/fonts-and-assets.doc.mjs +106 -0
- package/assets/docs/tree/generate-a-palette.doc.mjs +66 -0
- package/assets/docs/tree/grade-template-with-agent.doc.mjs +105 -0
- package/assets/docs/tree/help.doc.mjs +16 -0
- package/assets/docs/tree/integrations.doc.mjs +25 -470
- package/assets/docs/tree/links.doc.mjs +98 -0
- package/assets/docs/tree/package-and-test.doc.mjs +32 -0
- package/assets/docs/tree/page-template.doc.mjs +71 -0
- package/assets/docs/tree/publishing.doc.mjs +111 -0
- package/assets/docs/tree/quick-start.doc.mjs +272 -0
- package/assets/docs/tree/replace-a-core-component.doc.mjs +104 -0
- package/assets/docs/tree/replace-a-core-template.doc.mjs +172 -0
- package/assets/docs/tree/sections-and-placement.doc.mjs +108 -0
- package/assets/docs/tree/see-it-in-an-app.doc.mjs +59 -0
- package/assets/docs/tree/ship.doc.mjs +16 -0
- package/assets/docs/tree/short-and-findable.doc.mjs +108 -0
- package/assets/docs/tree/single-component.doc.mjs +165 -0
- package/assets/docs/tree/start-a-template.doc.mjs +143 -0
- package/assets/docs/tree/subcomponent.doc.mjs +115 -0
- package/assets/docs/tree/template-assets.doc.mjs +64 -0
- package/assets/docs/tree/template-doc-overview.doc.mjs +109 -0
- package/assets/docs/tree/template-fonts.doc.mjs +102 -0
- package/assets/docs/tree/template-grading-rubric.doc.mjs +452 -0
- package/assets/docs/tree/template-icons.doc.mjs +97 -0
- package/assets/docs/tree/template-images-media.doc.mjs +127 -0
- package/assets/docs/tree/template-styles.doc.mjs +93 -0
- package/assets/docs/tree/templates.doc.mjs +34 -0
- package/assets/docs/tree/test-in-an-app.doc.mjs +115 -0
- package/assets/docs/tree/test-template-in-app.doc.mjs +128 -0
- package/assets/docs/tree/themes.doc.mjs +39 -0
- package/assets/docs/tree/troubleshooting.doc.mjs +149 -0
- package/assets/docs/tree/upgrading.doc.mjs +103 -0
- package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +51 -0
- package/assets/docs/tree/verify-packed-template.doc.mjs +77 -0
- package/assets/docs/tree/versioning.doc.mjs +161 -0
- package/assets/docs/tree/write-good-templates.doc.mjs +64 -0
- package/assets/docs/tree/write-the-template-file.doc.mjs +154 -0
- package/assets/docs/typography.doc.mjs +24 -4
- package/assets/docs/working-with-ai.doc.mjs +30 -22
- package/authoring/config/config.doc.mjs +2 -2
- package/authoring/config/type.ts +2 -2
- package/authoring/doctypes/_schema.d.mts +3 -2
- package/authoring/doctypes/_schema.mjs +6 -0
- package/authoring/doctypes/base/graph-fields.doc.mjs +3 -3
- package/authoring/doctypes/base/type.ts +4 -2
- package/authoring/doctypes/command/command.doc.mjs +1 -1
- package/authoring/doctypes/command/type.ts +1 -1
- package/authoring/doctypes/component/component.doc.mjs +6 -0
- package/authoring/doctypes/component/type.ts +8 -0
- package/authoring/doctypes/reference/reference.doc.mjs +7 -0
- package/authoring/doctypes/reference/type.ts +5 -0
- package/authoring/doctypes/schema/schema.doc.mjs +2 -2
- package/authoring/doctypes/template/template.doc.mjs +1 -1
- package/authoring/doctypes/template/type.ts +2 -2
- package/authoring/integration/integration.doc.mjs +12 -10
- package/clients/cli/command-result-coverage.test.mjs +7 -7
- package/clients/cli/commands/component.doc.mjs +4 -3
- package/clients/cli/commands/debug-result-summary.test.mjs +2 -2
- package/clients/cli/commands/docs.doc.mjs +1 -1
- package/clients/cli/commands/docs.mjs +60 -17
- package/clients/cli/commands/doctor-integration-docs.doc.mjs +3 -2
- package/clients/cli/commands/doctor-integration.test.mjs +53 -0
- package/clients/cli/commands/doctor.doc.mjs +3 -1
- package/clients/cli/commands/doctor.mjs +49 -5
- package/clients/cli/commands/gap-report.doc.mjs +10 -9
- package/clients/cli/commands/init.doc.mjs +9 -6
- package/clients/cli/commands/integration-add.doc.mjs +9 -9
- package/clients/cli/commands/integration-authoring.test.mjs +61 -10
- package/clients/cli/commands/integration-pack.doc.mjs +5 -9
- package/clients/cli/commands/integration-real-world.test.mjs +1 -1
- package/clients/cli/commands/integration-verify.doc.mjs +22 -0
- package/clients/cli/commands/integration.doc.mjs +4 -4
- package/clients/cli/commands/integration.mjs +74 -43
- 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/manifest.doc.mjs +1 -1
- package/clients/cli/commands/search.doc.mjs +10 -3
- package/clients/cli/commands/search.mjs +21 -2
- package/clients/cli/commands/search.test.mjs +21 -4
- package/clients/cli/commands/swizzle.doc.mjs +1 -1
- package/clients/cli/commands/template.doc.mjs +1 -1
- package/clients/cli/commands/text-json-parity.test.mjs +24 -1
- package/clients/cli/commands/theme-add.doc.mjs +1 -1
- package/clients/cli/commands/theme-palette-generate.doc.mjs +3 -2
- package/clients/cli/commands/theme-palette.doc.mjs +1 -2
- package/clients/cli/commands/theme-targets.doc.mjs +2 -2
- package/clients/cli/commands/theme.doc.mjs +2 -1
- package/clients/cli/commands/upgrade.doc.mjs +62 -3
- package/clients/cli/index.mjs +32 -6
- package/clients/cli/lib/define-command.mjs +28 -4
- package/clients/cli/lib/define-command.test.mjs +54 -0
- package/clients/cli/lib/exit-codes.test.mjs +25 -2
- package/clients/cli/lib/json-shim.test.mjs +20 -6
- package/clients/cli/lib/manifest.mjs +23 -5
- package/foundation/agent-docs/agent-docs.mjs +1 -1
- package/foundation/discovery/authoring-self-docs.test.mjs +6 -2
- package/foundation/discovery/cli-self-docs.mjs +16 -2
- package/foundation/discovery/cli-self-docs.test.mjs +20 -0
- package/foundation/discovery/docs-discovery.mjs +5 -1
- package/foundation/discovery/docs-discovery.test.mjs +21 -0
- package/foundation/discovery/docs-section-key.d.mts +1 -1
- package/foundation/discovery/docs-section-key.mjs +1 -1
- package/foundation/discovery/template-adapter.mjs +1 -1
- package/foundation/doc-compiler/doc-loads.test.mjs +15 -2
- package/foundation/doc-compiler/inputs.test.mjs +0 -1
- package/foundation/doc-compiler/tree.d.mts +4 -0
- package/foundation/doc-compiler/tree.mjs +6 -1
- package/foundation/integrations/cli-requirement.d.mts +26 -6
- package/foundation/integrations/cli-requirement.mjs +46 -11
- package/foundation/integrations/cli-requirement.test.mjs +7 -2
- package/foundation/integrations/contribution-inventory.mjs +1 -1
- package/foundation/response/error-codes.doc.mjs +6 -8
- package/foundation/response/error-codes.test.mjs +30 -5
- package/foundation/response/response-types.doc.d.mts +4 -3
- package/foundation/response/response-types.doc.mjs +42 -6
- package/foundation/response/response.doc.mjs +11 -10
- 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/docs/docs.test.mjs +0 -245
- package/api/docs/integration-tree.test.mjs +0 -555
- package/api/docs/integrationDocs.test.mjs +0 -314
- package/api/search/search.test.mjs +0 -530
- package/assets/docs/tree/integrations.test.mjs +0 -62
- package/assets/docs/tree/writing-docs.doc.mjs +0 -286
- package/clients/cli/commands/docs.test.mjs +0 -323
- package/foundation/agent-docs/agent-docs.test.mjs +0 -1159
- package/foundation/doc-compiler/tree.test.mjs +0 -606
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file layout command — thin CLI wrapper around api/layout.mjs.
|
|
5
|
+
*
|
|
6
|
+
* Subcommands:
|
|
7
|
+
* astryx layout expand "<expr>" [path] compressed expression → validated TSX
|
|
8
|
+
* astryx layout check "<expr>" validate + echo both canonical surfaces
|
|
9
|
+
* astryx layout grammar agent cheatsheet (alias table is branch-generated)
|
|
10
|
+
*
|
|
11
|
+
* The expression argument may also come from --file or stdin (`-`),
|
|
12
|
+
* which is how multi-line outline (XLO) input usually arrives.
|
|
13
|
+
*
|
|
14
|
+
* The command surface (group + subcommand descriptions, args, flags) is sourced
|
|
15
|
+
* from the colocated CommandDocs via `defineCommand`; this file supplies only the
|
|
16
|
+
* actions.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import * as fs from 'node:fs';
|
|
20
|
+
import * as path from 'node:path';
|
|
21
|
+
import {jsonOut} from '../../../foundation/response/json.mjs';
|
|
22
|
+
import {emit, section, text, list, record, code, WARN} from '../formatters/index.mjs';
|
|
23
|
+
import {cliError} from '../lib/cli-error.mjs';
|
|
24
|
+
import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
|
|
25
|
+
import {layoutExpand, layoutCheck, layoutGrammar} from '../../../api/layout/layout.mjs';
|
|
26
|
+
import {defineCommand} from '../lib/define-command.mjs';
|
|
27
|
+
import {doc as layoutGroup} from './layout.doc.mjs';
|
|
28
|
+
import {doc as layoutExpandCommand} from './layout-expand.doc.mjs';
|
|
29
|
+
import {doc as layoutCheckCommand} from './layout-check.doc.mjs';
|
|
30
|
+
import {doc as layoutGrammarCommand} from './layout-grammar.doc.mjs';
|
|
31
|
+
import {doc as layoutExpandFn} from '../../../api/layout/layoutExpand.doc.mjs';
|
|
32
|
+
import {doc as layoutCheckFn} from '../../../api/layout/layoutCheck.doc.mjs';
|
|
33
|
+
import {doc as layoutGrammarFn} from '../../../api/layout/layoutGrammar.doc.mjs';
|
|
34
|
+
import {NO_RESULT_SET, resultSet} from '../../../foundation/debug/index.mjs';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The api layer's @returns for these functions widen the `type` discriminator
|
|
38
|
+
* to `string`, so annotate the command-local result with the precise response
|
|
39
|
+
* shapes from the colocated api/layout/layout.type.mjs so narrowing + jsonOut
|
|
40
|
+
* typecheck.
|
|
41
|
+
*
|
|
42
|
+
* @typedef {import('../../../api/layout/layout.type.mjs').LayoutExpandResponse} LayoutExpandResponse
|
|
43
|
+
* @typedef {import('../../../api/layout/layout.type.mjs').LayoutCheckResponse} LayoutCheckResponse
|
|
44
|
+
* @typedef {import('../../../api/layout/layout.type.mjs').LayoutGrammarResponse} LayoutGrammarResponse
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @typedef {object} LayoutExpandOptions
|
|
49
|
+
* @property {string} [file]
|
|
50
|
+
* @property {'compact'|'outline'|'auto'} [form]
|
|
51
|
+
* @property {string} [name]
|
|
52
|
+
* @property {boolean} [loose]
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @typedef {object} LayoutCheckOptions
|
|
57
|
+
* @property {string} [file]
|
|
58
|
+
* @property {'compact'|'outline'|'auto'} [form]
|
|
59
|
+
* @property {boolean} [loose]
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
/** The largest layout expression read from --file or stdin. */
|
|
63
|
+
const MAX_EXPRESSION_BYTES = 5 * 1024 * 1024; // 5 MB
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Resolve the expression from arg, --file, or stdin ('-').
|
|
67
|
+
* @param {string} [expr]
|
|
68
|
+
* @param {{file?: string}} [options]
|
|
69
|
+
* @returns {Promise<string>}
|
|
70
|
+
*/
|
|
71
|
+
async function readExpression(expr, options = {}) {
|
|
72
|
+
if (options.file) {
|
|
73
|
+
// Validate the file exists + is a regular file + is reasonably sized.
|
|
74
|
+
// We intentionally do NOT confine the read path (the user running the CLI
|
|
75
|
+
// controls --file; this is a read, not a write). The size cap prevents OOM
|
|
76
|
+
// from infinite streams like /dev/zero.
|
|
77
|
+
const filePath = path.resolve(process.cwd(), options.file);
|
|
78
|
+
const stat = fs.statSync(filePath, {throwIfNoEntry: false});
|
|
79
|
+
if (!stat || !stat.isFile()) {
|
|
80
|
+
cliError(`File not found: ${options.file}`, {
|
|
81
|
+
code: ERROR_CODES.ERR_FILE_NOT_FOUND,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
if (stat.size > MAX_EXPRESSION_BYTES) {
|
|
85
|
+
cliError(
|
|
86
|
+
`File "${options.file}" is too large (${(stat.size / 1024 / 1024).toFixed(1)} MB, max 5 MB)`,
|
|
87
|
+
{code: ERROR_CODES.ERR_FILE_NOT_FOUND},
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
return fs.readFileSync(filePath, 'utf-8');
|
|
92
|
+
} catch (e) {
|
|
93
|
+
const errno = /** @type {NodeJS.ErrnoException} */ (e);
|
|
94
|
+
if (errno && errno.code === 'ENOENT') {
|
|
95
|
+
// A --file pointing at a missing file is foreseeable — surface a
|
|
96
|
+
// stable code, not the raw ENOENT errno (and no stack in human mode).
|
|
97
|
+
cliError(`File not found: ${options.file}`, {
|
|
98
|
+
code: ERROR_CODES.ERR_FILE_NOT_FOUND,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
throw e;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (expr === '-') {
|
|
105
|
+
// Capped like --file: an endless stream must not be buffered whole.
|
|
106
|
+
/** @type {Buffer[]} */
|
|
107
|
+
const chunks = [];
|
|
108
|
+
let size = 0;
|
|
109
|
+
for await (const chunk of process.stdin) {
|
|
110
|
+
size += /** @type {Buffer} */ (chunk).length;
|
|
111
|
+
if (size > MAX_EXPRESSION_BYTES) {
|
|
112
|
+
cliError('The layout expression on stdin is too large (max 5 MB)', {
|
|
113
|
+
code: ERROR_CODES.ERR_INVALID_ARGUMENT,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
chunks.push(/** @type {Buffer} */ (chunk));
|
|
117
|
+
}
|
|
118
|
+
return Buffer.concat(chunks).toString('utf-8');
|
|
119
|
+
}
|
|
120
|
+
return expr ?? '';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @param {import('commander').Command} program
|
|
125
|
+
*/
|
|
126
|
+
export function registerLayout(program) {
|
|
127
|
+
const layoutCmd = defineCommand(program, layoutGroup);
|
|
128
|
+
|
|
129
|
+
defineCommand(layoutCmd, layoutExpandCommand, {
|
|
130
|
+
fn: layoutExpandFn,
|
|
131
|
+
action: async (/** @type {string} */ expression, /** @type {string} */ targetPath, /** @type {LayoutExpandOptions} */ options) => {
|
|
132
|
+
const json = program.opts().json || false;
|
|
133
|
+
const source = await readExpression(expression, options);
|
|
134
|
+
if (!source || source.trim() === '') {
|
|
135
|
+
return cliError(
|
|
136
|
+
'No layout expression given — pass it as an argument, via --file, or on stdin',
|
|
137
|
+
{code: ERROR_CODES.ERR_MISSING_ARGUMENT},
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
/** @type {LayoutExpandResponse} */
|
|
141
|
+
let result;
|
|
142
|
+
try {
|
|
143
|
+
result = /** @type {LayoutExpandResponse} */ (await layoutExpand(source, {
|
|
144
|
+
targetPath,
|
|
145
|
+
form: options.form,
|
|
146
|
+
loose: options.loose || false,
|
|
147
|
+
name: options.name,
|
|
148
|
+
cwd: process.cwd(),
|
|
149
|
+
}));
|
|
150
|
+
} catch (e) {
|
|
151
|
+
const err = /** @type {import('../../../api/error.mjs').AstryxError} */ (e);
|
|
152
|
+
return cliError(err.message, {suggestions: err.suggestions || [], code: err.code});
|
|
153
|
+
}
|
|
154
|
+
// Expanding turns an expression into TSX — a transformation, not a
|
|
155
|
+
// lookup. What it produced is in the output; there is no set to count.
|
|
156
|
+
if (json) {
|
|
157
|
+
jsonOut(result);
|
|
158
|
+
return NO_RESULT_SET;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** @type {import('../formatters/index.mjs').Block[]} */
|
|
162
|
+
const out = [];
|
|
163
|
+
if (result.data.warnings.length > 0) {
|
|
164
|
+
out.push(text(result.data.warnings.map(w => `${WARN} ${w}`).join('\n')));
|
|
165
|
+
}
|
|
166
|
+
if (result.data.written) {
|
|
167
|
+
out.push(
|
|
168
|
+
text(`[ok] Expanded to ${result.data.written}`),
|
|
169
|
+
// Field names are the JSON keys; todos are summarised, not listed.
|
|
170
|
+
record(
|
|
171
|
+
{componentsUsed: result.data.componentsUsed, todos: result.data.todos},
|
|
172
|
+
{
|
|
173
|
+
format: {
|
|
174
|
+
todos: (/** @type {string[]} */ todos) =>
|
|
175
|
+
`${todos.length} (search for "TODO(xle)")`,
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
),
|
|
179
|
+
);
|
|
180
|
+
} else {
|
|
181
|
+
// Raw expanded TSX (no target path) — preformatted, emitted verbatim.
|
|
182
|
+
out.push(code(result.data.code));
|
|
183
|
+
}
|
|
184
|
+
emit(...out);
|
|
185
|
+
return NO_RESULT_SET;
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
defineCommand(layoutCmd, layoutCheckCommand, {
|
|
190
|
+
fn: layoutCheckFn,
|
|
191
|
+
action: async (/** @type {string} */ expression, /** @type {LayoutCheckOptions} */ options) => {
|
|
192
|
+
const json = program.opts().json || false;
|
|
193
|
+
const source = await readExpression(expression, options);
|
|
194
|
+
if (!source || source.trim() === '') {
|
|
195
|
+
return cliError(
|
|
196
|
+
'No layout expression given — pass it as an argument, via --file, or on stdin',
|
|
197
|
+
{code: ERROR_CODES.ERR_MISSING_ARGUMENT},
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
/** @type {LayoutCheckResponse} */
|
|
201
|
+
let result;
|
|
202
|
+
try {
|
|
203
|
+
result = /** @type {LayoutCheckResponse} */ (await layoutCheck(source, {
|
|
204
|
+
form: options.form,
|
|
205
|
+
loose: options.loose || false,
|
|
206
|
+
cwd: process.cwd(),
|
|
207
|
+
}));
|
|
208
|
+
} catch (e) {
|
|
209
|
+
const err = /** @type {import('../../../api/error.mjs').AstryxError} */ (e);
|
|
210
|
+
return cliError(err.message, {suggestions: err.suggestions || [], code: err.code});
|
|
211
|
+
}
|
|
212
|
+
// Exit code is the contract and must NOT depend on --json vs human: an
|
|
213
|
+
// invalid (but parseable) layout exits 1 in BOTH modes so `layout check`
|
|
214
|
+
// works as a CI gate / agent check without parsing stdout. Decide it
|
|
215
|
+
// before the JSON return (parity with doctor integration validate).
|
|
216
|
+
if (!result.data.valid) process.exitCode = 1;
|
|
217
|
+
|
|
218
|
+
// A verdict on one expression: valid or not, with the errors that made
|
|
219
|
+
// it so. Nothing was looked up.
|
|
220
|
+
if (json) {
|
|
221
|
+
jsonOut(result);
|
|
222
|
+
return NO_RESULT_SET;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const {valid, form, errors, warnings, compact, outline} = result.data;
|
|
226
|
+
if (!valid) {
|
|
227
|
+
// Each error: the formatted issue, with a hanging "did you mean" line.
|
|
228
|
+
const items = errors.map(e =>
|
|
229
|
+
e.suggestions && e.suggestions.length > 0
|
|
230
|
+
? [e.formatted, `did you mean: ${e.suggestions.join(', ')}?`]
|
|
231
|
+
: [e.formatted],
|
|
232
|
+
);
|
|
233
|
+
emit(
|
|
234
|
+
text(`[fail] Invalid (${errors.length} error${errors.length === 1 ? '' : 's'}):`),
|
|
235
|
+
list(items),
|
|
236
|
+
);
|
|
237
|
+
return NO_RESULT_SET;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** @type {import('../formatters/index.mjs').Block[]} */
|
|
241
|
+
const out = [text(`[ok] Valid (parsed as ${form})`)];
|
|
242
|
+
if (warnings.length > 0) {
|
|
243
|
+
out.push(text(warnings.map(w => `${WARN} ${w}`).join('\n')));
|
|
244
|
+
}
|
|
245
|
+
// The canonical compact/outline surfaces are preformatted — emit verbatim.
|
|
246
|
+
out.push(section('compact'), code(compact), section('outline'), code(outline));
|
|
247
|
+
emit(...out);
|
|
248
|
+
return NO_RESULT_SET;
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
defineCommand(layoutCmd, layoutGrammarCommand, {
|
|
253
|
+
fn: layoutGrammarFn,
|
|
254
|
+
action: async () => {
|
|
255
|
+
const json = program.opts().json || false;
|
|
256
|
+
/** @type {LayoutGrammarResponse} */
|
|
257
|
+
let result;
|
|
258
|
+
try {
|
|
259
|
+
result = /** @type {LayoutGrammarResponse} */ (await layoutGrammar({cwd: process.cwd()}));
|
|
260
|
+
} catch (e) {
|
|
261
|
+
const err = /** @type {import('../../../api/error.mjs').AstryxError} */ (e);
|
|
262
|
+
return cliError(err.message, {suggestions: err.suggestions || [], code: err.code});
|
|
263
|
+
}
|
|
264
|
+
// One document, the same one every time: the grammar cheatsheet.
|
|
265
|
+
const answered = resultSet({count: 1, resultKind: 'doc'});
|
|
266
|
+
if (json) {
|
|
267
|
+
jsonOut(result);
|
|
268
|
+
return answered;
|
|
269
|
+
}
|
|
270
|
+
// The cheatsheet is a preformatted document — emit verbatim.
|
|
271
|
+
emit(code(result.data.text));
|
|
272
|
+
return answered;
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file `layout expand` and `layout check` document their input modes (the
|
|
5
|
+
* argument, `-` for stdin, --file) and where `layout expand` writes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {describe, it, expect} from 'vitest';
|
|
9
|
+
import {runCli} from '../../../test-utils/run-cli.mjs';
|
|
10
|
+
import {isFilePathArg} from '../../../foundation/fs/path-safety.mjs';
|
|
11
|
+
|
|
12
|
+
const SLOW = 30_000;
|
|
13
|
+
|
|
14
|
+
describe('layout argument behavior is documented', () => {
|
|
15
|
+
it('layout expand help covers stdin, --file precedence and the output path rule', async () => {
|
|
16
|
+
const {status, stdout} = await runCli(['layout', 'expand', '--help']);
|
|
17
|
+
expect(status).toBe(0);
|
|
18
|
+
expect(stdout).toMatch(/Pass - to read it from stdin/);
|
|
19
|
+
expect(stdout).toMatch(/instead of the argument/);
|
|
20
|
+
expect(stdout).toContain('<Name>.tsx');
|
|
21
|
+
expect(stdout).toMatch(/existing file there is replaced/);
|
|
22
|
+
const exts = [...(stdout.match(/ends in ([.\w, ]+ or \.\w+)/)?.[1] ?? '').matchAll(/\.\w+/g)];
|
|
23
|
+
expect(exts.length).toBeGreaterThan(0);
|
|
24
|
+
for (const [ext] of exts) expect(isFilePathArg(`src/Page${ext}`)).toBe(true);
|
|
25
|
+
}, SLOW);
|
|
26
|
+
|
|
27
|
+
it('layout check help covers stdin and --file precedence', async () => {
|
|
28
|
+
const {status, stdout} = await runCli(['layout', 'check', '--help']);
|
|
29
|
+
expect(status).toBe(0);
|
|
30
|
+
expect(stdout).toMatch(/Pass - to read it from stdin/);
|
|
31
|
+
expect(stdout).toMatch(/instead of the argument/);
|
|
32
|
+
}, SLOW);
|
|
33
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file `layout check -` reads the expression from stdin under the same 5 MB
|
|
5
|
+
* cap as --file, instead of buffering whatever the pipe delivers. Spawns the
|
|
6
|
+
* real bin, since the in-process harness has no stdin.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {describe, it, expect} from 'vitest';
|
|
10
|
+
import {spawnSync} from 'node:child_process';
|
|
11
|
+
import * as path from 'node:path';
|
|
12
|
+
import {fileURLToPath} from 'node:url';
|
|
13
|
+
|
|
14
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const BIN = path.resolve(HERE, '../bin/astryx.mjs');
|
|
16
|
+
// Run against the monorepo root so @astryxdesign/core is discoverable.
|
|
17
|
+
const REPO = path.resolve(HERE, '../../../..');
|
|
18
|
+
const SLOW = 60_000;
|
|
19
|
+
|
|
20
|
+
/** @param {string} input @param {string[]} [extra] */
|
|
21
|
+
function checkFromStdin(input, extra = []) {
|
|
22
|
+
return spawnSync(process.execPath, [BIN, 'layout', 'check', '-', ...extra], {
|
|
23
|
+
cwd: REPO,
|
|
24
|
+
input,
|
|
25
|
+
encoding: 'utf-8',
|
|
26
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe('layout check - (stdin)', () => {
|
|
31
|
+
it('refuses more than 5 MB with ERR_INVALID_ARGUMENT, in both modes', () => {
|
|
32
|
+
// Valid once trimmed, so only the cap can reject it.
|
|
33
|
+
const big = ' '.repeat(5 * 1024 * 1024 + 1) + 'V';
|
|
34
|
+
const json = checkFromStdin(big, ['--json']);
|
|
35
|
+
expect(json.status).toBe(1);
|
|
36
|
+
expect(JSON.parse(json.stdout).code).toBe('ERR_INVALID_ARGUMENT');
|
|
37
|
+
const human = checkFromStdin(big);
|
|
38
|
+
expect(human.status).toBe(1);
|
|
39
|
+
expect(human.stderr + human.stdout).toMatch(/too large/);
|
|
40
|
+
}, SLOW);
|
|
41
|
+
|
|
42
|
+
it('still reads a normal expression', () => {
|
|
43
|
+
const r = checkFromStdin('V > B', ['--json']);
|
|
44
|
+
expect(r.status).toBe(0);
|
|
45
|
+
expect(JSON.parse(r.stdout).data.valid).toBe(true);
|
|
46
|
+
}, SLOW);
|
|
47
|
+
});
|
|
@@ -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
|
+
});
|
|
@@ -20,7 +20,7 @@ export const doc = {
|
|
|
20
20
|
'tooling to introspect. Intended to be run with --json.',
|
|
21
21
|
examples: [
|
|
22
22
|
{label: 'Full manifest', cli: 'astryx manifest --json'},
|
|
23
|
-
{label: '
|
|
23
|
+
{label: 'The same manifest, under data.manifest (type "help")', cli: 'astryx --json'},
|
|
24
24
|
],
|
|
25
25
|
exitCodes: [
|
|
26
26
|
{code: 0, when: 'success'},
|
|
@@ -16,9 +16,12 @@ export const doc = {
|
|
|
16
16
|
summary: 'Search components, hooks, docs, and templates in one ranked list',
|
|
17
17
|
description:
|
|
18
18
|
'Terminal front-end to search(): prints one ranked, greppable list across ' +
|
|
19
|
-
'every content domain, each row carrying a follow-up command to act on it.'
|
|
19
|
+
'every content domain, each row carrying a follow-up command to act on it. ' +
|
|
20
|
+
'Outside an app, where @astryxdesign/core is not installed, it searches the docs.',
|
|
20
21
|
fn: 'search',
|
|
21
|
-
|
|
22
|
+
// Every word after `search` is the query: `astryx search dark mode` searches
|
|
23
|
+
// for "dark mode", with no quotes needed.
|
|
24
|
+
args: [{name: 'query', param: 'query', required: true, variadic: true}],
|
|
22
25
|
options: [
|
|
23
26
|
{
|
|
24
27
|
flag: '--type <domain>',
|
|
@@ -38,6 +41,10 @@ export const doc = {
|
|
|
38
41
|
],
|
|
39
42
|
examples: [
|
|
40
43
|
{label: 'Ranked results', cli: 'astryx search button'},
|
|
44
|
+
{
|
|
45
|
+
label: 'Several words, no quotes',
|
|
46
|
+
cli: 'astryx search dark mode --type doc',
|
|
47
|
+
},
|
|
41
48
|
{
|
|
42
49
|
label: 'Filter + JSON',
|
|
43
50
|
cli: 'astryx search "data table" --type template --json',
|
|
@@ -47,7 +54,7 @@ export const doc = {
|
|
|
47
54
|
{code: 0, when: 'success (including zero matches)'},
|
|
48
55
|
{
|
|
49
56
|
code: 1,
|
|
50
|
-
when: 'invalid --type, a --limit that is not a positive integer, or @astryxdesign/core cannot be found',
|
|
57
|
+
when: 'invalid --type, a --limit that is not a positive integer, or --type component, hook, or template where @astryxdesign/core cannot be found',
|
|
51
58
|
},
|
|
52
59
|
],
|
|
53
60
|
related: ['component', 'hook', 'docs', 'template', 'build'],
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*
|
|
15
15
|
* Usage:
|
|
16
16
|
* astryx search button Ranked results across all domains
|
|
17
|
+
* astryx search dark mode Every word is part of the query
|
|
17
18
|
* astryx search modal --type component Filter to a single domain
|
|
18
19
|
* astryx search forms --limit 5 Cap the result count
|
|
19
20
|
* astryx search button --verbose Also print score / reason
|
|
@@ -31,6 +32,7 @@ import {cliError} from '../lib/cli-error.mjs';
|
|
|
31
32
|
import {defineCommand} from '../lib/define-command.mjs';
|
|
32
33
|
import {search as searchApi} from '../../../api/search/search.mjs';
|
|
33
34
|
import {Project} from '../../../foundation/config/project.mjs';
|
|
35
|
+
import {findCoreDir} from '../../../foundation/fs/paths.mjs';
|
|
34
36
|
import {warnOnIntegrationIssues} from '../../../foundation/integrations/integration-warnings.mjs';
|
|
35
37
|
import {doc as searchCommand} from './search.doc.mjs';
|
|
36
38
|
import {doc as searchFn} from '../../../api/search/search.doc.mjs';
|
|
@@ -42,10 +44,13 @@ export function registerSearch(program) {
|
|
|
42
44
|
defineCommand(program, searchCommand, {
|
|
43
45
|
fn: searchFn,
|
|
44
46
|
action: async (
|
|
45
|
-
/** @type {string} */
|
|
47
|
+
/** @type {string[] | string} */ words,
|
|
46
48
|
/** @type {{type?: import('../../../api/search/search.type.mjs').SearchDomain, limit?: string, verbose?: boolean}} */ options,
|
|
47
49
|
) => {
|
|
48
50
|
const json = program.opts().json || false;
|
|
51
|
+
// The query is variadic: `astryx search dark mode` is one query, "dark
|
|
52
|
+
// mode". Taking only the first word dropped the rest without a word.
|
|
53
|
+
const query = Array.isArray(words) ? words.join(' ') : words;
|
|
49
54
|
|
|
50
55
|
try {
|
|
51
56
|
const project = await Project.load(process.cwd());
|
|
@@ -99,12 +104,25 @@ export function registerSearch(program) {
|
|
|
99
104
|
// ── Text output ──────────────────────────────────────────────
|
|
100
105
|
const run = getCliInvocation();
|
|
101
106
|
const {query: q, matchCount, results} = result.data;
|
|
107
|
+
// Outside an app an open search covers the docs alone. Say so, so a
|
|
108
|
+
// docs-only list does not read as "Astryx has no such component".
|
|
109
|
+
const note =
|
|
110
|
+
!options.type && !findCoreDir(process.cwd())
|
|
111
|
+
? [
|
|
112
|
+
text(
|
|
113
|
+
'@astryxdesign/core is not installed here, so only the docs were searched.',
|
|
114
|
+
),
|
|
115
|
+
]
|
|
116
|
+
: [];
|
|
102
117
|
|
|
103
118
|
// No matches is a valid, successful outcome — clean message, exit 0.
|
|
104
119
|
if (results.length === 0) {
|
|
105
120
|
emit(
|
|
106
121
|
text(`No results for "${q}".`),
|
|
107
|
-
|
|
122
|
+
...note,
|
|
123
|
+
text(
|
|
124
|
+
`Try a broader term, or browse: ${run} ${note.length > 0 ? 'docs' : 'component --list'}`,
|
|
125
|
+
),
|
|
108
126
|
);
|
|
109
127
|
return answered;
|
|
110
128
|
}
|
|
@@ -155,6 +173,7 @@ export function registerSearch(program) {
|
|
|
155
173
|
fields,
|
|
156
174
|
format: {command: formatCliCommand, parent: formatCliCommand},
|
|
157
175
|
}),
|
|
176
|
+
...note,
|
|
158
177
|
);
|
|
159
178
|
return answered;
|
|
160
179
|
},
|
|
@@ -191,6 +191,18 @@ describe('search CLI — exit codes + JSON contract', () => {
|
|
|
191
191
|
expect(r.stdout).toContain('No results');
|
|
192
192
|
});
|
|
193
193
|
|
|
194
|
+
it('takes every word after `search` as one query', async () => {
|
|
195
|
+
// Commander took only the first word, so `search dark mode` searched for
|
|
196
|
+
// "dark" and dropped "mode" without a word.
|
|
197
|
+
const json = await runCli(['--json', 'search', 'dark', 'mode', '--type', 'doc'], REPO_ROOT);
|
|
198
|
+
expect(json.status).toBe(0);
|
|
199
|
+
const env = JSON.parse(json.stdout);
|
|
200
|
+
expect(env.data.query).toBe('dark mode');
|
|
201
|
+
expect(env.data.results[0]).toMatchObject({name: 'theme', section: 'light-dark-mode'});
|
|
202
|
+
const text = await runCli(['search', 'dark', 'mode', '--type', 'doc'], REPO_ROOT);
|
|
203
|
+
expect(text.stdout).toContain('Results for "dark mode"');
|
|
204
|
+
}, SCAN_TIMEOUT);
|
|
205
|
+
|
|
194
206
|
it('exits 1 for an invalid --type', async () => {
|
|
195
207
|
const r = await runCli(['search', 'x', '--type', 'bogus'], REPO_ROOT);
|
|
196
208
|
expect(r.status).toBe(1);
|
|
@@ -299,14 +311,19 @@ describe('search CLI — exit codes + JSON contract', () => {
|
|
|
299
311
|
expect(r.stdout).toContain('reason:');
|
|
300
312
|
});
|
|
301
313
|
|
|
302
|
-
it('
|
|
314
|
+
it('searches the docs when no @astryxdesign/core is reachable, and exits 1 for --type component', async () => {
|
|
303
315
|
const empty = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-search-cli-no-core-'));
|
|
304
316
|
try {
|
|
305
|
-
const
|
|
317
|
+
const open = await runCli(['--json', 'search', 'make', 'an', 'integration'], empty);
|
|
318
|
+
expect(open.status).toBe(0);
|
|
319
|
+
expect(JSON.parse(open.stdout).data.results[0]).toMatchObject({domain: 'doc'});
|
|
320
|
+
// The text says the search covered the docs alone.
|
|
321
|
+
const text = await runCli(['search', 'button'], empty);
|
|
322
|
+
expect(text.status).toBe(0);
|
|
323
|
+
expect(text.stdout).toContain('only the docs were searched');
|
|
324
|
+
const json = await runCli(['--json', 'search', 'button', '--type', 'component'], empty);
|
|
306
325
|
expect(json.status).toBe(1);
|
|
307
326
|
expect(JSON.parse(json.stdout)).toMatchObject({code: 'ERR_CORE_NOT_FOUND'});
|
|
308
|
-
const text = await runCli(['search', 'button'], empty);
|
|
309
|
-
expect(text.status).toBe(1);
|
|
310
327
|
} finally {
|
|
311
328
|
fs.rmSync(empty, {recursive: true, force: true});
|
|
312
329
|
}
|
|
@@ -46,7 +46,7 @@ export const doc = {
|
|
|
46
46
|
],
|
|
47
47
|
examples: [
|
|
48
48
|
{label: 'List swizzlable components', cli: 'astryx swizzle --list'},
|
|
49
|
-
{label: 'Eject a component', cli: 'astryx swizzle
|
|
49
|
+
{label: 'Eject a component', cli: 'astryx swizzle Button'},
|
|
50
50
|
],
|
|
51
51
|
exitCodes: [
|
|
52
52
|
{code: 0, when: 'success'},
|
|
@@ -14,7 +14,7 @@ export const doc = {
|
|
|
14
14
|
name: 'template',
|
|
15
15
|
displayName: 'astryx template',
|
|
16
16
|
namespace: 'cli/commands',
|
|
17
|
-
summary: '
|
|
17
|
+
summary: 'List, show, or scaffold page and block templates',
|
|
18
18
|
description:
|
|
19
19
|
'One entry point for the template family: with no name it lists the discovered ' +
|
|
20
20
|
'templates; with a name it shows the source or a layout skeleton, or scaffolds it ' +
|
|
@@ -372,11 +372,34 @@ const CASES = [
|
|
|
372
372
|
skipFieldChecks: true,
|
|
373
373
|
},
|
|
374
374
|
{
|
|
375
|
+
name: 'integration verify',
|
|
376
|
+
args: ['integration', 'verify'],
|
|
377
|
+
// Needs a packable project; pack itself may exit differently.
|
|
378
|
+
skipFieldChecks: true,
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
// The deprecated alias of `integration verify`.
|
|
375
382
|
name: 'integration pack',
|
|
376
383
|
args: ['integration', 'pack', '--check'],
|
|
377
|
-
// Needs a packable project; pack itself may exit differently.
|
|
378
384
|
skipFieldChecks: true,
|
|
379
385
|
},
|
|
386
|
+
// ── Layout command: remove these cases when the layout command is deleted ──
|
|
387
|
+
{
|
|
388
|
+
name: 'layout check',
|
|
389
|
+
args: ['layout', 'check', 'Button'],
|
|
390
|
+
skipFieldChecks: true,
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
name: 'layout expand',
|
|
394
|
+
args: ['layout', 'expand', 'Button'],
|
|
395
|
+
skipFieldChecks: true,
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
name: 'layout grammar',
|
|
399
|
+
args: ['layout', 'grammar'],
|
|
400
|
+
skipFieldChecks: true,
|
|
401
|
+
},
|
|
402
|
+
// ── End layout cases ──
|
|
380
403
|
{
|
|
381
404
|
name: 'manifest',
|
|
382
405
|
args: ['manifest'],
|
|
@@ -41,7 +41,7 @@ export const doc = {
|
|
|
41
41
|
examples: [
|
|
42
42
|
{label: 'Scaffold a theme', cli: 'astryx theme add matcha'},
|
|
43
43
|
{
|
|
44
|
-
label: '
|
|
44
|
+
label: 'Pick the owner when two packages ship the same slug',
|
|
45
45
|
cli: 'astryx theme add ocean --package @acme/themes',
|
|
46
46
|
},
|
|
47
47
|
],
|
|
@@ -35,12 +35,13 @@ export const doc = {
|
|
|
35
35
|
{
|
|
36
36
|
flag: '--preview <path>',
|
|
37
37
|
param: 'options.preview',
|
|
38
|
-
description: 'Write a
|
|
38
|
+
description: 'Write a self-contained HTML preview page; the path must end in .html',
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
flag: '-f, --overwrite',
|
|
42
42
|
param: 'options.overwrite',
|
|
43
|
-
description:
|
|
43
|
+
description:
|
|
44
|
+
'Replace existing candidate, receipt, and preview files. Without it, if any of them exists, nothing is written',
|
|
44
45
|
},
|
|
45
46
|
],
|
|
46
47
|
examples: [
|
|
@@ -8,8 +8,7 @@ export const doc = {
|
|
|
8
8
|
namespace: 'cli/commands',
|
|
9
9
|
summary: 'Create and work with theme-owned color palettes',
|
|
10
10
|
description:
|
|
11
|
-
'Palette authoring tools.
|
|
12
|
-
'Palette inspection and diagnostic commands are intentionally deferred to follow-up work.',
|
|
11
|
+
'Palette authoring tools. generate writes a palette candidate for you to review before a theme uses it.',
|
|
13
12
|
subcommands: ['generate'],
|
|
14
13
|
examples: [
|
|
15
14
|
{
|
|
@@ -20,8 +20,8 @@ export const doc = {
|
|
|
20
20
|
'the component that declares it, and the props and states that are legal override keys ' +
|
|
21
21
|
'under it. This is the whole themeable surface in one command: what auditing a theme, or ' +
|
|
22
22
|
'answering "which key paints this pixel?", used to need one `astryx component <Name>` per ' +
|
|
23
|
-
'component to assemble. Pass a component name to scope it; pass any
|
|
24
|
-
'keys. `--json` for a list a repo can lint its own theme against.',
|
|
23
|
+
'component to assemble. Pass a component name to scope it; pass any other text to search ' +
|
|
24
|
+
'target keys, classes, and components. `--json` for a list a repo can lint its own theme against.',
|
|
25
25
|
fn: 'themeTargets',
|
|
26
26
|
args: [{name: 'filter', param: 'filter', required: false}],
|
|
27
27
|
examples: [
|