@astryxdesign/cli 0.6.3-canary.ea2f048 → 0.6.3-canary.ebaebc4
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 +2 -1
- package/api/build/build.type.d.mts +2 -2
- package/api/build/build.type.mjs +2 -2
- package/api/component/component.type.d.mts +6 -6
- package/api/component/component.type.mjs +19 -19
- package/api/discover/discover.type.d.mts +4 -4
- package/api/discover/discover.type.mjs +10 -10
- package/api/docs/_adapter.d.mts +37 -24
- package/api/docs/_adapter.mjs +169 -83
- package/api/docs/compiled-topics.test.mjs +78 -0
- package/api/docs/detail/detail.mjs +14 -63
- package/api/docs/detail/section/section.d.mts +1 -1
- package/api/docs/detail/section/section.mjs +44 -20
- package/api/docs/detail/section/section.test.mjs +41 -0
- package/api/docs/docs.d.mts +7 -2
- package/api/docs/docs.doc.mjs +27 -10
- package/api/docs/docs.mjs +16 -9
- package/api/docs/docs.test.mjs +6 -0
- package/api/docs/docs.type.d.mts +40 -3
- package/api/docs/docs.type.mjs +36 -8
- package/api/docs/index/index.d.mts +18 -0
- package/api/docs/index/index.mjs +32 -0
- package/api/docs/index/index.test.mjs +62 -0
- package/api/docs/integrationDocs.test.mjs +106 -0
- package/api/doctor/doctor.d.mts +48 -0
- package/api/doctor/doctor.mjs +232 -0
- package/api/doctor/doctor.test.mjs +196 -0
- package/api/hook/hook.type.d.mts +3 -3
- package/api/hook/hook.type.mjs +11 -11
- package/api/hook/list/list.d.mts +1 -1
- package/api/integration/add-contribution.mjs +5 -3
- package/api/integration/add-contribution.test.mjs +4 -4
- package/api/integration/integration-authoring.type.d.mts +1 -1
- package/api/integration/pack-check.mjs +49 -7
- package/api/integration/pack-check.test.mjs +249 -0
- package/api/search/search.d.mts +1 -1
- package/api/search/search.mjs +5 -5
- package/api/search/search.type.d.mts +2 -2
- package/api/search/search.type.mjs +1 -1
- package/api/swizzle/swizzle.type.d.mts +2 -2
- package/api/swizzle/swizzle.type.mjs +2 -2
- package/api/template/template.d.mts +1 -1
- package/api/template/template.type.d.mts +6 -6
- package/api/template/template.type.mjs +12 -12
- package/api/theme/build/build.mjs +20 -6
- package/api/theme/build/build.test.mjs +127 -0
- package/api/theme/palette/generate/generate.mjs +1 -1
- package/api/theme/palette/generate/generator.d.mts +10 -13
- package/api/theme/palette/generate/generator.mjs +7 -3
- package/api/theme/theme.type.d.mts +170 -11
- package/api/theme/theme.type.mjs +94 -27
- package/api/upgrade/_adapter.mjs +71 -5
- package/api/upgrade/project-context.test.mjs +272 -0
- package/api/upgrade/upgrade.doc.mjs +4 -3
- package/api/upgrade/upgrade.type.d.mts +5 -5
- package/api/upgrade/upgrade.type.mjs +11 -11
- package/assets/codemods/integration-discovery.mjs +40 -2
- package/assets/codemods/integration-discovery.test.mjs +58 -0
- package/assets/codemods/transforms/v0.3.0/__tests__/unwrap-authoring-factories.test.mjs +27 -5
- package/assets/codemods/transforms/v0.3.0/unwrap-authoring-factories.mjs +20 -5
- package/assets/docs/README.md +9 -0
- package/assets/docs/authoring.doc.mjs +14 -0
- package/assets/docs/cli-integrations.doc.mjs +86 -15
- package/assets/docs/styling-libraries.doc.mjs +1 -1
- package/assets/docs/working-with-ai.doc.mjs +1 -1
- package/authoring/_shared/contract.ts +22 -0
- package/authoring/codemod/codemod.doc.mjs +6 -1
- package/authoring/codemod/parse.d.mts +8 -8
- package/authoring/codemod/parse.mjs +8 -6
- package/authoring/config/parse.d.mts +13 -13
- package/authoring/config/parse.mjs +8 -8
- package/authoring/config/type.ts +3 -3
- package/authoring/debug/parse.d.mts +5 -5
- package/authoring/debug/parse.mjs +3 -3
- package/authoring/doctypes/_schema.d.mts +788 -23
- package/authoring/doctypes/_schema.mjs +492 -39
- package/authoring/doctypes/base/graph-fields.doc.d.mts +9 -0
- package/authoring/doctypes/base/graph-fields.doc.mjs +62 -0
- package/authoring/doctypes/base/type.ts +40 -0
- package/authoring/doctypes/command/command.doc.mjs +3 -2
- package/authoring/doctypes/command/parse.d.mts +2 -2
- package/authoring/doctypes/command/parse.mjs +1 -1
- package/authoring/doctypes/command/type.ts +3 -2
- package/authoring/doctypes/component/component.doc.mjs +6 -3
- package/authoring/doctypes/component/parse.d.mts +2 -2
- package/authoring/doctypes/component/parse.mjs +1 -1
- package/authoring/doctypes/component/type.ts +4 -3
- package/authoring/doctypes/enum/parse.d.mts +2 -2
- package/authoring/doctypes/enum/parse.mjs +1 -1
- package/authoring/doctypes/enum/type.ts +3 -1
- package/authoring/doctypes/function/function.doc.mjs +4 -0
- package/authoring/doctypes/function/parse.d.mts +2 -2
- package/authoring/doctypes/function/parse.mjs +1 -1
- package/authoring/doctypes/function/type.ts +6 -2
- package/authoring/doctypes/hook/hook.doc.mjs +4 -0
- package/authoring/doctypes/hook/parse.d.mts +2 -2
- package/authoring/doctypes/hook/parse.mjs +1 -1
- package/authoring/doctypes/hook/type.ts +3 -2
- package/authoring/doctypes/legacy.d.mts +8 -6
- package/authoring/doctypes/legacy.mjs +5 -4
- package/authoring/doctypes/load-contract.test.mjs +207 -0
- package/authoring/doctypes/namespace/namespace.doc.d.mts +9 -0
- package/authoring/doctypes/namespace/namespace.doc.mjs +132 -0
- package/authoring/doctypes/namespace/parse.d.mts +12 -0
- package/authoring/doctypes/namespace/parse.mjs +25 -0
- package/authoring/doctypes/namespace/parse.test.mjs +165 -0
- package/authoring/doctypes/namespace/type.ts +71 -0
- package/authoring/doctypes/parse.d.mts +20 -18
- package/authoring/doctypes/parse.mjs +16 -10
- package/authoring/doctypes/parse.test.mjs +77 -3
- package/authoring/doctypes/reference/parse.d.mts +2 -2
- package/authoring/doctypes/reference/parse.mjs +8 -5
- package/authoring/doctypes/reference/reference.doc.mjs +17 -4
- package/authoring/doctypes/reference/type.ts +51 -5
- package/authoring/doctypes/schema/parse.d.mts +2 -2
- package/authoring/doctypes/schema/parse.mjs +1 -1
- package/authoring/doctypes/schema/type.ts +3 -2
- package/authoring/doctypes/template/parse.d.mts +92 -1
- package/authoring/doctypes/template/parse.mjs +36 -2
- package/authoring/doctypes/template/parse.test.mjs +8 -2
- package/authoring/doctypes/template/template.doc.mjs +4 -0
- package/authoring/doctypes/template/type.ts +5 -2
- package/authoring/doctypes/types.ts +10 -9
- package/authoring/gap-report/parse.d.mts +10 -10
- package/authoring/gap-report/parse.mjs +6 -6
- package/authoring/gap-report/type.ts +1 -1
- package/authoring/identity/identity.doc.d.mts +9 -0
- package/authoring/identity/identity.doc.mjs +61 -0
- package/authoring/identity/type.ts +132 -0
- package/authoring/index.d.mts +1 -0
- package/authoring/index.d.ts +49 -17
- package/authoring/index.mjs +1 -0
- package/authoring/integration/integration.doc.mjs +13 -6
- package/authoring/integration/parse.d.mts +2 -2
- package/authoring/integration/parse.mjs +1 -1
- package/authoring/integration/parse.test.mjs +10 -1
- package/authoring/integration/schema.d.mts +6 -4
- package/authoring/integration/schema.mjs +9 -3
- package/authoring/integration/type.ts +23 -6
- package/authoring/shadcn/receipt.d.mts +6 -6
- package/clients/cli/commands/docs.doc.mjs +13 -3
- package/clients/cli/commands/docs.mjs +121 -21
- package/clients/cli/commands/docs.test.mjs +88 -0
- package/clients/cli/commands/integration-authoring.test.mjs +13 -9
- package/clients/cli/commands/theme-palette-generate.doc.mjs +8 -4
- package/clients/cli/commands/upgrade.doc.mjs +2 -2
- package/clients/cli/formatters/index.mjs +162 -1
- package/clients/cli/formatters/index.test.mjs +91 -0
- package/clients/cli/lib/manifest.mjs +7 -2
- package/foundation/config/project.mjs +21 -6
- package/foundation/discovery/authoring-self-docs.d.mts +69 -0
- package/foundation/discovery/authoring-self-docs.mjs +214 -0
- package/foundation/discovery/authoring-self-docs.test.mjs +154 -0
- package/foundation/discovery/component-discovery.d.mts +1 -1
- package/foundation/discovery/component-discovery.mjs +2 -1
- package/foundation/discovery/docs-discovery.d.mts +11 -4
- package/foundation/discovery/docs-discovery.mjs +208 -88
- package/foundation/discovery/docs-discovery.test.mjs +279 -13
- package/foundation/discovery/docs-output-budget.d.mts +28 -0
- package/foundation/discovery/docs-output-budget.mjs +50 -0
- package/foundation/discovery/docs-section-key.d.mts +98 -0
- package/foundation/discovery/docs-section-key.mjs +221 -0
- package/foundation/discovery/docs-section-key.test.mjs +224 -0
- package/foundation/discovery/template-adapter.mjs +2 -1
- package/foundation/discovery/theming-targets.test.mjs +4 -0
- package/foundation/doc-compiler/compile.d.mts +162 -0
- package/foundation/doc-compiler/compile.mjs +262 -0
- package/foundation/doc-compiler/doc-compiler.test.mjs +687 -0
- package/foundation/doc-compiler/ir.d.mts +9 -0
- package/foundation/doc-compiler/ir.mjs +287 -0
- package/foundation/doc-compiler/lenses.d.mts +33 -0
- package/foundation/doc-compiler/lenses.mjs +127 -0
- package/foundation/identity/provider-identity.d.mts +90 -0
- package/foundation/identity/provider-identity.mjs +320 -0
- package/foundation/identity/provider-identity.test.mjs +254 -0
- package/foundation/identity/providers.d.mts +7 -0
- package/foundation/identity/providers.mjs +16 -0
- package/foundation/integrations/autolink.mjs +12 -5
- package/foundation/integrations/integration-warnings.mjs +6 -0
- package/foundation/integrations/integrations.d.mts +46 -2
- package/foundation/integrations/integrations.mjs +167 -8
- package/foundation/integrations/integrations.test.mjs +384 -1
- package/foundation/integrations/provider-conflicts.test.mjs +125 -0
- package/foundation/integrations/validate-contributions.d.mts +2 -0
- package/foundation/integrations/validate-contributions.mjs +10 -0
- package/foundation/response/json-contract.test.mjs +46 -17
- package/foundation/response/response-types.doc.mjs +6 -1
- package/package.json +9 -11
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Public type surface for an Astryx integration manifest
|
|
5
5
|
* (`astryx.integration.{ts,mjs,js}`, sibling to the integration package's
|
|
6
|
-
* package.json). Identity
|
|
7
|
-
*
|
|
8
|
-
* the CLI validates it via
|
|
6
|
+
* package.json). Identity defaults to the package name, but `providerId` can keep
|
|
7
|
+
* a stable logical identity across an explicit package rename. Authors write a
|
|
8
|
+
* plain object against {@link AstryxIntegration}; the CLI validates it via
|
|
9
|
+
* `parseIntegration` at the load boundary.
|
|
9
10
|
*
|
|
10
11
|
* The manifest module may also carry `debug` and `gapReport` NAMED exports.
|
|
11
12
|
* They are not fields here on purpose: a CLI released before a given manifest
|
|
@@ -13,11 +14,20 @@
|
|
|
13
14
|
* named export is simply not read. See the `cli-integrations` doc topic.
|
|
14
15
|
*/
|
|
15
16
|
export interface AstryxIntegration {
|
|
17
|
+
/** Stable logical provider ID. Omit to use package.json#name. Set this only
|
|
18
|
+
* when a package rename must preserve existing artifact IDs. */
|
|
19
|
+
providerId?: string;
|
|
16
20
|
/** Relative path to the components/docs root (resolved to absolute). */
|
|
17
21
|
components?: string;
|
|
18
22
|
/** Relative path to the templates root (resolved to absolute). */
|
|
19
23
|
templates?: string;
|
|
20
|
-
/** Relative path to the codemods root (resolved to absolute).
|
|
24
|
+
/** Relative path to the codemods root (resolved to absolute).
|
|
25
|
+
* The root uses a version-folder-first layout:
|
|
26
|
+
* `<codemodsRoot>/<version>/<id>.<ext>`, where `<version>` is an exact
|
|
27
|
+
* semver string (e.g. `0.2.0`, no `v` prefix) and `<id>` is a kebab-case
|
|
28
|
+
* module basename. Each module default-exports a codemod envelope stamped
|
|
29
|
+
* `type: 'code'` or `type: 'config'`. Codemod ids must be unique within
|
|
30
|
+
* a package across all versions. */
|
|
21
31
|
codemods?: string;
|
|
22
32
|
/** Relative path to the reference-docs (topics) root (resolved to
|
|
23
33
|
* absolute). Every `{topic}.doc.{ts,mjs,js}` under it is a topic the CLI
|
|
@@ -25,7 +35,14 @@ export interface AstryxIntegration {
|
|
|
25
35
|
* `replace` or `extend` a built-in topic; see the ReferenceDoc type. */
|
|
26
36
|
docs?: string;
|
|
27
37
|
/** Relative path to the source-theme catalog root (resolved to absolute).
|
|
28
|
-
* The root contains `manifest.json` plus one directory per theme slug.
|
|
38
|
+
* The root contains `manifest.json` plus one directory per theme slug.
|
|
39
|
+
* `manifest.json` is `{ "version": 1, "themes": [...] }` where each
|
|
40
|
+
* entry requires `slug`, `displayName`, `description` (string),
|
|
41
|
+
* `maintained` (boolean), `entry` (source file relative to `themes/<slug>/`),
|
|
42
|
+
* `exportName` (a valid JS identifier naming the runtime export), and
|
|
43
|
+
* `files` (non-empty array of filenames relative to `themes/<slug>/`).
|
|
44
|
+
* Every file listed must exist on disk; the entry file must also appear
|
|
45
|
+
* in `files`. */
|
|
29
46
|
themes?: string;
|
|
30
47
|
/** Static package guidance appended to the CLI-owned managed agent block. */
|
|
31
48
|
agentDocs?: {
|
|
@@ -49,4 +66,4 @@ export type {
|
|
|
49
66
|
GapReportCategory,
|
|
50
67
|
GapReportTarget,
|
|
51
68
|
GapReportHandlerReceipt,
|
|
52
|
-
} from '../gap-report/type';
|
|
69
|
+
} from '../gap-report/type.js';
|
|
@@ -63,7 +63,7 @@ export function createRegistryReceipt(input: {
|
|
|
63
63
|
name: string;
|
|
64
64
|
path: string;
|
|
65
65
|
aliases: string[];
|
|
66
|
-
kind: "
|
|
66
|
+
kind: "page" | "block" | "example" | "showcase";
|
|
67
67
|
};
|
|
68
68
|
source: {
|
|
69
69
|
package: "@astryxdesign/cli";
|
|
@@ -83,7 +83,7 @@ export function createRegistryReceipt(input: {
|
|
|
83
83
|
name: string;
|
|
84
84
|
path: string;
|
|
85
85
|
aliases: string[];
|
|
86
|
-
kind: "
|
|
86
|
+
kind: "page" | "block" | "example" | "showcase";
|
|
87
87
|
};
|
|
88
88
|
source: {
|
|
89
89
|
package: "@astryxdesign/cli";
|
|
@@ -112,7 +112,7 @@ export function parseRegistryReceipt(input: unknown): {
|
|
|
112
112
|
name: string;
|
|
113
113
|
path: string;
|
|
114
114
|
aliases: string[];
|
|
115
|
-
kind: "
|
|
115
|
+
kind: "page" | "block" | "example" | "showcase";
|
|
116
116
|
};
|
|
117
117
|
source: {
|
|
118
118
|
package: "@astryxdesign/cli";
|
|
@@ -132,7 +132,7 @@ export function parseRegistryReceipt(input: unknown): {
|
|
|
132
132
|
name: string;
|
|
133
133
|
path: string;
|
|
134
134
|
aliases: string[];
|
|
135
|
-
kind: "
|
|
135
|
+
kind: "page" | "block" | "example" | "showcase";
|
|
136
136
|
};
|
|
137
137
|
source: {
|
|
138
138
|
package: "@astryxdesign/cli";
|
|
@@ -165,8 +165,8 @@ export const registryReceiptSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
165
165
|
path: z.ZodString;
|
|
166
166
|
aliases: z.ZodArray<z.ZodString>;
|
|
167
167
|
kind: z.ZodEnum<{
|
|
168
|
-
block: "block";
|
|
169
168
|
page: "page";
|
|
169
|
+
block: "block";
|
|
170
170
|
example: "example";
|
|
171
171
|
showcase: "showcase";
|
|
172
172
|
}>;
|
|
@@ -190,8 +190,8 @@ export const registryReceiptSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
190
190
|
path: z.ZodString;
|
|
191
191
|
aliases: z.ZodArray<z.ZodString>;
|
|
192
192
|
kind: z.ZodEnum<{
|
|
193
|
-
block: "block";
|
|
194
193
|
page: "page";
|
|
194
|
+
block: "block";
|
|
195
195
|
example: "example";
|
|
196
196
|
showcase: "showcase";
|
|
197
197
|
}>;
|
|
@@ -16,22 +16,32 @@ export const doc = {
|
|
|
16
16
|
summary: 'Print reference docs',
|
|
17
17
|
description:
|
|
18
18
|
'Reads the reference docs: with no topic it lists every topic; a topic prints that ' +
|
|
19
|
-
'full doc;
|
|
20
|
-
'(
|
|
19
|
+
'full doc; `--index` lists its sections instead, each with the key to read it by; a ' +
|
|
20
|
+
'topic plus a section prints that section (by key, exact title, or a unique part of ' +
|
|
21
|
+
'a title).',
|
|
21
22
|
fn: 'docs',
|
|
22
23
|
args: [
|
|
23
24
|
{name: 'topic', param: 'topic', required: false},
|
|
24
25
|
{name: 'section', param: 'section', required: false},
|
|
25
26
|
],
|
|
27
|
+
options: [
|
|
28
|
+
{
|
|
29
|
+
flag: '--index',
|
|
30
|
+
param: 'options.index',
|
|
31
|
+
description: "List the topic's sections and their keys instead of printing the whole topic",
|
|
32
|
+
},
|
|
33
|
+
],
|
|
26
34
|
examples: [
|
|
27
35
|
{label: 'List topics', cli: 'astryx docs'},
|
|
28
36
|
{label: 'One topic as JSON', cli: 'astryx docs spacing --json'},
|
|
37
|
+
{label: "A topic's sections", cli: 'astryx docs theme --index'},
|
|
38
|
+
{label: 'One section', cli: 'astryx docs theme quick-start'},
|
|
29
39
|
],
|
|
30
40
|
exitCodes: [
|
|
31
41
|
{code: 0, when: 'success'},
|
|
32
42
|
{
|
|
33
43
|
code: 1,
|
|
34
|
-
when: 'unknown topic, or a section that matches no
|
|
44
|
+
when: 'unknown topic, or a section that matches no section or more than one',
|
|
35
45
|
},
|
|
36
46
|
],
|
|
37
47
|
related: ['search', 'component', 'hook', 'template'],
|
|
@@ -3,18 +3,29 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @file docs command — Print Astryx reference docs
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* A topic prints its whole doc; `--index` lists its sections instead, so a
|
|
7
|
+
* reader can open one section by its key.
|
|
7
8
|
* Supports --detail (full|compact|brief) and --lang (en|zh|dense).
|
|
8
9
|
*
|
|
9
10
|
* Usage:
|
|
10
11
|
* astryx docs List available topics
|
|
11
|
-
* astryx docs <topic> Print
|
|
12
|
+
* astryx docs <topic> Print the whole topic
|
|
13
|
+
* astryx docs <topic> --index List the topic's sections
|
|
12
14
|
* astryx docs <topic> <section> Print one section
|
|
13
15
|
*/
|
|
14
16
|
|
|
15
17
|
import {getCliInvocation} from '../../../foundation/env/package-manager.mjs';
|
|
16
18
|
import {jsonOut} from '../../../foundation/response/json.mjs';
|
|
17
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
emit,
|
|
21
|
+
section,
|
|
22
|
+
records,
|
|
23
|
+
text,
|
|
24
|
+
code,
|
|
25
|
+
wrapText,
|
|
26
|
+
displayWidth,
|
|
27
|
+
WRAP_WIDTH,
|
|
28
|
+
} from '../formatters/index.mjs';
|
|
18
29
|
import {cliError} from '../lib/cli-error.mjs';
|
|
19
30
|
import {defineCommand} from '../lib/define-command.mjs';
|
|
20
31
|
import {resultSet} from '../../../foundation/debug/index.mjs';
|
|
@@ -41,6 +52,28 @@ function formatTable(headers, rows) {
|
|
|
41
52
|
return `${head}\n${sep}\n${body}`;
|
|
42
53
|
}
|
|
43
54
|
|
|
55
|
+
/**
|
|
56
|
+
* A table too wide for {@link WRAP_WIDTH}: one `header: cell` line per cell and
|
|
57
|
+
* a blank line between rows, so nothing runs off the side of a terminal.
|
|
58
|
+
* @param {string[]} headers
|
|
59
|
+
* @param {string[][]} rows
|
|
60
|
+
* @returns {string}
|
|
61
|
+
*/
|
|
62
|
+
function formatTableVertical(headers, rows) {
|
|
63
|
+
const width = Math.max(...headers.map(h => h.length)) + 2;
|
|
64
|
+
return rows
|
|
65
|
+
.map(row =>
|
|
66
|
+
headers
|
|
67
|
+
.map((h, i) =>
|
|
68
|
+
wrapText(`${`${h}:`.padEnd(width)}${row[i] ?? ''}`, {
|
|
69
|
+
indent: ' '.repeat(width),
|
|
70
|
+
}),
|
|
71
|
+
)
|
|
72
|
+
.join('\n'),
|
|
73
|
+
)
|
|
74
|
+
.join('\n\n');
|
|
75
|
+
}
|
|
76
|
+
|
|
44
77
|
/**
|
|
45
78
|
* @param {string[]} headers
|
|
46
79
|
* @param {string[][]} rows
|
|
@@ -58,7 +91,7 @@ function formatTableCompact(headers, rows) {
|
|
|
58
91
|
function formatBlock(block, detail) {
|
|
59
92
|
switch (block.type) {
|
|
60
93
|
case 'prose':
|
|
61
|
-
return block.text;
|
|
94
|
+
return wrapText(block.text);
|
|
62
95
|
|
|
63
96
|
case 'heading':
|
|
64
97
|
return `${'#'.repeat(block.level || 3)} ${block.text}`;
|
|
@@ -77,16 +110,37 @@ function formatBlock(block, detail) {
|
|
|
77
110
|
if (detail === 'compact') {
|
|
78
111
|
return formatTableCompact(block.headers, block.rows);
|
|
79
112
|
}
|
|
80
|
-
|
|
113
|
+
{
|
|
114
|
+
const table = formatTable(block.headers, block.rows);
|
|
115
|
+
return table.split('\n').some(line => displayWidth(line) > WRAP_WIDTH)
|
|
116
|
+
? formatTableVertical(block.headers, block.rows)
|
|
117
|
+
: table;
|
|
118
|
+
}
|
|
81
119
|
|
|
82
120
|
case 'list': {
|
|
83
|
-
const prefix =
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
121
|
+
const prefix =
|
|
122
|
+
block.style === 'ordered'
|
|
123
|
+
? (/** @type {number} */ i) => `${i + 1}. `
|
|
124
|
+
: block.style === 'dont'
|
|
125
|
+
? () => 'x '
|
|
126
|
+
: block.style === 'do'
|
|
127
|
+
? () => '+ '
|
|
128
|
+
: () => '- ';
|
|
129
|
+
return block.items
|
|
130
|
+
.map((item, i) => {
|
|
131
|
+
const head = prefix(i);
|
|
132
|
+
return wrapText(`${head}${item}`, {indent: ' '.repeat(head.length)});
|
|
133
|
+
})
|
|
134
|
+
.join('\n');
|
|
88
135
|
}
|
|
89
136
|
|
|
137
|
+
case 'workflow':
|
|
138
|
+
case 'collection':
|
|
139
|
+
case 'reference':
|
|
140
|
+
throw new Error(
|
|
141
|
+
`Documentation block "${block.type}" requires the compiled graph renderer.`,
|
|
142
|
+
);
|
|
143
|
+
|
|
90
144
|
default:
|
|
91
145
|
return null;
|
|
92
146
|
}
|
|
@@ -107,7 +161,8 @@ function formatSection(section, detail) {
|
|
|
107
161
|
return `${section.title}: ${first.split('\n')[0]}`;
|
|
108
162
|
}
|
|
109
163
|
|
|
110
|
-
const heading =
|
|
164
|
+
const heading =
|
|
165
|
+
detail === 'compact' ? `[${section.title}]` : `## ${section.title}`;
|
|
111
166
|
return `${heading}\n\n${blocks.join('\n\n')}`;
|
|
112
167
|
}
|
|
113
168
|
|
|
@@ -118,25 +173,51 @@ function formatSection(section, detail) {
|
|
|
118
173
|
*/
|
|
119
174
|
function formatReferenceFull(docs, detail) {
|
|
120
175
|
if (detail === 'brief') {
|
|
121
|
-
const header = `${docs.title}: ${docs.description}
|
|
176
|
+
const header = wrapText(`${docs.title}: ${docs.description}`);
|
|
122
177
|
const sections = docs.sections.map(s => formatSection(s, detail));
|
|
123
178
|
return `${header}\n${sections.join('\n')}`;
|
|
124
179
|
}
|
|
125
180
|
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
181
|
+
const description = wrapText(docs.description);
|
|
182
|
+
const header =
|
|
183
|
+
detail === 'compact'
|
|
184
|
+
? `# ${docs.title}\n${description}`
|
|
185
|
+
: `# ${docs.title}\n\n${description}`;
|
|
129
186
|
const sections = docs.sections.map(s => formatSection(s, detail));
|
|
130
187
|
const sep = detail === 'compact' ? '\n\n' : '\n\n';
|
|
131
188
|
return `${header}\n\n${sections.join(sep)}`;
|
|
132
189
|
}
|
|
133
190
|
|
|
191
|
+
/**
|
|
192
|
+
* A topic's section index: what the topic is, one line per section with the
|
|
193
|
+
* key to read it by, and how to read further.
|
|
194
|
+
* @param {import('../../../api/docs/docs.type.mjs').DocsIndex} index
|
|
195
|
+
* @param {string} run
|
|
196
|
+
*/
|
|
197
|
+
function emitIndex(index, run) {
|
|
198
|
+
emit(
|
|
199
|
+
section(index.title, index.description ? wrapText(index.description) : undefined),
|
|
200
|
+
records(index.sections, {
|
|
201
|
+
fields: ['id', 'title', 'summary'],
|
|
202
|
+
layout: 'inline',
|
|
203
|
+
overflow: 'truncate',
|
|
204
|
+
}),
|
|
205
|
+
text(
|
|
206
|
+
[
|
|
207
|
+
`Read one section: ${run} docs ${index.name} <section>`,
|
|
208
|
+
`Read everything: ${run} docs ${index.name}`,
|
|
209
|
+
].join('\n'),
|
|
210
|
+
),
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
134
214
|
/**
|
|
135
215
|
* What the run answered with. A named topic (or one of its sections) resolves
|
|
136
216
|
* or throws, so it is always a direct match of one doc; the bare form lists
|
|
137
217
|
* every topic there is.
|
|
138
218
|
*
|
|
139
219
|
* @param {import('../../../api/docs/docs.type.mjs').DocsListResponse
|
|
220
|
+
* | import('../../../api/docs/docs.type.mjs').DocsIndexResponse
|
|
140
221
|
* | import('../../../api/docs/docs.type.mjs').DocsDetailResponse
|
|
141
222
|
* | import('../../../api/docs/docs.type.mjs').DocsDetailSectionResponse} result
|
|
142
223
|
* @returns {import('../../../foundation/debug/command-result.mjs').CommandResult}
|
|
@@ -155,7 +236,11 @@ function summarize(result) {
|
|
|
155
236
|
export function registerDocs(program) {
|
|
156
237
|
defineCommand(program, docsCommand, {
|
|
157
238
|
fn: docsFn,
|
|
158
|
-
action: async (
|
|
239
|
+
action: async (
|
|
240
|
+
/** @type {string | undefined} */ topic,
|
|
241
|
+
/** @type {string | undefined} */ sectionName,
|
|
242
|
+
/** @type {{index?: boolean}} */ options = {},
|
|
243
|
+
) => {
|
|
159
244
|
const run = getCliInvocation();
|
|
160
245
|
const lang = program.opts().lang || null;
|
|
161
246
|
const zh = program.opts().zh || false;
|
|
@@ -165,11 +250,17 @@ export function registerDocs(program) {
|
|
|
165
250
|
|
|
166
251
|
let result;
|
|
167
252
|
try {
|
|
168
|
-
result = await docsApi(topic, sectionName, {
|
|
253
|
+
result = await docsApi(topic, sectionName, {
|
|
254
|
+
lang,
|
|
255
|
+
zh,
|
|
256
|
+
dense,
|
|
257
|
+
index: Boolean(options.index),
|
|
258
|
+
});
|
|
169
259
|
} catch (e) {
|
|
170
260
|
// docs API throws structured errors with {name, reason} suggestions —
|
|
171
261
|
// pass them through untouched so the CLI envelope matches the API.
|
|
172
|
-
const err =
|
|
262
|
+
const err =
|
|
263
|
+
/** @type {import('../../../api/error.mjs').AstryxError} */ (e);
|
|
173
264
|
return cliError(err.message, {
|
|
174
265
|
suggestions: err.suggestions || [],
|
|
175
266
|
code: err.code,
|
|
@@ -188,17 +279,26 @@ export function registerDocs(program) {
|
|
|
188
279
|
// description), then the usage footer as plain prose.
|
|
189
280
|
emit(
|
|
190
281
|
section('Available docs'),
|
|
191
|
-
records(result.data, {
|
|
282
|
+
records(result.data, {
|
|
283
|
+
fields: ['topic', 'description'],
|
|
284
|
+
layout: 'inline',
|
|
285
|
+
}),
|
|
192
286
|
text(
|
|
193
287
|
[
|
|
194
|
-
`Usage: ${run} docs <topic
|
|
195
|
-
` ${run} docs <topic>
|
|
288
|
+
`Usage: ${run} docs <topic> read the whole topic`,
|
|
289
|
+
` ${run} docs <topic> --index list its sections`,
|
|
290
|
+
` ${run} docs <topic> <section> read one section`,
|
|
196
291
|
].join('\n'),
|
|
197
292
|
),
|
|
198
293
|
);
|
|
199
294
|
break;
|
|
200
295
|
}
|
|
201
296
|
|
|
297
|
+
case 'docs.index': {
|
|
298
|
+
emitIndex(result.data, run);
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
|
|
202
302
|
case 'docs.detail': {
|
|
203
303
|
emit(code(formatReferenceFull(result.data, detail)));
|
|
204
304
|
break;
|
|
@@ -6,6 +6,8 @@ import * as path from 'node:path';
|
|
|
6
6
|
import * as os from 'node:os';
|
|
7
7
|
import {Command} from 'commander';
|
|
8
8
|
import {registerDocs} from './docs.mjs';
|
|
9
|
+
import {runCli} from '../../../test-utils/run-cli.mjs';
|
|
10
|
+
import {displayWidth} from '../formatters/index.mjs';
|
|
9
11
|
|
|
10
12
|
let tmpDir;
|
|
11
13
|
|
|
@@ -100,3 +102,89 @@ describe('migration docs', () => {
|
|
|
100
102
|
expect(output).toContain('Map shadcn and Radix Primitives');
|
|
101
103
|
});
|
|
102
104
|
});
|
|
105
|
+
|
|
106
|
+
describe('progressive reads', () => {
|
|
107
|
+
const SLOW = 60_000;
|
|
108
|
+
/** @param {string} out */
|
|
109
|
+
const widest = out => Math.max(...out.split('\n').map(line => line.length));
|
|
110
|
+
|
|
111
|
+
it('lists every topic on one line each', async () => {
|
|
112
|
+
const {status, stdout} = await runCli(['docs']);
|
|
113
|
+
expect(status).toBe(0);
|
|
114
|
+
expect(stdout).toMatch(/^principles +\S/m);
|
|
115
|
+
expect(widest(stdout)).toBeLessThanOrEqual(120);
|
|
116
|
+
}, SLOW);
|
|
117
|
+
|
|
118
|
+
it("prints a topic's section index with the keys to read by", async () => {
|
|
119
|
+
const {status, stdout} = await runCli(['docs', 'theme', '--index']);
|
|
120
|
+
expect(status).toBe(0);
|
|
121
|
+
expect(stdout).toMatch(/^quick-start +Quick Start/m);
|
|
122
|
+
expect(stdout).toContain('docs theme <section>');
|
|
123
|
+
expect(stdout).toMatch(/Read everything: +\S.* docs theme$/m);
|
|
124
|
+
expect(widest(stdout)).toBeLessThanOrEqual(120);
|
|
125
|
+
}, SLOW);
|
|
126
|
+
|
|
127
|
+
it('prints one section by its key', async () => {
|
|
128
|
+
const {status, stdout} = await runCli(['docs', 'theme', 'quick-start']);
|
|
129
|
+
expect(status).toBe(0);
|
|
130
|
+
expect(stdout).toMatch(/^## Quick Start/m);
|
|
131
|
+
}, SLOW);
|
|
132
|
+
|
|
133
|
+
it('prints the whole topic by default, as before', async () => {
|
|
134
|
+
const index = await runCli(['docs', 'theme', '--index']);
|
|
135
|
+
const full = await runCli(['docs', 'theme']);
|
|
136
|
+
expect(full.status).toBe(0);
|
|
137
|
+
expect(full.stdout).toMatch(/^## Quick Start/m);
|
|
138
|
+
expect(full.stdout.length).toBeGreaterThan(index.stdout.length * 3);
|
|
139
|
+
expect((await runCli(['--detail', 'full', 'docs', 'theme'])).stdout).toBe(
|
|
140
|
+
full.stdout,
|
|
141
|
+
);
|
|
142
|
+
expect(widest(full.stdout.replace(/```[\s\S]*?```/g, ''))).toBeLessThanOrEqual(
|
|
143
|
+
120,
|
|
144
|
+
);
|
|
145
|
+
}, SLOW);
|
|
146
|
+
|
|
147
|
+
it('returns the matching envelopes as JSON', async () => {
|
|
148
|
+
const envelope = async args => JSON.parse((await runCli([...args, '--json'])).stdout);
|
|
149
|
+
expect((await envelope(['docs', 'theme'])).type).toBe('docs.detail');
|
|
150
|
+
expect((await envelope(['docs', 'theme', '--index'])).type).toBe(
|
|
151
|
+
'docs.index',
|
|
152
|
+
);
|
|
153
|
+
expect((await envelope(['docs', 'theme', 'quick-start'])).type).toBe(
|
|
154
|
+
'docs.detail.section',
|
|
155
|
+
);
|
|
156
|
+
}, SLOW);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe('text width in every language', () => {
|
|
160
|
+
const SLOW = 60_000;
|
|
161
|
+
/** Widest line outside code blocks, in terminal columns. */
|
|
162
|
+
const widest = out => {
|
|
163
|
+
let inCode = false;
|
|
164
|
+
let max = 0;
|
|
165
|
+
for (const line of out.split('\n')) {
|
|
166
|
+
if (/^\s*```/.test(line)) {
|
|
167
|
+
inCode = !inCode;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
// A single unbreakable token (a long URL) cannot wrap without breaking it.
|
|
171
|
+
const oneToken = !/\s/.test(line.trim());
|
|
172
|
+
if (!inCode && !line.startsWith('#') && !oneToken) {
|
|
173
|
+
max = Math.max(max, displayWidth(line));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return max;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
it.each([
|
|
180
|
+
[['docs', 'theme', '--index', '--lang', 'zh']],
|
|
181
|
+
[['docs', 'theme', '--lang', 'zh']],
|
|
182
|
+
[['--detail', 'full', 'docs', 'theme', '--lang', 'zh']],
|
|
183
|
+
[['--detail', 'full', 'docs', 'internationalization']],
|
|
184
|
+
[['--detail', 'full', 'docs', 'styling']],
|
|
185
|
+
])('%j fits in 120 columns', async args => {
|
|
186
|
+
const {status, stdout} = await runCli(args);
|
|
187
|
+
expect(status).toBe(0);
|
|
188
|
+
expect(widest(stdout)).toBeLessThanOrEqual(120);
|
|
189
|
+
}, SLOW);
|
|
190
|
+
});
|
|
@@ -120,7 +120,7 @@ describe('integration authoring CLI', () => {
|
|
|
120
120
|
);
|
|
121
121
|
});
|
|
122
122
|
|
|
123
|
-
it('
|
|
123
|
+
it('pack-check rejects a generated component without an exports map (no-map false green)', async () => {
|
|
124
124
|
const added = await runCli(
|
|
125
125
|
['integration', 'add', 'component', 'AcmeWidget', '--json'],
|
|
126
126
|
tmpDir,
|
|
@@ -131,19 +131,23 @@ describe('integration authoring CLI', () => {
|
|
|
131
131
|
['integration', 'pack', '--check', '--json'],
|
|
132
132
|
tmpDir,
|
|
133
133
|
);
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
// Without an exports map, the extensionless import cannot resolve —
|
|
135
|
+
// pack-check must fail, not false-green.
|
|
136
|
+
expect(checked.status).not.toBe(0);
|
|
137
|
+
const envelope = parseEnvelope(checked.stdout);
|
|
138
|
+
expect(envelope).toMatchObject({
|
|
136
139
|
type: 'integration.pack-check',
|
|
137
140
|
data: {
|
|
138
141
|
name: '@acme/widgets',
|
|
139
|
-
|
|
140
|
-
packable: true,
|
|
141
|
-
contributions: {
|
|
142
|
-
local: {components: ['AcmeWidget']},
|
|
143
|
-
packed: {components: ['AcmeWidget']},
|
|
144
|
-
},
|
|
142
|
+
packable: false,
|
|
145
143
|
},
|
|
146
144
|
});
|
|
145
|
+
expect(envelope.data.issues).toContainEqual(
|
|
146
|
+
expect.objectContaining({
|
|
147
|
+
severity: 'error',
|
|
148
|
+
message: expect.stringContaining('AcmeWidget'),
|
|
149
|
+
}),
|
|
150
|
+
);
|
|
147
151
|
});
|
|
148
152
|
|
|
149
153
|
it('requires the explicit --check gate on pack', async () => {
|
|
@@ -18,7 +18,11 @@ export const doc = {
|
|
|
18
18
|
'Without --out it prints a preview. With --out it writes a candidate file and detached ' +
|
|
19
19
|
'receipt. --preview writes a standardized, self-contained HTML review artifact. ' +
|
|
20
20
|
'TypeScript output is directly importable and contains no generator dependency. ' +
|
|
21
|
-
'JSON is also supported. Existing author-owned files are left untouched unless --overwrite is explicit.'
|
|
21
|
+
'JSON is also supported. Existing author-owned files are left untouched unless --overwrite is explicit. ' +
|
|
22
|
+
'When used in a theme integration, keep the palette request under the theme slug, ' +
|
|
23
|
+
'write the candidate and receipt under that same slug, import the candidate from the theme source, ' +
|
|
24
|
+
"and list all three paths in the theme catalog entry's `files` array " +
|
|
25
|
+
'so `astryx theme add` copies them into the consumer project.',
|
|
22
26
|
fn: 'themePaletteGenerate',
|
|
23
27
|
args: [{name: 'config', param: 'configPath', required: true}],
|
|
24
28
|
options: [
|
|
@@ -42,15 +46,15 @@ export const doc = {
|
|
|
42
46
|
examples: [
|
|
43
47
|
{
|
|
44
48
|
label: 'Preview candidate JSON',
|
|
45
|
-
cli: 'astryx theme palette generate palette.config.json',
|
|
49
|
+
cli: 'astryx theme palette generate themes/ocean/palette.config.json',
|
|
46
50
|
},
|
|
47
51
|
{
|
|
48
52
|
label: 'Write candidate and receipt',
|
|
49
|
-
cli: 'astryx theme palette generate palette.config.json --out ocean.palette.ts',
|
|
53
|
+
cli: 'astryx theme palette generate themes/ocean/palette.config.json --out themes/ocean/tokens/ocean.palette.ts',
|
|
50
54
|
},
|
|
51
55
|
{
|
|
52
56
|
label: 'Write candidate, receipt, and review preview',
|
|
53
|
-
cli: 'astryx theme palette generate palette.config.json --out ocean.palette.ts --preview ocean.palette.html',
|
|
57
|
+
cli: 'astryx theme palette generate themes/ocean/palette.config.json --out themes/ocean/tokens/ocean.palette.ts --preview themes/ocean/tokens/ocean.palette.html',
|
|
54
58
|
},
|
|
55
59
|
],
|
|
56
60
|
exitCodes: [
|
|
@@ -53,10 +53,10 @@ export const doc = {
|
|
|
53
53
|
'Exclude named codemods (repeatable). Re-run past a failed codemod by skipping it.',
|
|
54
54
|
},
|
|
55
55
|
{
|
|
56
|
-
flag: '--integration <package
|
|
56
|
+
flag: '--integration <package>',
|
|
57
57
|
param: 'options.integration',
|
|
58
58
|
description:
|
|
59
|
-
'Explicit integration
|
|
59
|
+
'Explicit integration specifier (repeatable). Resolved beneath node_modules; absolute paths and `.` or `..` segments are rejected.',
|
|
60
60
|
default: [],
|
|
61
61
|
},
|
|
62
62
|
{
|