@astryxdesign/cli 0.6.4 → 0.6.5-canary.031021b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/README.md +97 -90
- 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/_adapter.d.mts +25 -0
- package/api/component/_adapter.mjs +59 -5
- package/api/component/component.d.mts +6 -3
- package/api/component/component.doc.mjs +37 -17
- package/api/component/component.mjs +249 -9
- package/api/component/component.type.d.mts +25 -0
- package/api/component/component.type.mjs +44 -0
- package/api/discover/_adapter.d.mts +114 -6
- package/api/discover/_adapter.mjs +372 -17
- package/api/discover/_adapter.test.mjs +215 -0
- package/api/discover/_catalog-view.d.mts +115 -0
- package/api/discover/_catalog-view.mjs +203 -0
- package/api/discover/_catalog-view.test.mjs +128 -0
- package/api/discover/detail/detail.d.mts +18 -6
- package/api/discover/detail/detail.mjs +67 -13
- package/api/discover/detail/detail.test.mjs +85 -0
- package/api/discover/detail/item/item.d.mts +26 -0
- package/api/discover/detail/item/item.mjs +78 -0
- package/api/discover/detail/item/item.test.mjs +73 -0
- package/api/discover/discover.d.mts +3 -9
- package/api/discover/discover.doc.mjs +61 -18
- package/api/discover/discover.mjs +220 -36
- package/api/discover/discover.test.mjs +11 -2
- package/api/discover/discover.type.d.mts +147 -8
- package/api/discover/discover.type.mjs +102 -12
- package/api/discover/list/list.d.mts +20 -6
- package/api/discover/list/list.mjs +45 -12
- package/api/discover/list/list.test.mjs +46 -0
- package/api/discover/search/search.d.mts +18 -16
- package/api/discover/search/search.mjs +102 -56
- package/api/discover/search/search.test.mjs +144 -10
- 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.d.mts +8 -3
- package/api/doctor/doctor.doc.mjs +17 -8
- package/api/doctor/doctor.mjs +90 -9
- package/api/doctor/doctor.test.mjs +122 -10
- 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 +1 -0
- package/api/index.mjs +5 -3
- package/api/init/init.doc.mjs +17 -12
- package/api/integration/add-helpers.d.mts +5 -2
- package/api/integration/add-helpers.mjs +36 -9
- 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 +107 -0
- package/api/integration/pack-check.mjs +82 -9
- 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/isError.doc.mjs +1 -1
- 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.doc.mjs +2 -1
- package/api/theme/add/add.mjs +17 -25
- package/api/theme/add/add.rollback.test.mjs +158 -0
- package/api/theme/add/add.staging.test.mjs +40 -23
- package/api/theme/build/build.family.test.mjs +7 -12
- package/api/theme/build/build.mjs +8 -18
- package/api/theme/build/build.rollback.test.mjs +148 -0
- 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/files-changed.test.mjs +111 -0
- package/api/upgrade/run/run.mjs +5 -3
- package/api/upgrade/upgrade.doc.mjs +24 -22
- package/api/upgrade/upgrade.type.mjs +2 -2
- package/assets/codemods/__tests__/runner.test.mjs +3 -1
- package/assets/codemods/file-count.test.mjs +163 -0
- package/assets/codemods/integration-runner.mjs +3 -3
- package/assets/codemods/runner.mjs +5 -4
- 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/internationalization.doc.mjs +7 -5
- 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 +57 -47
- 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/component-lookups.doc.mjs +149 -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 -451
- 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/assets/templates/blocks/components/InternationalizationProvider/InternationalizationProvider01ShippedLocale.tsx +1 -1
- package/authoring/config/config.doc.mjs +9 -1
- package/authoring/config/parse.d.mts +2 -0
- package/authoring/config/parse.mjs +19 -0
- package/authoring/config/parse.test.mjs +8 -0
- package/authoring/config/type.ts +11 -0
- package/authoring/discover/discover.doc.d.mts +13 -0
- package/authoring/discover/discover.doc.mjs +138 -0
- package/authoring/discover/parse.d.mts +24 -0
- package/authoring/discover/parse.mjs +128 -0
- package/authoring/discover/parse.test.mjs +124 -0
- package/authoring/discover/type.ts +87 -0
- 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/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/index.d.mts +1 -0
- package/authoring/index.d.ts +10 -0
- package/authoring/index.mjs +1 -0
- package/authoring/integration/integration.doc.mjs +12 -10
- package/clients/cli/commands/component/index.mjs +152 -55
- package/clients/cli/commands/component-batch.test.mjs +341 -0
- package/clients/cli/commands/component-ownership.test.mjs +89 -0
- package/clients/cli/commands/component.doc.mjs +27 -9
- package/clients/cli/commands/discover.doc.mjs +53 -9
- package/clients/cli/commands/discover.mjs +393 -118
- package/clients/cli/commands/discover.sources.test.mjs +267 -0
- 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/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 +7 -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 +28 -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 +17 -1
- package/clients/cli/lib/json-shim.mjs +24 -14
- package/clients/cli/lib/manifest.mjs +18 -5
- package/clients/cli/lib/manifest.test.mjs +5 -2
- package/clients/cli/lib/parse-error-format.test.mjs +81 -0
- package/foundation/agent-docs/agent-docs.mjs +1 -1
- package/foundation/discovery/authoring-self-docs.mjs +1 -0
- 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/doc-compiler/doc-loads.test.mjs +3 -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/integrations/integrations.d.mts +14 -1
- package/foundation/integrations/integrations.mjs +41 -1
- package/foundation/integrations/integrations.test.mjs +31 -0
- package/foundation/response/batch.type.d.mts +33 -0
- package/foundation/response/batch.type.mjs +34 -0
- 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 +40 -10
- package/foundation/response/response-types.doc.test.mjs +23 -0
- package/foundation/response/response.doc.mjs +11 -10
- package/package.json +9 -9
- package/api/docs/docs.test.mjs +0 -243
- package/api/docs/integration-tree.test.mjs +0 -555
- package/api/docs/integrationDocs.test.mjs +0 -314
- package/api/search/search.test.mjs +0 -512
- 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 -294
- package/foundation/agent-docs/agent-docs.test.mjs +0 -1159
- package/foundation/doc-compiler/tree.test.mjs +0 -598
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file `astryx discover` with a discover source: the list, a package page, a
|
|
5
|
+
* search, and the refused option pair, driven through the real CLI against a
|
|
6
|
+
* hermetic project whose astryx.config sets `discover`. Saved copies go to a
|
|
7
|
+
* temp cache directory, never the project.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {describe, it, expect, beforeEach, afterEach} from 'vitest';
|
|
11
|
+
import * as fs from 'node:fs';
|
|
12
|
+
import * as path from 'node:path';
|
|
13
|
+
import * as os from 'node:os';
|
|
14
|
+
import {runCli} from '../../../test-utils/run-cli.mjs';
|
|
15
|
+
|
|
16
|
+
const CONFIG = `
|
|
17
|
+
const packages = [
|
|
18
|
+
{
|
|
19
|
+
package: '@test/kit',
|
|
20
|
+
integration: 'test-kit',
|
|
21
|
+
aliases: [],
|
|
22
|
+
latest: '3.2.0',
|
|
23
|
+
versions: [
|
|
24
|
+
{version: '3.2.0', publishedAt: '2026-09-29T00:00:00.000Z', prerelease: false, status: 'ok'},
|
|
25
|
+
{version: '3.1.4', publishedAt: '2026-09-01T00:00:00.000Z', prerelease: false, status: 'ok'},
|
|
26
|
+
],
|
|
27
|
+
contributions: [{kind: 'component', name: 'Dial'}],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
package: '@test/charts',
|
|
31
|
+
integration: 'test-charts',
|
|
32
|
+
aliases: [],
|
|
33
|
+
latest: '2.0.0',
|
|
34
|
+
versions: [
|
|
35
|
+
{version: '2.1.0-beta.1', publishedAt: '2026-09-25T00:00:00.000Z', prerelease: true, status: 'ok'},
|
|
36
|
+
{version: '2.0.0', publishedAt: '2026-09-20T00:00:00.000Z', prerelease: false, status: 'ok'},
|
|
37
|
+
],
|
|
38
|
+
contributions: [
|
|
39
|
+
{kind: 'component', name: 'Chart'},
|
|
40
|
+
{kind: 'template', name: 'pages/Report'},
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
package: '@test/boards',
|
|
45
|
+
integration: 'test-boards',
|
|
46
|
+
aliases: [],
|
|
47
|
+
latest: '1.0.0',
|
|
48
|
+
versions: [
|
|
49
|
+
{version: '1.0.0', publishedAt: '2026-09-10T00:00:00.000Z', prerelease: false, status: 'ok'},
|
|
50
|
+
],
|
|
51
|
+
contributions: [{kind: 'template', name: 'pages/DialBoard'}],
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
export default {
|
|
56
|
+
integrations: ['@test/kit'],
|
|
57
|
+
async discover({package: name}) {
|
|
58
|
+
return {
|
|
59
|
+
schemaVersion: 1,
|
|
60
|
+
source: {name: 'Test catalog', generatedAt: '2026-09-30T00:00:00.000Z', complete: true},
|
|
61
|
+
packages: packages.filter(p => name == null || p.package === name),
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
let tmpDir;
|
|
68
|
+
let project;
|
|
69
|
+
/** @type {Record<string, string | undefined>} */
|
|
70
|
+
let savedEnv;
|
|
71
|
+
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-discover-sources-'));
|
|
74
|
+
project = path.join(tmpDir, 'project');
|
|
75
|
+
const pkgDir = path.join(project, 'node_modules', '@test', 'kit');
|
|
76
|
+
fs.mkdirSync(path.join(pkgDir, 'components'), {recursive: true});
|
|
77
|
+
fs.writeFileSync(
|
|
78
|
+
path.join(project, 'package.json'),
|
|
79
|
+
JSON.stringify({
|
|
80
|
+
name: 'proj',
|
|
81
|
+
version: '1.0.0',
|
|
82
|
+
dependencies: {'@test/kit': '3.1.4'},
|
|
83
|
+
}),
|
|
84
|
+
);
|
|
85
|
+
fs.writeFileSync(path.join(project, 'astryx.config.mjs'), CONFIG);
|
|
86
|
+
fs.writeFileSync(
|
|
87
|
+
path.join(pkgDir, 'package.json'),
|
|
88
|
+
JSON.stringify({name: '@test/kit', version: '3.1.4'}),
|
|
89
|
+
);
|
|
90
|
+
fs.writeFileSync(
|
|
91
|
+
path.join(pkgDir, 'astryx.integration.mjs'),
|
|
92
|
+
`export default {components: './components'};\n`,
|
|
93
|
+
);
|
|
94
|
+
fs.writeFileSync(
|
|
95
|
+
path.join(pkgDir, 'components', 'Dial.doc.mjs'),
|
|
96
|
+
`export const docs = {name: 'Dial', usage: {description: 'A dial.'}};\n`,
|
|
97
|
+
);
|
|
98
|
+
fs.writeFileSync(
|
|
99
|
+
path.join(pkgDir, 'components', 'Dial.tsx'),
|
|
100
|
+
`export function Dial() { return null; }\n`,
|
|
101
|
+
);
|
|
102
|
+
// The saved copy lives in the per-user cache; point it at the temp dir.
|
|
103
|
+
savedEnv = {
|
|
104
|
+
HOME: process.env.HOME,
|
|
105
|
+
XDG_CACHE_HOME: process.env.XDG_CACHE_HOME,
|
|
106
|
+
LOCALAPPDATA: process.env.LOCALAPPDATA,
|
|
107
|
+
};
|
|
108
|
+
process.env.HOME = path.join(tmpDir, 'home');
|
|
109
|
+
process.env.XDG_CACHE_HOME = path.join(tmpDir, 'cache');
|
|
110
|
+
process.env.LOCALAPPDATA = path.join(tmpDir, 'localappdata');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
afterEach(() => {
|
|
114
|
+
for (const [key, value] of Object.entries(savedEnv)) {
|
|
115
|
+
if (value === undefined) delete process.env[key];
|
|
116
|
+
else process.env[key] = value;
|
|
117
|
+
}
|
|
118
|
+
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
/** The per-user cache directory this platform uses under the redirected env. */
|
|
122
|
+
function userCacheDir() {
|
|
123
|
+
if (process.platform === 'win32') {
|
|
124
|
+
return path.join(tmpDir, 'localappdata', 'astryx', 'Cache');
|
|
125
|
+
}
|
|
126
|
+
if (process.platform === 'darwin') {
|
|
127
|
+
return path.join(tmpDir, 'home', 'Library', 'Caches', 'astryx');
|
|
128
|
+
}
|
|
129
|
+
return path.join(tmpDir, 'cache', 'astryx');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
describe('astryx discover with a discover source', () => {
|
|
133
|
+
it('lists what the project has and what it could add, in --json', async () => {
|
|
134
|
+
const {status, stdout} = await runCli(['discover', '--json'], {
|
|
135
|
+
cwd: project,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(status).toBe(0);
|
|
139
|
+
const {data, meta} = JSON.parse(stdout);
|
|
140
|
+
expect(data).toEqual([
|
|
141
|
+
expect.objectContaining({
|
|
142
|
+
name: '@test/kit',
|
|
143
|
+
components: ['Dial'],
|
|
144
|
+
version: '3.1.4',
|
|
145
|
+
latest: '3.2.0',
|
|
146
|
+
}),
|
|
147
|
+
]);
|
|
148
|
+
expect(meta.available).toEqual([
|
|
149
|
+
{
|
|
150
|
+
name: '@test/charts',
|
|
151
|
+
components: ['Chart'],
|
|
152
|
+
version: '2.0.0',
|
|
153
|
+
templates: ['pages/Report'],
|
|
154
|
+
source: 'Test catalog',
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: '@test/boards',
|
|
158
|
+
components: [],
|
|
159
|
+
version: '1.0.0',
|
|
160
|
+
templates: ['pages/DialBoard'],
|
|
161
|
+
source: 'Test catalog',
|
|
162
|
+
},
|
|
163
|
+
]);
|
|
164
|
+
expect(meta.sources).toEqual([
|
|
165
|
+
expect.objectContaining({
|
|
166
|
+
name: 'Test catalog',
|
|
167
|
+
from: 'astryx.config',
|
|
168
|
+
status: 'fresh',
|
|
169
|
+
}),
|
|
170
|
+
]);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('prints both sides as records', async () => {
|
|
174
|
+
const {status, stdout} = await runCli(['discover'], {cwd: project});
|
|
175
|
+
|
|
176
|
+
expect(status).toBe(0);
|
|
177
|
+
expect(stdout).toMatch(/^Installed$/m);
|
|
178
|
+
expect(stdout).toMatch(/^Available$/m);
|
|
179
|
+
expect(stdout).toMatch(/^name:\s+@test\/charts$/m);
|
|
180
|
+
expect(stdout).toMatch(/^latest:\s+3\.2\.0$/m);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('shows a package it does not have with its releases and the command that adds it, and never runs it', async () => {
|
|
184
|
+
const {status, stdout} = await runCli(['discover', '@test/charts'], {
|
|
185
|
+
cwd: project,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
expect(status).toBe(0);
|
|
189
|
+
expect(stdout).toMatch(/^installed:\s+false$/m);
|
|
190
|
+
expect(stdout).toContain('- 2.0.0 2026-09-20 latest');
|
|
191
|
+
expect(stdout).not.toContain('2.1.0-beta.1 ');
|
|
192
|
+
expect(stdout).toContain('1 prerelease is not listed.');
|
|
193
|
+
// The verb follows the detected package manager.
|
|
194
|
+
expect(stdout).toMatch(
|
|
195
|
+
/^(npm install|pnpm add|yarn add|bun add) @test\/charts$/m,
|
|
196
|
+
);
|
|
197
|
+
expect(
|
|
198
|
+
fs.existsSync(path.join(project, 'node_modules', '@test', 'charts')),
|
|
199
|
+
).toBe(false);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('says which item is missing from a package only a source lists', async () => {
|
|
203
|
+
const {status, stdout} = await runCli(
|
|
204
|
+
['discover', '@test/charts/Nope', '--json'],
|
|
205
|
+
{cwd: project},
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
expect(status).toBe(1);
|
|
209
|
+
const {code, error} = JSON.parse(stdout);
|
|
210
|
+
expect(code).toBe('ERR_UNKNOWN_COMPONENT');
|
|
211
|
+
expect(error).toBe('"Nope" not found in @test/charts');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('searches every kind in every source', async () => {
|
|
215
|
+
const {status, stdout} = await runCli(['discover', 'report', '--json'], {
|
|
216
|
+
cwd: project,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
expect(status).toBe(0);
|
|
220
|
+
expect(JSON.parse(stdout).data.matches).toEqual([
|
|
221
|
+
{
|
|
222
|
+
package: '@test/charts',
|
|
223
|
+
component: 'pages/Report',
|
|
224
|
+
kind: 'template',
|
|
225
|
+
installed: false,
|
|
226
|
+
},
|
|
227
|
+
]);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('refuses --installed with --available', async () => {
|
|
231
|
+
const {status, stdout} = await runCli(
|
|
232
|
+
['discover', '--installed', '--available', '--json'],
|
|
233
|
+
{cwd: project},
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
expect(status).toBe(1);
|
|
237
|
+
expect(JSON.parse(stdout).code).toBe('ERR_INVALID_OPTION');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('lists every match for a free-text query, even an exact component name, and opens one by its package path', async () => {
|
|
241
|
+
const exact = await runCli(['discover', 'Dial', '--json'], {cwd: project});
|
|
242
|
+
const {type, data} = JSON.parse(exact.stdout);
|
|
243
|
+
expect(type).toBe('discover.search');
|
|
244
|
+
expect(data.matches.map(m => [m.component, m.installed])).toEqual([
|
|
245
|
+
['Dial', true],
|
|
246
|
+
['pages/DialBoard', false],
|
|
247
|
+
]);
|
|
248
|
+
|
|
249
|
+
const opened = await runCli(['discover', '@test/kit/Dial', '--json'], {
|
|
250
|
+
cwd: project,
|
|
251
|
+
});
|
|
252
|
+
expect(JSON.parse(opened.stdout).type).toBe('discover.detail.doc');
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('saves the answer in the user cache and writes nothing into the project', async () => {
|
|
256
|
+
const before = fs.readdirSync(project).sort();
|
|
257
|
+
await runCli(['discover', '--json'], {cwd: project});
|
|
258
|
+
|
|
259
|
+
expect(fs.readdirSync(project).sort()).toEqual(before);
|
|
260
|
+
expect(fs.existsSync(path.join(project, 'node_modules', '.cache'))).toBe(
|
|
261
|
+
false,
|
|
262
|
+
);
|
|
263
|
+
expect(fs.readdirSync(path.join(userCacheDir(), 'discover'))).toHaveLength(
|
|
264
|
+
1,
|
|
265
|
+
);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
@@ -47,7 +47,7 @@ export const doc = {
|
|
|
47
47
|
{label: 'One section', cli: 'astryx docs theme quick-start'},
|
|
48
48
|
{label: 'The CLI docs tree', cli: 'astryx docs cli'},
|
|
49
49
|
{label: 'One API function', cli: 'astryx docs cli/api/functions/search'},
|
|
50
|
-
{label: 'A whole guide', cli: 'astryx docs cli/integrations --full'},
|
|
50
|
+
{label: 'A whole guide', cli: 'astryx docs cli/integrations/quick-start --full'},
|
|
51
51
|
],
|
|
52
52
|
exitCodes: [
|
|
53
53
|
{code: 0, when: 'success'},
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* open one section by its key, and `--full` prints the whole topic. `--json`
|
|
8
8
|
* keeps the docs() contract: a topic returns its whole doc, and `--index` its
|
|
9
9
|
* sections.
|
|
10
|
-
* Supports --detail (full|compact|brief) and --lang (en|zh|dense).
|
|
10
|
+
* Supports --detail (full|compact|brief) and --lang (en|zh|dense). A code
|
|
11
|
+
* block's label prints above its fence, and table cells escape their pipes.
|
|
11
12
|
*
|
|
12
13
|
* Usage:
|
|
13
14
|
* astryx docs List available topics
|
|
@@ -42,21 +43,35 @@ import {doc as docsFn} from '../../../api/docs/docs.doc.mjs';
|
|
|
42
43
|
|
|
43
44
|
// ─── Formatting ──────────────────────────────────────────────────────────────
|
|
44
45
|
|
|
46
|
+
/**
|
|
47
|
+
* A table cell with its pipes escaped. Columns are separated by ` | `, and a
|
|
48
|
+
* union type such as `'light' | 'dark'` is spelled with the same character,
|
|
49
|
+
* so an unescaped cell reads as extra columns. `astryx component` escapes its
|
|
50
|
+
* prop tables the same way.
|
|
51
|
+
* @param {string | undefined} cell
|
|
52
|
+
* @returns {string}
|
|
53
|
+
*/
|
|
54
|
+
function tableCell(cell) {
|
|
55
|
+
return (cell || '').replaceAll('|', '\\|');
|
|
56
|
+
}
|
|
57
|
+
|
|
45
58
|
/**
|
|
46
59
|
* @param {string[]} headers
|
|
47
60
|
* @param {string[][]} rows
|
|
48
61
|
* @returns {string}
|
|
49
62
|
*/
|
|
50
63
|
function formatTable(headers, rows) {
|
|
51
|
-
const
|
|
52
|
-
|
|
64
|
+
const head = headers.map(tableCell);
|
|
65
|
+
const cells = rows.map(r => r.map(tableCell));
|
|
66
|
+
const widths = head.map((h, i) =>
|
|
67
|
+
Math.max(h.length, ...cells.map(r => (r[i] || '').length)),
|
|
53
68
|
);
|
|
54
69
|
const sep = widths.map(w => '-'.repeat(w)).join(' | ');
|
|
55
|
-
const
|
|
56
|
-
const body =
|
|
57
|
-
.map(r => r.map((c, i) =>
|
|
70
|
+
const top = head.map((h, i) => h.padEnd(widths[i])).join(' | ');
|
|
71
|
+
const body = cells
|
|
72
|
+
.map(r => r.map((c, i) => c.padEnd(widths[i])).join(' | '))
|
|
58
73
|
.join('\n');
|
|
59
|
-
return `${
|
|
74
|
+
return `${top}\n${sep}\n${body}`;
|
|
60
75
|
}
|
|
61
76
|
|
|
62
77
|
/**
|
|
@@ -65,15 +80,19 @@ function formatTable(headers, rows) {
|
|
|
65
80
|
* @returns {string}
|
|
66
81
|
*/
|
|
67
82
|
function formatTableCompact(headers, rows) {
|
|
68
|
-
|
|
83
|
+
// An empty cell, such as a Default with none, adds nothing to the line.
|
|
84
|
+
return rows
|
|
85
|
+
.map(r => r.filter(cell => String(cell ?? '').trim() !== '').join(' = '))
|
|
86
|
+
.join('\n');
|
|
69
87
|
}
|
|
70
88
|
|
|
71
89
|
/**
|
|
90
|
+
* One content block as text. Exported for its tests.
|
|
72
91
|
* @param {import('@astryxdesign/cli/authoring').ReferenceContentBlock} block
|
|
73
92
|
* @param {'full' | 'compact' | 'brief'} detail
|
|
74
93
|
* @returns {string | null}
|
|
75
94
|
*/
|
|
76
|
-
function formatBlock(block, detail) {
|
|
95
|
+
export function formatBlock(block, detail) {
|
|
77
96
|
switch (block.type) {
|
|
78
97
|
case 'prose':
|
|
79
98
|
return block.text;
|
|
@@ -84,13 +103,20 @@ function formatBlock(block, detail) {
|
|
|
84
103
|
case 'code':
|
|
85
104
|
if (detail === 'compact' || detail === 'brief') return null;
|
|
86
105
|
{
|
|
87
|
-
|
|
88
|
-
|
|
106
|
+
// The label names the block, so it prints above the fence, not
|
|
107
|
+
// inside it: `// label` is not a comment in bash, CSS, JSON, or
|
|
108
|
+
// HTML, and a reader who copies the block would copy it too.
|
|
109
|
+
const label = block.label
|
|
110
|
+
? `${block.label.replace(/:\s*$/, '')}:\n`
|
|
111
|
+
: '';
|
|
112
|
+
return `${label}\`\`\`${block.lang}\n${block.code}\n\`\`\``;
|
|
89
113
|
}
|
|
90
114
|
|
|
91
115
|
case 'table':
|
|
92
116
|
if (detail === 'brief') {
|
|
93
|
-
return block.rows
|
|
117
|
+
return block.rows
|
|
118
|
+
.map(r => r.slice(0, 2).map(tableCell).join('='))
|
|
119
|
+
.join(' | ');
|
|
94
120
|
}
|
|
95
121
|
if (detail === 'compact') {
|
|
96
122
|
return formatTableCompact(block.headers, block.rows);
|
|
@@ -202,16 +228,23 @@ function emitIndex(index, run) {
|
|
|
202
228
|
}
|
|
203
229
|
|
|
204
230
|
/**
|
|
205
|
-
* One child row of a namespace
|
|
206
|
-
*
|
|
231
|
+
* One child row of a namespace. Namespace and guide route names are already
|
|
232
|
+
* readable, so repeating their titles adds noise (`start-a-template Start a
|
|
233
|
+
* template`). Typed docs keep a distinct title when it carries the real symbol
|
|
234
|
+
* name (`assert-response assertResponse()`).
|
|
207
235
|
* @param {import('../../../api/docs/docs.type.mjs').DocsNodeChild} child
|
|
208
236
|
* @returns {{name: string, summary: string}}
|
|
209
237
|
*/
|
|
210
238
|
function childRow(child) {
|
|
239
|
+
const titleAddsIdentity =
|
|
240
|
+
child.kind !== 'namespace' &&
|
|
241
|
+
child.kind !== 'generic' &&
|
|
242
|
+
child.title !== child.name;
|
|
211
243
|
return {
|
|
212
244
|
name: child.name,
|
|
213
|
-
summary:
|
|
214
|
-
|
|
245
|
+
summary: titleAddsIdentity
|
|
246
|
+
? `${child.title}: ${child.summary}`
|
|
247
|
+
: child.summary,
|
|
215
248
|
};
|
|
216
249
|
}
|
|
217
250
|
|
|
@@ -227,6 +260,17 @@ function emitNode(node, detail, run) {
|
|
|
227
260
|
if (node.kind === 'namespace') {
|
|
228
261
|
emit(
|
|
229
262
|
section(node.title, wrapText(node.summary)),
|
|
263
|
+
// A namespace may author intro `blocks`; they render above its children.
|
|
264
|
+
...(node.content?.length
|
|
265
|
+
? [
|
|
266
|
+
text(
|
|
267
|
+
node.content
|
|
268
|
+
.map(b => formatBlock(b, detail))
|
|
269
|
+
.filter(Boolean)
|
|
270
|
+
.join('\n\n'),
|
|
271
|
+
),
|
|
272
|
+
]
|
|
273
|
+
: []),
|
|
230
274
|
...node.slots.flatMap(slot => [
|
|
231
275
|
// A namespace with one slot titled like itself needs no second heading.
|
|
232
276
|
...(node.slots.length === 1 && slot.title === node.title
|
|
@@ -235,7 +279,6 @@ function emitNode(node, detail, run) {
|
|
|
235
279
|
records(slot.children.map(childRow), {
|
|
236
280
|
fields: ['name', 'summary'],
|
|
237
281
|
layout: 'inline',
|
|
238
|
-
overflow: 'truncate',
|
|
239
282
|
}),
|
|
240
283
|
]),
|
|
241
284
|
text(
|
|
@@ -10,9 +10,10 @@ export const doc = {
|
|
|
10
10
|
namespace: 'cli/commands',
|
|
11
11
|
summary: 'Check an integration\'s docs: the docs tree they add, every link, and overlaps with Core topics',
|
|
12
12
|
description:
|
|
13
|
-
'
|
|
13
|
+
'Checks the docs tree the package adds, every link in its docs, and overlaps with Core topics. ' +
|
|
14
|
+
'Intentional replacements and extensions are information. ' +
|
|
14
15
|
'A same-name topic without `replaces` or `extends` is an accidental conflict ' +
|
|
15
|
-
'and fails until the author declares the relationship or renames it.',
|
|
16
|
+
'and fails until the author declares the relationship or renames it. See {@link generic:check-your-docs}.',
|
|
16
17
|
fn: 'integrationDocConflicts',
|
|
17
18
|
args: [
|
|
18
19
|
{
|
|
@@ -13,6 +13,7 @@ vi.mock('../../../foundation/discovery/template-conflict-release.mjs', () => ({
|
|
|
13
13
|
expandedTemplateConflictSchemaActive: () => true,
|
|
14
14
|
}));
|
|
15
15
|
import * as fs from 'node:fs';
|
|
16
|
+
import * as os from 'node:os';
|
|
16
17
|
import * as path from 'node:path';
|
|
17
18
|
import {Command} from 'commander';
|
|
18
19
|
import {discoverCoreTemplates} from '../../../foundation/discovery/template-adapter.mjs';
|
|
@@ -344,6 +345,35 @@ describe('doctor integration — command', () => {
|
|
|
344
345
|
expect(process.exitCode).toBe(1);
|
|
345
346
|
});
|
|
346
347
|
|
|
348
|
+
it('components exits 1 when Core is missing, with no [ok] after the failure', async () => {
|
|
349
|
+
// Outside the repo, so nothing above the package resolves Core.
|
|
350
|
+
const outside = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-no-core-'));
|
|
351
|
+
const previousTmp = tmpDir;
|
|
352
|
+
tmpDir = outside;
|
|
353
|
+
try {
|
|
354
|
+
writeComponentIntegration('AcmeCarousel');
|
|
355
|
+
expect(findCoreDir(outside)).toBeNull();
|
|
356
|
+
process.chdir(outside);
|
|
357
|
+
|
|
358
|
+
await createProgram().parseAsync([
|
|
359
|
+
'node',
|
|
360
|
+
'astryx',
|
|
361
|
+
'doctor',
|
|
362
|
+
'integration',
|
|
363
|
+
'components',
|
|
364
|
+
]);
|
|
365
|
+
|
|
366
|
+
const printed = logCalls.join('\n');
|
|
367
|
+
expect(printed).toContain('core_not_found');
|
|
368
|
+
expect(printed).not.toContain('[ok]');
|
|
369
|
+
expect(process.exitCode).toBe(1);
|
|
370
|
+
} finally {
|
|
371
|
+
tmpDir = previousTmp;
|
|
372
|
+
process.chdir(previousCwd);
|
|
373
|
+
fs.rmSync(outside, {recursive: true, force: true});
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
347
377
|
it('components warns with the exact package-qualified command', async () => {
|
|
348
378
|
const coreDir = findCoreDir(tmpDir);
|
|
349
379
|
expect(coreDir).not.toBeNull();
|
|
@@ -433,6 +463,29 @@ describe('doctor integration — command', () => {
|
|
|
433
463
|
expect(process.exitCode).toBeUndefined();
|
|
434
464
|
});
|
|
435
465
|
|
|
466
|
+
it('docs exits 1 for a placement that hides a guide, with no [ok] after the failure', async () => {
|
|
467
|
+
writeDocIntegration({name: 'deploying'});
|
|
468
|
+
fs.writeFileSync(
|
|
469
|
+
path.join(tmpDir, 'docs', 'deploying.doc.mjs'),
|
|
470
|
+
"export default {type: 'generic', name: 'deploying', title: 'Deploying', description: 'Deploy.', placement: {parent: 'namespace:nope', slot: 'guides'}, sections: [{title: 'Deploy', content: [{type: 'prose', text: 'Deploy.'}]}]};\n",
|
|
471
|
+
);
|
|
472
|
+
process.chdir(tmpDir);
|
|
473
|
+
|
|
474
|
+
await createProgram().parseAsync([
|
|
475
|
+
'node',
|
|
476
|
+
'astryx',
|
|
477
|
+
'doctor',
|
|
478
|
+
'integration',
|
|
479
|
+
'docs',
|
|
480
|
+
]);
|
|
481
|
+
|
|
482
|
+
const printed = logCalls.join('\n');
|
|
483
|
+
expect(printed).toContain('[fail]');
|
|
484
|
+
expect(printed).toContain('invalid_doc_graph');
|
|
485
|
+
expect(printed).not.toContain('[ok]');
|
|
486
|
+
expect(process.exitCode).toBe(1);
|
|
487
|
+
});
|
|
488
|
+
|
|
436
489
|
it('docs exits 1 for an accidental same-name Core topic', async () => {
|
|
437
490
|
const [coreTopic] = Object.keys(discoverBuiltinTopics());
|
|
438
491
|
writeDocIntegration({name: coreTopic});
|
|
@@ -16,7 +16,9 @@ export const doc = {
|
|
|
16
16
|
summary: 'Diagnose Astryx projects and integration packages',
|
|
17
17
|
description:
|
|
18
18
|
'Runs read-only project health diagnostics by default: Node version, @astryxdesign/core ' +
|
|
19
|
-
'install and version alignment, themes, config,
|
|
19
|
+
'install and version alignment, themes, config, integrations (linked without a config entry, ' +
|
|
20
|
+
'provider identity, contribution issues), agent docs, core peer dependencies, package manager, ' +
|
|
21
|
+
"and the docs the CLI reads. It writes nothing, but loading astryx.config runs that file's code. " +
|
|
20
22
|
'The `integration` subcommands provide authoring checks for one integration package.',
|
|
21
23
|
fn: 'doctor',
|
|
22
24
|
subcommands: ['integration'],
|
|
@@ -36,6 +36,8 @@ import {doc as integrationTemplateConflictsFn} from '../../../api/integration/in
|
|
|
36
36
|
import {doc as integrationComponentConflictsFn} from '../../../api/integration/integrationComponentConflicts.doc.mjs';
|
|
37
37
|
import {doc as integrationDocConflictsFn} from '../../../api/integration/integrationDocConflicts.doc.mjs';
|
|
38
38
|
import {NO_RESULT_SET} from '../../../foundation/debug/index.mjs';
|
|
39
|
+
import {cliError} from '../lib/cli-error.mjs';
|
|
40
|
+
import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
|
|
39
41
|
|
|
40
42
|
const STATUS = {
|
|
41
43
|
pass: '[ok]',
|
|
@@ -181,8 +183,10 @@ function printComponentConflicts(data) {
|
|
|
181
183
|
),
|
|
182
184
|
...issueBlocks(data.issues),
|
|
183
185
|
];
|
|
186
|
+
// An [ok] after a failed check reads as a pass: say nothing it could not check.
|
|
187
|
+
const failed = data.issues.some(issue => issue.severity === 'error');
|
|
184
188
|
if (data.conflicts.length === 0) {
|
|
185
|
-
output.push(text('[ok] No component names conflict with Core.'));
|
|
189
|
+
if (!failed) output.push(text('[ok] No component names conflict with Core.'));
|
|
186
190
|
} else {
|
|
187
191
|
output.push(
|
|
188
192
|
records(data.conflicts, {
|
|
@@ -224,7 +228,9 @@ function printDocConflicts(data) {
|
|
|
224
228
|
);
|
|
225
229
|
}
|
|
226
230
|
if (data.findings.length === 0) {
|
|
227
|
-
|
|
231
|
+
if (!data.issues.some(issue => issue.severity === 'error')) {
|
|
232
|
+
output.push(text('[ok] No doc topics overlap with Core.'));
|
|
233
|
+
}
|
|
228
234
|
} else {
|
|
229
235
|
output.push(
|
|
230
236
|
records(data.findings, {
|
|
@@ -315,19 +321,57 @@ async function runAuthoringCheck(program, pkg, kind) {
|
|
|
315
321
|
return NO_RESULT_SET;
|
|
316
322
|
}
|
|
317
323
|
|
|
324
|
+
/**
|
|
325
|
+
* The first word after a command group, which names a subcommand it does not
|
|
326
|
+
* have, or null.
|
|
327
|
+
* @param {import('commander').Command | undefined} invoked
|
|
328
|
+
* @returns {string | null}
|
|
329
|
+
*/
|
|
330
|
+
function unknownWord(invoked) {
|
|
331
|
+
const word = (invoked?.args ?? []).find(arg => !String(arg).startsWith('-'));
|
|
332
|
+
return word == null ? null : String(word);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Report an unknown subcommand, in text as in JSON, with the ones the group has.
|
|
337
|
+
* @param {import('commander').Command} group
|
|
338
|
+
* @param {string} label the group's full name
|
|
339
|
+
* @param {string} word
|
|
340
|
+
*/
|
|
341
|
+
function unknownSubcommand(group, label, word) {
|
|
342
|
+
return cliError(`unknown subcommand '${label} ${word}'`, {
|
|
343
|
+
suggestions: group.commands.map(child => ({
|
|
344
|
+
name: child.name(),
|
|
345
|
+
reason: 'available subcommand',
|
|
346
|
+
})),
|
|
347
|
+
code: ERROR_CODES.ERR_UNKNOWN_SUBCOMMAND,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
318
351
|
/**
|
|
319
352
|
* Register `astryx doctor` and its integration-authoring leaves.
|
|
320
353
|
* @param {import('commander').Command} program
|
|
321
354
|
*/
|
|
322
355
|
export function registerDoctor(program) {
|
|
323
|
-
|
|
356
|
+
/** @type {import('commander').Command} */
|
|
357
|
+
let doctorCmd;
|
|
358
|
+
doctorCmd = defineCommand(program, doctorCommand, {
|
|
324
359
|
fn: doctorFn,
|
|
325
|
-
|
|
360
|
+
// `doctor integrations` is a mistyped subcommand, not a project check.
|
|
361
|
+
action: async (options, invoked) => {
|
|
362
|
+
const word = unknownWord(invoked);
|
|
363
|
+
if (word != null) return unknownSubcommand(doctorCmd, 'doctor', word);
|
|
364
|
+
return runProjectDoctor(program);
|
|
365
|
+
},
|
|
326
366
|
});
|
|
327
367
|
/** @type {import('commander').Command} */
|
|
328
368
|
let integrationCmd;
|
|
329
369
|
integrationCmd = defineCommand(doctorCmd, doctorIntegrationGroup, {
|
|
330
|
-
action: () => {
|
|
370
|
+
action: (options, invoked) => {
|
|
371
|
+
const word = unknownWord(invoked);
|
|
372
|
+
if (word != null) {
|
|
373
|
+
return unknownSubcommand(integrationCmd, 'doctor integration', word);
|
|
374
|
+
}
|
|
331
375
|
integrationCmd.outputHelp();
|
|
332
376
|
return NO_RESULT_SET;
|
|
333
377
|
},
|
|
@@ -11,9 +11,12 @@ export const doc = {
|
|
|
11
11
|
name: 'gap-report',
|
|
12
12
|
displayName: 'astryx gap-report',
|
|
13
13
|
namespace: 'cli/commands',
|
|
14
|
-
summary: '
|
|
14
|
+
summary: 'Report a missing component or feature to the package that owns it',
|
|
15
15
|
description:
|
|
16
|
-
'Reports a missing component, variant, layout, styling, accessibility, API, or documentation capability
|
|
16
|
+
'Reports a missing component, variant, layout, styling, accessibility, API, or documentation capability to the package that owns it: the --package you name, else the one package that provides the component, else Core. ' +
|
|
17
|
+
'Every configured handler receives the report: the project config handler first, then each integration handler in config order. Public handlers run only with --confirm-public; internal handlers always run. A failing handler does not stop the others. ' +
|
|
18
|
+
"With no handler it files a GitHub issue for the owning package, only with --confirm-public (without it nothing is sent), or returns the package's issues URL when that is not on GitHub. " +
|
|
19
|
+
'The report records whether an agent or a person ran it.',
|
|
17
20
|
fn: 'gapReport',
|
|
18
21
|
args: [
|
|
19
22
|
{
|
|
@@ -45,12 +48,14 @@ export const doc = {
|
|
|
45
48
|
{
|
|
46
49
|
flag: '--package <pkg>',
|
|
47
50
|
param: 'options.package',
|
|
48
|
-
description:
|
|
51
|
+
description:
|
|
52
|
+
'Package that owns the gap: @astryxdesign/core or a loaded integration. Overrides automatic routing; needed when more than one package provides the component',
|
|
49
53
|
},
|
|
50
54
|
{
|
|
51
55
|
flag: '--confirm-public',
|
|
52
56
|
param: 'options.confirmPublic',
|
|
53
|
-
description:
|
|
57
|
+
description:
|
|
58
|
+
'Allow public delivery: public handlers run, and with no handler it files a GitHub issue with your gh login',
|
|
54
59
|
},
|
|
55
60
|
{
|
|
56
61
|
flag: '--list-categories',
|
|
@@ -62,13 +67,9 @@ export const doc = {
|
|
|
62
67
|
examples: [
|
|
63
68
|
{label: 'List categories', cli: 'astryx gap-report --list-categories'},
|
|
64
69
|
{
|
|
65
|
-
label: '
|
|
70
|
+
label: 'Prepare a report (nothing public happens without --confirm-public)',
|
|
66
71
|
cli: "astryx gap-report Button --category missing_variant --reason 'Need a compact size'",
|
|
67
72
|
},
|
|
68
|
-
{
|
|
69
|
-
label: 'Confirm public filing',
|
|
70
|
-
cli: "astryx gap-report Button --category docs_gap --reason 'Missing keyboard example' --confirm-public",
|
|
71
|
-
},
|
|
72
73
|
],
|
|
73
74
|
exitCodes: [
|
|
74
75
|
{
|