@astryxdesign/cli 0.6.4-canary.f0355e3 → 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 +96 -99
- package/api/build/build.doc.mjs +1 -6
- package/api/build/build.test.mjs +0 -22
- package/api/build/kit/kit.mjs +5 -44
- 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 +17 -37
- 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/_adapter.d.mts +3 -8
- package/api/docs/_adapter.mjs +6 -14
- package/api/docs/docOverlays.test.mjs +1 -27
- package/api/docs/docs.doc.mjs +2 -2
- package/api/docs/docs.test.mjs +243 -0
- package/api/docs/integration-tree.test.mjs +555 -0
- package/api/docs/integrationDocs.test.mjs +314 -0
- package/api/doctor/doctor.d.mts +3 -8
- package/api/doctor/doctor.doc.mjs +8 -17
- package/api/doctor/doctor.mjs +9 -90
- package/api/doctor/doctor.test.mjs +10 -122
- 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 +10 -19
- package/api/hook/hook.doc.mjs +3 -6
- package/api/index.d.mts +2 -1
- package/api/index.mjs +5 -5
- package/api/init/init.doc.mjs +12 -17
- package/api/integration/add-helpers.d.mts +2 -5
- package/api/integration/add-helpers.mjs +9 -36
- package/api/integration/add-theme.mjs +1 -22
- package/api/integration/add-theme.test.mjs +0 -34
- package/api/integration/authoring-checks.mjs +2 -2
- package/api/integration/integrationPackCheck.doc.mjs +3 -3
- package/api/integration/pack-check.mjs +9 -82
- package/api/integration/pack-check.test.mjs +0 -90
- 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 +1 -27
- package/api/search/search.doc.mjs +2 -2
- package/api/search/search.mjs +16 -228
- package/api/search/search.test.mjs +512 -0
- package/api/swizzle/swizzle.doc.mjs +5 -7
- package/api/template/copy/copy.mjs +1 -1
- package/api/template/copy/copy.test.mjs +0 -9
- package/api/template/template-integration.test.mjs +65 -1
- package/api/template/template.doc.mjs +1 -2
- 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/theme/generateTonalPalette.doc.mjs +2 -1
- package/api/theme/listThemes.doc.mjs +1 -1
- package/api/theme/themeAdd.doc.mjs +10 -9
- package/api/theme/themeBuild.doc.mjs +13 -13
- package/api/theme/themeList.doc.mjs +1 -1
- package/api/theme/themeListAvailable.doc.mjs +1 -2
- package/api/theme/themePaletteGenerate.doc.mjs +8 -15
- package/api/theme/themeTargets.doc.mjs +2 -3
- package/api/theme/themeTemplate.doc.mjs +1 -2
- package/api/upgrade/run/run.mjs +4 -6
- package/api/upgrade/upgrade.doc.mjs +22 -24
- 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/README.md +2 -4
- package/assets/docs/browser-support.doc.mjs +11 -11
- package/assets/docs/color.doc.mjs +2 -8
- package/assets/docs/elevation.doc.mjs +4 -6
- package/assets/docs/getting-started.doc.mjs +16 -5
- package/assets/docs/icons.doc.mjs +21 -2
- package/assets/docs/illustrations.doc.mjs +15 -7
- package/assets/docs/internationalization.doc.mjs +5 -7
- package/assets/docs/layout.doc.dense.mjs +82 -130
- package/assets/docs/layout.doc.mjs +77 -133
- package/assets/docs/migration.doc.mjs +21 -19
- package/assets/docs/motion.doc.mjs +3 -16
- package/assets/docs/principles.doc.dense.mjs +5 -5
- package/assets/docs/principles.doc.mjs +0 -8
- package/assets/docs/principles.doc.zh.mjs +6 -6
- package/assets/docs/shape.doc.mjs +3 -8
- package/assets/docs/spacing.doc.mjs +2 -7
- package/assets/docs/styling-libraries.doc.mjs +2 -6
- package/assets/docs/styling.doc.mjs +23 -19
- package/assets/docs/theme.doc.dense.mjs +18 -58
- package/assets/docs/theme.doc.mjs +46 -56
- package/assets/docs/theme.doc.zh.mjs +8 -9
- package/assets/docs/tokens.doc.dense.mjs +2 -2
- package/assets/docs/tokens.doc.mjs +8 -389
- package/assets/docs/tokens.doc.zh.mjs +2 -2
- package/assets/docs/tree/integrations.doc.mjs +451 -25
- package/assets/docs/tree/integrations.test.mjs +62 -0
- package/assets/docs/tree/writing-docs.doc.mjs +286 -0
- package/assets/docs/typography.doc.mjs +4 -24
- package/assets/docs/working-with-ai.doc.mjs +22 -30
- package/assets/templates/blocks/components/InternationalizationProvider/InternationalizationProvider01ShippedLocale.tsx +1 -1
- package/authoring/config/config.doc.mjs +2 -10
- 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/_schema.d.mts +2 -3
- package/authoring/doctypes/_schema.mjs +0 -6
- package/authoring/doctypes/base/graph-fields.doc.mjs +3 -3
- package/authoring/doctypes/base/type.ts +2 -4
- package/authoring/doctypes/command/command.doc.mjs +1 -1
- package/authoring/doctypes/command/type.ts +1 -1
- package/authoring/doctypes/component/component.doc.mjs +0 -6
- package/authoring/doctypes/component/type.ts +0 -8
- package/authoring/doctypes/reference/reference.doc.mjs +0 -7
- package/authoring/doctypes/reference/type.ts +0 -5
- 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/index.d.mts +0 -1
- package/authoring/index.d.ts +0 -10
- package/authoring/index.mjs +0 -1
- package/authoring/integration/integration.doc.mjs +10 -12
- 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 +9 -27
- 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.doc.mjs +1 -1
- package/clients/cli/commands/docs.mjs +17 -60
- package/clients/cli/commands/docs.test.mjs +294 -0
- package/clients/cli/commands/doctor-integration-docs.doc.mjs +2 -3
- package/clients/cli/commands/doctor-integration.test.mjs +0 -53
- package/clients/cli/commands/doctor.doc.mjs +1 -3
- package/clients/cli/commands/doctor.mjs +5 -49
- package/clients/cli/commands/gap-report.doc.mjs +9 -10
- package/clients/cli/commands/init.doc.mjs +6 -9
- package/clients/cli/commands/integration-add.doc.mjs +9 -9
- package/clients/cli/commands/integration-authoring.test.mjs +10 -61
- package/clients/cli/commands/integration-pack.doc.mjs +9 -5
- package/clients/cli/commands/integration-real-world.test.mjs +1 -1
- package/clients/cli/commands/integration.doc.mjs +4 -4
- package/clients/cli/commands/integration.mjs +43 -74
- 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 +3 -10
- package/clients/cli/commands/search.mjs +2 -21
- package/clients/cli/commands/search.test.mjs +4 -21
- 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 +16 -5
- package/clients/cli/commands/theme-add.doc.mjs +1 -1
- package/clients/cli/commands/theme-palette-generate.doc.mjs +2 -3
- package/clients/cli/commands/theme-palette.doc.mjs +2 -1
- package/clients/cli/commands/theme-targets.doc.mjs +2 -2
- package/clients/cli/commands/theme.doc.mjs +1 -2
- package/clients/cli/commands/upgrade.doc.mjs +3 -62
- package/clients/cli/index.mjs +10 -28
- package/clients/cli/lib/define-command.mjs +4 -28
- package/clients/cli/lib/define-command.test.mjs +0 -54
- package/clients/cli/lib/exit-codes.test.mjs +9 -18
- 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 +13 -18
- package/clients/cli/lib/manifest.test.mjs +2 -5
- package/foundation/agent-docs/agent-docs.mjs +1 -1
- package/foundation/agent-docs/agent-docs.test.mjs +1159 -0
- package/foundation/discovery/authoring-self-docs.mjs +0 -1
- package/foundation/discovery/authoring-self-docs.test.mjs +2 -6
- package/foundation/discovery/cli-self-docs.mjs +2 -16
- package/foundation/discovery/cli-self-docs.test.mjs +0 -20
- package/foundation/discovery/docs-discovery.mjs +1 -5
- package/foundation/discovery/docs-discovery.test.mjs +0 -21
- 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 +14 -3
- package/foundation/doc-compiler/tree.d.mts +0 -4
- package/foundation/doc-compiler/tree.mjs +1 -6
- package/foundation/doc-compiler/tree.test.mjs +598 -0
- package/foundation/integrations/cli-requirement.d.mts +6 -26
- package/foundation/integrations/cli-requirement.mjs +11 -46
- package/foundation/integrations/cli-requirement.test.mjs +2 -7
- package/foundation/integrations/contribution-inventory.mjs +1 -1
- 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/error-codes.doc.mjs +8 -6
- package/foundation/response/error-codes.test.mjs +5 -30
- package/foundation/response/response-types.doc.d.mts +3 -4
- package/foundation/response/response-types.doc.mjs +27 -40
- package/foundation/response/response-types.doc.test.mjs +0 -23
- package/foundation/response/response.doc.mjs +10 -11
- 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 -107
- 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/add-a-component.doc.mjs +0 -75
- package/assets/docs/tree/add-a-theme.doc.mjs +0 -85
- package/assets/docs/tree/add-a-topic.doc.mjs +0 -144
- package/assets/docs/tree/agent-guidance.doc.mjs +0 -138
- package/assets/docs/tree/block-template.doc.mjs +0 -130
- package/assets/docs/tree/build-the-template.doc.mjs +0 -28
- package/assets/docs/tree/building-blocks.doc.mjs +0 -46
- package/assets/docs/tree/check-your-docs.doc.mjs +0 -137
- package/assets/docs/tree/checks.doc.mjs +0 -119
- package/assets/docs/tree/codemods.doc.mjs +0 -147
- package/assets/docs/tree/component-family.doc.mjs +0 -113
- package/assets/docs/tree/component-imports.doc.mjs +0 -69
- package/assets/docs/tree/component-lookups.doc.mjs +0 -149
- package/assets/docs/tree/components.doc.mjs +0 -23
- package/assets/docs/tree/configuration.doc.mjs +0 -23
- package/assets/docs/tree/debug-and-gap-reports.doc.mjs +0 -182
- package/assets/docs/tree/define-the-theme.doc.mjs +0 -118
- package/assets/docs/tree/describe-the-component.doc.mjs +0 -57
- package/assets/docs/tree/docs.doc.mjs +0 -21
- package/assets/docs/tree/document-the-template.doc.mjs +0 -28
- package/assets/docs/tree/document-the-theme.doc.mjs +0 -68
- package/assets/docs/tree/export-template-assets.doc.mjs +0 -147
- package/assets/docs/tree/extend-or-replace.doc.mjs +0 -103
- package/assets/docs/tree/fonts-and-assets.doc.mjs +0 -106
- package/assets/docs/tree/generate-a-palette.doc.mjs +0 -66
- package/assets/docs/tree/grade-template-with-agent.doc.mjs +0 -105
- package/assets/docs/tree/help.doc.mjs +0 -16
- package/assets/docs/tree/links.doc.mjs +0 -98
- package/assets/docs/tree/package-and-test.doc.mjs +0 -32
- package/assets/docs/tree/page-template.doc.mjs +0 -71
- package/assets/docs/tree/publishing.doc.mjs +0 -111
- package/assets/docs/tree/quick-start.doc.mjs +0 -272
- package/assets/docs/tree/replace-a-core-component.doc.mjs +0 -104
- package/assets/docs/tree/replace-a-core-template.doc.mjs +0 -172
- package/assets/docs/tree/sections-and-placement.doc.mjs +0 -108
- package/assets/docs/tree/see-it-in-an-app.doc.mjs +0 -59
- package/assets/docs/tree/ship.doc.mjs +0 -16
- package/assets/docs/tree/short-and-findable.doc.mjs +0 -108
- package/assets/docs/tree/single-component.doc.mjs +0 -165
- package/assets/docs/tree/start-a-template.doc.mjs +0 -143
- package/assets/docs/tree/subcomponent.doc.mjs +0 -115
- package/assets/docs/tree/template-assets.doc.mjs +0 -64
- package/assets/docs/tree/template-doc-overview.doc.mjs +0 -109
- package/assets/docs/tree/template-fonts.doc.mjs +0 -102
- package/assets/docs/tree/template-grading-rubric.doc.mjs +0 -452
- package/assets/docs/tree/template-icons.doc.mjs +0 -97
- package/assets/docs/tree/template-images-media.doc.mjs +0 -127
- package/assets/docs/tree/template-styles.doc.mjs +0 -93
- package/assets/docs/tree/templates.doc.mjs +0 -34
- package/assets/docs/tree/test-in-an-app.doc.mjs +0 -115
- package/assets/docs/tree/test-template-in-app.doc.mjs +0 -128
- package/assets/docs/tree/themes.doc.mjs +0 -39
- package/assets/docs/tree/troubleshooting.doc.mjs +0 -149
- package/assets/docs/tree/upgrading.doc.mjs +0 -103
- package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +0 -51
- package/assets/docs/tree/verify-packed-template.doc.mjs +0 -77
- package/assets/docs/tree/versioning.doc.mjs +0 -161
- package/assets/docs/tree/write-good-templates.doc.mjs +0 -64
- package/assets/docs/tree/write-the-template-file.doc.mjs +0 -154
- 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/commands/integration-verify.doc.mjs +0 -22
- 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,34 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file CommandDoc for the `astryx layout` command group. A parent group with no
|
|
5
|
+
* behavior of its own; it dispatches to the expand/check/grammar subcommands,
|
|
6
|
+
* which carry the actual args, flags, and wrapped functions.
|
|
7
|
+
* @position packages/cli/clients/cli/commands — command documentation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** @type {import('@astryxdesign/cli/authoring').CommandDoc} */
|
|
11
|
+
export const doc = {
|
|
12
|
+
type: 'command',
|
|
13
|
+
name: 'layout',
|
|
14
|
+
displayName: 'astryx layout',
|
|
15
|
+
namespace: 'cli/commands',
|
|
16
|
+
summary: 'Generate XDS layouts from compressed expressions (XLE/XLO)',
|
|
17
|
+
description:
|
|
18
|
+
'The layout command group. Running astryx layout with no subcommand prints the ' +
|
|
19
|
+
'subcommand list; the work happens in the subcommands: expand an expression into ' +
|
|
20
|
+
'TSX (expand), validate one (check), or print the grammar cheatsheet (grammar).',
|
|
21
|
+
subcommands: ['expand', 'check', 'grammar'],
|
|
22
|
+
examples: [
|
|
23
|
+
{label: 'Print the grammar', cli: 'astryx layout grammar'},
|
|
24
|
+
{
|
|
25
|
+
label: 'Expand to TSX',
|
|
26
|
+
cli: "astryx layout expand 'V[g6] > C{card-callout}*4' ./src/Page.tsx",
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
exitCodes: [
|
|
30
|
+
{code: 0, when: 'a subcommand succeeded, or --help'},
|
|
31
|
+
{code: 1, when: 'no subcommand (help is printed) or an unknown subcommand'},
|
|
32
|
+
],
|
|
33
|
+
related: ['template', 'build'],
|
|
34
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Error-code contract tests for the layout command wrapper. Locks the
|
|
5
|
+
* two foreseeable input errors to STABLE ERR_* codes (never ERR_UNKNOWN or a
|
|
6
|
+
* raw fs errno) and asserts --json/human exit parity:
|
|
7
|
+
* - empty expression → ERR_MISSING_ARGUMENT
|
|
8
|
+
* - --file <missing> → ERR_FILE_NOT_FOUND (not raw ENOENT), no stack leak
|
|
9
|
+
* Spawns the real binary so the bin error boundary is exercised faithfully.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {describe, it, expect} from 'vitest';
|
|
13
|
+
import {spawnSync} from 'node:child_process';
|
|
14
|
+
import * as path from 'node:path';
|
|
15
|
+
import {fileURLToPath} from 'node:url';
|
|
16
|
+
|
|
17
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const CLI_BIN = path.resolve(__dirname, '../bin/astryx.mjs');
|
|
19
|
+
|
|
20
|
+
function run(args) {
|
|
21
|
+
const res = spawnSync('node', [CLI_BIN, ...args], {
|
|
22
|
+
encoding: 'utf-8',
|
|
23
|
+
timeout: 20_000,
|
|
24
|
+
});
|
|
25
|
+
return {status: res.status, stdout: res.stdout || '', stderr: res.stderr || ''};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('layout --json error codes', () => {
|
|
29
|
+
for (const sub of ['expand', 'check']) {
|
|
30
|
+
it(`layout ${sub} "" → ERR_MISSING_ARGUMENT (not ERR_UNKNOWN)`, () => {
|
|
31
|
+
const r = run(['--json', 'layout', sub, '']);
|
|
32
|
+
expect(r.status).toBe(1);
|
|
33
|
+
const env = JSON.parse(r.stdout);
|
|
34
|
+
expect(env.code).toBe('ERR_MISSING_ARGUMENT');
|
|
35
|
+
expect(r.stderr).toBe('');
|
|
36
|
+
expect(r.stdout).not.toMatch(/\n\s+at /); // no stack frames
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it(`layout ${sub} --file <missing> → ERR_FILE_NOT_FOUND (not ENOENT)`, () => {
|
|
40
|
+
const r = run([
|
|
41
|
+
'--json',
|
|
42
|
+
'layout',
|
|
43
|
+
sub,
|
|
44
|
+
'--file',
|
|
45
|
+
'/tmp/definitely-missing-astryx-xyz.mjs',
|
|
46
|
+
]);
|
|
47
|
+
expect(r.status).toBe(1);
|
|
48
|
+
const env = JSON.parse(r.stdout);
|
|
49
|
+
expect(env.code).toBe('ERR_FILE_NOT_FOUND');
|
|
50
|
+
expect(r.stderr).toBe('');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it(`layout ${sub}: --json and human agree on exit code (empty input)`, () => {
|
|
54
|
+
const human = run(['layout', sub, '']);
|
|
55
|
+
const json = run(['--json', 'layout', sub, '']);
|
|
56
|
+
expect(json.status).toBe(human.status);
|
|
57
|
+
expect(json.status).toBe(1);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it(`layout ${sub} --file <missing> in human mode leaks no stack trace`, () => {
|
|
61
|
+
const r = run(['layout', sub, '--file', '/tmp/definitely-missing-astryx-xyz.mjs']);
|
|
62
|
+
expect(r.status).toBe(1);
|
|
63
|
+
expect(r.stderr).not.toMatch(/\n\s+at /);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file CLI-level tests for `astryx layout check` exit-code parity.
|
|
5
|
+
*
|
|
6
|
+
* The exit code is the contract and must NOT depend on --json vs human mode:
|
|
7
|
+
* an invalid (but parseable) layout exits 1 in BOTH modes so `layout check`
|
|
8
|
+
* works as a CI gate / agent check without parsing stdout. Runs the real
|
|
9
|
+
* program in-process via the shared runCli harness.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {describe, it, expect} from 'vitest';
|
|
13
|
+
import {runCli} from '../../../test-utils/run-cli.mjs';
|
|
14
|
+
|
|
15
|
+
const SLOW = 30_000;
|
|
16
|
+
// Parses fine, but references an unknown component -> data.valid: false
|
|
17
|
+
// (a semantic error, surfaced as data, not a thrown ERR_LAYOUT_PARSE).
|
|
18
|
+
const INVALID = 'ZzzUnknownComponent[foo=bar]';
|
|
19
|
+
const VALID = 'K';
|
|
20
|
+
|
|
21
|
+
describe('layout check — exit-code parity (--json vs human)', () => {
|
|
22
|
+
it('human mode exits 1 for an invalid (parseable) expression', async () => {
|
|
23
|
+
const {status} = await runCli(['layout', 'check', INVALID]);
|
|
24
|
+
expect(status).toBe(1);
|
|
25
|
+
}, SLOW);
|
|
26
|
+
|
|
27
|
+
it('--json exits 1 for the SAME invalid expression (modes agree)', async () => {
|
|
28
|
+
const {status, stdout} = await runCli(['layout', 'check', INVALID, '--json']);
|
|
29
|
+
const env = JSON.parse(stdout);
|
|
30
|
+
expect(env.type).toBe('layout.check');
|
|
31
|
+
expect(env.data.valid).toBe(false);
|
|
32
|
+
expect(status).toBe(1);
|
|
33
|
+
}, SLOW);
|
|
34
|
+
|
|
35
|
+
it('a valid expression exits 0 in both modes', async () => {
|
|
36
|
+
expect((await runCli(['layout', 'check', VALID])).status).toBe(0);
|
|
37
|
+
const {status, stdout} = await runCli(['layout', 'check', VALID, '--json']);
|
|
38
|
+
expect(JSON.parse(stdout).data.valid).toBe(true);
|
|
39
|
+
expect(status).toBe(0);
|
|
40
|
+
}, SLOW);
|
|
41
|
+
});
|
|
@@ -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: 'Shorthand', cli: 'astryx --json'},
|
|
24
24
|
],
|
|
25
25
|
exitCodes: [
|
|
26
26
|
{code: 0, when: 'success'},
|
|
@@ -16,12 +16,9 @@ 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.
|
|
20
|
-
'Outside an app, where @astryxdesign/core is not installed, it searches the docs.',
|
|
19
|
+
'every content domain, each row carrying a follow-up command to act on it.',
|
|
21
20
|
fn: 'search',
|
|
22
|
-
|
|
23
|
-
// for "dark mode", with no quotes needed.
|
|
24
|
-
args: [{name: 'query', param: 'query', required: true, variadic: true}],
|
|
21
|
+
args: [{name: 'query', param: 'query', required: true}],
|
|
25
22
|
options: [
|
|
26
23
|
{
|
|
27
24
|
flag: '--type <domain>',
|
|
@@ -41,10 +38,6 @@ export const doc = {
|
|
|
41
38
|
],
|
|
42
39
|
examples: [
|
|
43
40
|
{label: 'Ranked results', cli: 'astryx search button'},
|
|
44
|
-
{
|
|
45
|
-
label: 'Several words, no quotes',
|
|
46
|
-
cli: 'astryx search dark mode --type doc',
|
|
47
|
-
},
|
|
48
41
|
{
|
|
49
42
|
label: 'Filter + JSON',
|
|
50
43
|
cli: 'astryx search "data table" --type template --json',
|
|
@@ -54,7 +47,7 @@ export const doc = {
|
|
|
54
47
|
{code: 0, when: 'success (including zero matches)'},
|
|
55
48
|
{
|
|
56
49
|
code: 1,
|
|
57
|
-
when: 'invalid --type, a --limit that is not a positive integer, or
|
|
50
|
+
when: 'invalid --type, a --limit that is not a positive integer, or @astryxdesign/core cannot be found',
|
|
58
51
|
},
|
|
59
52
|
],
|
|
60
53
|
related: ['component', 'hook', 'docs', 'template', 'build'],
|
|
@@ -14,7 +14,6 @@
|
|
|
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
|
|
18
17
|
* astryx search modal --type component Filter to a single domain
|
|
19
18
|
* astryx search forms --limit 5 Cap the result count
|
|
20
19
|
* astryx search button --verbose Also print score / reason
|
|
@@ -32,7 +31,6 @@ import {cliError} from '../lib/cli-error.mjs';
|
|
|
32
31
|
import {defineCommand} from '../lib/define-command.mjs';
|
|
33
32
|
import {search as searchApi} from '../../../api/search/search.mjs';
|
|
34
33
|
import {Project} from '../../../foundation/config/project.mjs';
|
|
35
|
-
import {findCoreDir} from '../../../foundation/fs/paths.mjs';
|
|
36
34
|
import {warnOnIntegrationIssues} from '../../../foundation/integrations/integration-warnings.mjs';
|
|
37
35
|
import {doc as searchCommand} from './search.doc.mjs';
|
|
38
36
|
import {doc as searchFn} from '../../../api/search/search.doc.mjs';
|
|
@@ -44,13 +42,10 @@ export function registerSearch(program) {
|
|
|
44
42
|
defineCommand(program, searchCommand, {
|
|
45
43
|
fn: searchFn,
|
|
46
44
|
action: async (
|
|
47
|
-
/** @type {string
|
|
45
|
+
/** @type {string} */ query,
|
|
48
46
|
/** @type {{type?: import('../../../api/search/search.type.mjs').SearchDomain, limit?: string, verbose?: boolean}} */ options,
|
|
49
47
|
) => {
|
|
50
48
|
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;
|
|
54
49
|
|
|
55
50
|
try {
|
|
56
51
|
const project = await Project.load(process.cwd());
|
|
@@ -104,25 +99,12 @@ export function registerSearch(program) {
|
|
|
104
99
|
// ── Text output ──────────────────────────────────────────────
|
|
105
100
|
const run = getCliInvocation();
|
|
106
101
|
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
|
-
: [];
|
|
117
102
|
|
|
118
103
|
// No matches is a valid, successful outcome — clean message, exit 0.
|
|
119
104
|
if (results.length === 0) {
|
|
120
105
|
emit(
|
|
121
106
|
text(`No results for "${q}".`),
|
|
122
|
-
|
|
123
|
-
text(
|
|
124
|
-
`Try a broader term, or browse: ${run} ${note.length > 0 ? 'docs' : 'component --list'}`,
|
|
125
|
-
),
|
|
107
|
+
text(`Try a broader term, or browse: ${run} component --list`),
|
|
126
108
|
);
|
|
127
109
|
return answered;
|
|
128
110
|
}
|
|
@@ -173,7 +155,6 @@ export function registerSearch(program) {
|
|
|
173
155
|
fields,
|
|
174
156
|
format: {command: formatCliCommand, parent: formatCliCommand},
|
|
175
157
|
}),
|
|
176
|
-
...note,
|
|
177
158
|
);
|
|
178
159
|
return answered;
|
|
179
160
|
},
|