@astryxdesign/cli 0.1.6-canary.e8d4c3f → 0.1.6-canary.eb23da7
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/package.json +9 -9
- package/src/api/template.mjs +1 -1
- package/src/codemods/__tests__/registry.test.mjs +0 -1
- package/src/codemods/registry.mjs +0 -1
- package/src/config.mjs +14 -5
- package/src/doc.mjs +261 -17
- package/src/integration.mjs +15 -4
- package/src/lib/component-format.mjs +13 -45
- package/src/lib/component-format.test.mjs +1 -95
- package/src/lib/component-loader.mjs +3 -52
- package/src/lib/hook-format.mjs +3 -8
- package/src/template.mjs +67 -9
- package/src/types/config.d.ts +66 -11
- package/src/types/doc.d.ts +135 -19
- package/src/types/integration.d.ts +18 -7
- package/src/types/template-api.d.ts +50 -14
- package/templates/pages/ide/page.tsx +41 -35
- package/src/codemods/transforms/v0.1.7/__tests__/migrate-table-tableprops-to-direct-props.test.mjs +0 -120
- package/src/codemods/transforms/v0.1.7/index.mjs +0 -19
- package/src/codemods/transforms/v0.1.7/migrate-table-tableprops-to-direct-props.mjs +0 -188
- package/src/lib/componentDocOverlay.test.mjs +0 -111
- package/src/schemas/doc-schema.mjs +0 -226
- package/src/schemas/template-schema.mjs +0 -47
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/cli",
|
|
3
|
-
"version": "0.1.6-canary.
|
|
3
|
+
"version": "0.1.6-canary.eb23da7",
|
|
4
4
|
"displayName": "CLI",
|
|
5
5
|
"description": "Scaffold projects, browse templates, generate themes, and get agent-ready docs from the command line.",
|
|
6
6
|
"author": "Meta Open Source",
|
|
@@ -79,10 +79,10 @@
|
|
|
79
79
|
"zod": "^4.4.3"
|
|
80
80
|
},
|
|
81
81
|
"peerDependencies": {
|
|
82
|
-
"@astryxdesign/charts": "0.1.6-canary.
|
|
83
|
-
"@astryxdesign/core": "0.1.6-canary.
|
|
84
|
-
"@astryxdesign/lab": "0.1.6-canary.
|
|
85
|
-
"@astryxdesign/theme-neutral": "0.1.6-canary.
|
|
82
|
+
"@astryxdesign/charts": "0.1.6-canary.eb23da7",
|
|
83
|
+
"@astryxdesign/core": "0.1.6-canary.eb23da7",
|
|
84
|
+
"@astryxdesign/lab": "0.1.6-canary.eb23da7",
|
|
85
|
+
"@astryxdesign/theme-neutral": "0.1.6-canary.eb23da7",
|
|
86
86
|
"gpt-tokenizer": "^3.4.0"
|
|
87
87
|
},
|
|
88
88
|
"peerDependenciesMeta": {
|
|
@@ -100,10 +100,10 @@
|
|
|
100
100
|
}
|
|
101
101
|
},
|
|
102
102
|
"devDependencies": {
|
|
103
|
-
"@astryxdesign/charts": "0.1.6-canary.
|
|
104
|
-
"@astryxdesign/core": "0.1.6-canary.
|
|
105
|
-
"@astryxdesign/lab": "0.1.6-canary.
|
|
106
|
-
"@astryxdesign/theme-neutral": "0.1.6-canary.
|
|
103
|
+
"@astryxdesign/charts": "0.1.6-canary.eb23da7",
|
|
104
|
+
"@astryxdesign/core": "0.1.6-canary.eb23da7",
|
|
105
|
+
"@astryxdesign/lab": "0.1.6-canary.eb23da7",
|
|
106
|
+
"@astryxdesign/theme-neutral": "0.1.6-canary.eb23da7",
|
|
107
107
|
"gpt-tokenizer": "^3.4.0"
|
|
108
108
|
},
|
|
109
109
|
"scripts": {
|
package/src/api/template.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import * as fs from 'node:fs';
|
|
|
8
8
|
import * as path from 'node:path';
|
|
9
9
|
import {createJiti} from 'jiti';
|
|
10
10
|
import {loadModuleWithSchema} from '../lib/module-loader.mjs';
|
|
11
|
-
import {TemplateEnvelopeSchema} from '../
|
|
11
|
+
import {TemplateEnvelopeSchema} from '../template.mjs';
|
|
12
12
|
import {CLI_ROOT, discoverExternalPackages} from '../utils/paths.mjs';
|
|
13
13
|
import {
|
|
14
14
|
assertWithin,
|
|
@@ -21,7 +21,6 @@ const registry = new Map([
|
|
|
21
21
|
['0.1.2', () => import('./transforms/v0.1.2/index.mjs')],
|
|
22
22
|
['0.1.3', () => import('./transforms/v0.1.3/index.mjs')],
|
|
23
23
|
['0.1.5', () => import('./transforms/v0.1.5/index.mjs')],
|
|
24
|
-
['0.1.7', () => import('./transforms/v0.1.7/index.mjs')],
|
|
25
24
|
]);
|
|
26
25
|
|
|
27
26
|
// Re-export from the shared utility so registry callers and other consumers
|
package/src/config.mjs
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Type-preserving helper for the Astryx config file.
|
|
5
|
+
*
|
|
6
|
+
* This is an intentionally tiny runtime identity function: it returns its
|
|
7
|
+
* argument unchanged. Its value is the exported TypeScript surface from
|
|
8
|
+
* `@astryxdesign/cli/config`, so config files get editor/type feedback without
|
|
9
|
+
* coupling to CLI internals. Validation is NOT performed here — it happens at
|
|
10
|
+
* the load boundary (see `loadModuleWithSchema` + `AstryxConfigSchema`).
|
|
11
|
+
*
|
|
12
|
+
* @template {import('./types/config').AstryxConfig} T
|
|
13
|
+
* @param {T} config
|
|
14
|
+
* @returns {T}
|
|
8
15
|
*/
|
|
9
|
-
export
|
|
16
|
+
export function createConfig(config) {
|
|
17
|
+
return config;
|
|
18
|
+
}
|
package/src/doc.mjs
CHANGED
|
@@ -1,27 +1,271 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @file
|
|
4
|
+
* @file Public doc-authoring API.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
6
|
+
* A doc describes a documentable unit of the design system so the CLI and docs
|
|
7
|
+
* surfaces can list, search, and render it. There are three kinds, each a tiny
|
|
8
|
+
* runtime identity helper that stamps a `type` discriminant (mirroring how
|
|
9
|
+
* `createPageTemplate`/`createBlockTemplate` stamp `type`):
|
|
10
|
+
*
|
|
11
|
+
* - `createComponentDoc` -> `type: 'component'` — a component (or a family).
|
|
12
|
+
* - `createFunctionDoc` -> `type: 'function'` — any function, including
|
|
13
|
+
* hooks: an "inputs + outputs" surface (`params` + `returns`).
|
|
14
|
+
* - `createDoc` -> `type: 'generic'` — reference/topic docs.
|
|
15
|
+
*
|
|
16
|
+
* Like `createConfig`/`createIntegration`/`createBlockTemplate`, these factories
|
|
17
|
+
* do NOT validate. Validation happens at the LOAD boundary, where doc discovery
|
|
18
|
+
* runs the loaded value through {@link ComponentDocSchema}. That schema handles
|
|
19
|
+
* BOTH the new stamped formats and the legacy loose `export const docs = {...}`
|
|
20
|
+
* shape, so existing docs keep loading unchanged (a later PR migrates them).
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import {z} from 'zod';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A single documented prop. Mirrors `PropDoc` from
|
|
27
|
+
* `@astryxdesign/core/docs-types`. Only `name`, `type`, and `description` are
|
|
28
|
+
* required; everything else is optional. Extra fields (e.g. `slotElements`)
|
|
29
|
+
* pass through so the schema does not have to track every evolution of the
|
|
30
|
+
* rich playground/element surface.
|
|
31
|
+
*/
|
|
32
|
+
const PropSchema = z
|
|
33
|
+
.object({
|
|
34
|
+
name: z.string().min(1, 'prop name is required'),
|
|
35
|
+
type: z.string().min(1, 'prop type is required'),
|
|
36
|
+
description: z.string(),
|
|
37
|
+
default: z.string().optional(),
|
|
38
|
+
required: z.boolean().optional(),
|
|
39
|
+
})
|
|
40
|
+
.passthrough();
|
|
41
|
+
|
|
42
|
+
/** A single documented function/hook parameter (mirrors `HookParamDoc`). */
|
|
43
|
+
const ParamSchema = z
|
|
44
|
+
.object({
|
|
45
|
+
name: z.string().min(1, 'param name is required'),
|
|
46
|
+
type: z.string().min(1, 'param type is required'),
|
|
47
|
+
description: z.string(),
|
|
48
|
+
default: z.string().optional(),
|
|
49
|
+
required: z.boolean().optional(),
|
|
50
|
+
})
|
|
51
|
+
.passthrough();
|
|
52
|
+
|
|
53
|
+
/** A single documented function/hook return field (mirrors `HookReturnDoc`). */
|
|
54
|
+
const ReturnSchema = z
|
|
55
|
+
.object({
|
|
56
|
+
name: z.string().min(1, 'return name is required'),
|
|
57
|
+
type: z.string().min(1, 'return type is required'),
|
|
58
|
+
description: z.string(),
|
|
59
|
+
})
|
|
60
|
+
.passthrough();
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Fields shared by every doc kind. Deliberately permissive on the rich blobs
|
|
64
|
+
* (usage/theming/playground) — those are large and still evolving, so they are
|
|
65
|
+
* `z.unknown()` passthrough rather than enumerated.
|
|
66
|
+
*
|
|
67
|
+
* `group` vs `parent` are distinct concepts and intentionally separate fields:
|
|
68
|
+
* - `group` is a FLAT sidebar bucket label. Many docs can share a group; it
|
|
69
|
+
* is not an inheritance key.
|
|
70
|
+
* - `parent` is a DIRECTED inheritance/composition pointer — a doc naming the
|
|
71
|
+
* doc it belongs to (e.g. a sub-component naming its parent component). The
|
|
72
|
+
* legacy `subComponentOf` field is a synonym; both map to the same concept.
|
|
73
|
+
*
|
|
74
|
+
* `relatedDocs` is a single flat curated cross-reference list. It replaces the
|
|
75
|
+
* legacy split `relatedComponents` + `relatedHooks`; a downstream reader can
|
|
76
|
+
* merge the legacy pair into it.
|
|
77
|
+
*/
|
|
78
|
+
const BaseDocFields = {
|
|
79
|
+
/** Name of the documented unit, without any prefix. Required. */
|
|
80
|
+
name: z.string().min(1, 'name is required'),
|
|
81
|
+
/** Human-readable display name for gallery/sidebar. */
|
|
82
|
+
displayName: z.string().optional(),
|
|
83
|
+
/** One-line/short description. */
|
|
84
|
+
description: z.string().optional(),
|
|
85
|
+
/** Usage documentation (description, best practices, anatomy, slotElements). */
|
|
86
|
+
usage: z.unknown().optional(),
|
|
87
|
+
/** Flat sidebar grouping label (not an inheritance key). */
|
|
88
|
+
group: z.string().optional(),
|
|
89
|
+
/** Overview-page functional category. */
|
|
90
|
+
category: z.string().optional(),
|
|
91
|
+
/** CLI fuzzy-search keywords. */
|
|
92
|
+
keywords: z.array(z.string()).optional(),
|
|
93
|
+
/** Directed inheritance/composition pointer to the doc this belongs to. */
|
|
94
|
+
parent: z.string().optional(),
|
|
95
|
+
/** Flat curated cross-reference list of related doc names. */
|
|
96
|
+
relatedDocs: z.array(z.string()).optional(),
|
|
97
|
+
/** Hide the whole doc from human-facing UI (stays importable/discoverable). */
|
|
98
|
+
hidden: z.boolean().optional(),
|
|
99
|
+
/** Exclude from the categorized overview page (kept in sidebar/CLI). */
|
|
100
|
+
isHiddenFromOverview: z.boolean().optional(),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* New-format schema for a stamped component doc (`type: 'component'`). The
|
|
105
|
+
* top-level component keys are enumerated, but nested blobs stay loose so real
|
|
106
|
+
* docs are not rejected. `.passthrough()` keeps unknown top-level fields
|
|
107
|
+
* (translations, element descriptors, ...) flowing through.
|
|
108
|
+
*/
|
|
109
|
+
export const ComponentDocKindSchema = z
|
|
110
|
+
.object({
|
|
111
|
+
...BaseDocFields,
|
|
112
|
+
type: z.literal('component'),
|
|
113
|
+
props: z.array(PropSchema),
|
|
114
|
+
theming: z.unknown().optional(),
|
|
115
|
+
playground: z.unknown().optional(),
|
|
116
|
+
examples: z.array(z.unknown()).optional(),
|
|
117
|
+
})
|
|
118
|
+
.passthrough();
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* New-format schema for a stamped function doc (`type: 'function'`). Covers any
|
|
122
|
+
* function, including hooks: an inputs (`params`) + outputs (`returns`) surface.
|
|
12
123
|
*/
|
|
124
|
+
export const FunctionDocKindSchema = z
|
|
125
|
+
.object({
|
|
126
|
+
...BaseDocFields,
|
|
127
|
+
type: z.literal('function'),
|
|
128
|
+
params: z.array(ParamSchema),
|
|
129
|
+
returns: z.array(ReturnSchema),
|
|
130
|
+
})
|
|
131
|
+
.passthrough();
|
|
13
132
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
133
|
+
/**
|
|
134
|
+
* New-format schema for a stamped generic doc (`type: 'generic'`) — reference
|
|
135
|
+
* and topic docs. Just the shared base plus the discriminant.
|
|
136
|
+
*/
|
|
137
|
+
export const GenericDocKindSchema = z
|
|
138
|
+
.object({
|
|
139
|
+
...BaseDocFields,
|
|
140
|
+
type: z.literal('generic'),
|
|
141
|
+
})
|
|
142
|
+
.passthrough();
|
|
19
143
|
|
|
20
|
-
|
|
144
|
+
/**
|
|
145
|
+
* The stamped-doc contract: one of the three new per-kind schemas, discriminated
|
|
146
|
+
* by the injected `type`. Used when a loaded doc carries a `type` field.
|
|
147
|
+
*/
|
|
148
|
+
export const StampedDocSchema = z.discriminatedUnion('type', [
|
|
21
149
|
ComponentDocKindSchema,
|
|
22
150
|
FunctionDocKindSchema,
|
|
23
151
|
GenericDocKindSchema,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
152
|
+
]);
|
|
153
|
+
|
|
154
|
+
// ── Legacy loose format (unchanged, kept for back-compat) ─────────────
|
|
155
|
+
//
|
|
156
|
+
// The pre-factory docs are authored as a loose, untyped object with no `type`
|
|
157
|
+
// discriminant and validated by shape. Enumerated top-level fields observed
|
|
158
|
+
// across the existing loose docs:
|
|
159
|
+
// name, displayName, description, group, category, keywords,
|
|
160
|
+
// isHiddenFromOverview, hidden, hiddenComponents, usage, props, components,
|
|
161
|
+
// subComponentOf, playground, theming, examples, params, returns,
|
|
162
|
+
// relatedComponents, relatedHooks, relatedDocs, parent, importPath.
|
|
163
|
+
|
|
164
|
+
const LegacyBaseDocSchema = z.object({
|
|
165
|
+
name: z.string().min(1, 'name is required'),
|
|
166
|
+
displayName: z.string().optional(),
|
|
167
|
+
description: z.string().optional(),
|
|
168
|
+
group: z.string().optional(),
|
|
169
|
+
category: z.string().optional(),
|
|
170
|
+
keywords: z.array(z.string()).optional(),
|
|
171
|
+
isHiddenFromOverview: z.boolean().optional(),
|
|
172
|
+
hidden: z.boolean().optional(),
|
|
173
|
+
hiddenComponents: z.array(z.string()).optional(),
|
|
174
|
+
usage: z.unknown().optional(),
|
|
175
|
+
playground: z.unknown().optional(),
|
|
176
|
+
theming: z.unknown().optional(),
|
|
177
|
+
examples: z.array(z.unknown()).optional(),
|
|
178
|
+
showcase: z.unknown().optional(),
|
|
179
|
+
// `parent` and legacy `subComponentOf` are synonyms; both accepted here so a
|
|
180
|
+
// parent-based doc that omits a stamped `type` still validates.
|
|
181
|
+
parent: z.string().optional(),
|
|
182
|
+
relatedDocs: z.array(z.string()).optional(),
|
|
183
|
+
relatedComponents: z.array(z.string()).optional(),
|
|
184
|
+
relatedHooks: z.array(z.string()).optional(),
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
/** Legacy: directory exporting a single primary component (props inline). */
|
|
188
|
+
const LegacySingleComponentDocSchema = LegacyBaseDocSchema.extend({
|
|
189
|
+
props: z.array(PropSchema),
|
|
190
|
+
}).passthrough();
|
|
191
|
+
|
|
192
|
+
/** Legacy: directory exporting multiple components (props on each entry). */
|
|
193
|
+
const LegacyMultiComponentDocSchema = LegacyBaseDocSchema.extend({
|
|
194
|
+
components: z.array(z.unknown()),
|
|
195
|
+
}).passthrough();
|
|
196
|
+
|
|
197
|
+
/** Legacy: a standalone hook doc — inputs (`params`) + outputs (`returns`). */
|
|
198
|
+
const LegacyHookDocSchema = LegacyBaseDocSchema.extend({
|
|
199
|
+
params: z.array(ParamSchema),
|
|
200
|
+
returns: z.array(ReturnSchema),
|
|
201
|
+
}).passthrough();
|
|
202
|
+
|
|
203
|
+
/** Legacy: a sub-component doc parented via `subComponentOf`. */
|
|
204
|
+
const LegacySubComponentDocSchema = LegacyBaseDocSchema.extend({
|
|
205
|
+
subComponentOf: z.string().min(1, 'subComponentOf is required'),
|
|
206
|
+
description: z.string(),
|
|
207
|
+
props: z.array(PropSchema),
|
|
208
|
+
}).passthrough();
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* The permissive legacy union. `subComponentOf` is listed first so a doc that
|
|
212
|
+
* carries both `subComponentOf` and `props` is validated as a sub-component
|
|
213
|
+
* (the more specific shape); hook (`params`/`returns`) before multi/single.
|
|
214
|
+
*/
|
|
215
|
+
export const LegacyDocSchema = z.union([
|
|
216
|
+
LegacySubComponentDocSchema,
|
|
217
|
+
LegacyHookDocSchema,
|
|
218
|
+
LegacyMultiComponentDocSchema,
|
|
219
|
+
LegacySingleComponentDocSchema,
|
|
220
|
+
]);
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* The LOAD-boundary contract for a doc. Handles BOTH formats:
|
|
224
|
+
* - NEW: a stamped doc (`type: 'component' | 'function' | 'generic'`,
|
|
225
|
+
* produced by the factories below) is validated against the matching
|
|
226
|
+
* per-kind schema in {@link StampedDocSchema}.
|
|
227
|
+
* - OLD: a loose, unstamped doc is validated against the permissive
|
|
228
|
+
* {@link LegacyDocSchema} union (the three legacy shapes + standalone hook).
|
|
229
|
+
*
|
|
230
|
+
* Discovery validates the SHAPE, not "was it made by the factory". This PR does
|
|
231
|
+
* NOT migrate real docs; a later PR does. Both formats normalize into the same
|
|
232
|
+
* internal shape consumers already expect.
|
|
233
|
+
*/
|
|
234
|
+
export const ComponentDocSchema = z.union([StampedDocSchema, LegacyDocSchema]);
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Author a component doc. Stamp-only: returns the def with `type: 'component'`
|
|
238
|
+
* injected. Validation happens at the load boundary.
|
|
239
|
+
*
|
|
240
|
+
* @template {import('./types/doc').AstryxComponentDocInput} T
|
|
241
|
+
* @param {T} def
|
|
242
|
+
* @returns {T & {type: 'component'}}
|
|
243
|
+
*/
|
|
244
|
+
export function createComponentDoc(def) {
|
|
245
|
+
return /** @type {T & {type: 'component'}} */ ({...def, type: 'component'});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Author a function doc (covers any function, including hooks). Stamp-only:
|
|
250
|
+
* returns the def with `type: 'function'` injected. Validation happens at the
|
|
251
|
+
* load boundary.
|
|
252
|
+
*
|
|
253
|
+
* @template {import('./types/doc').AstryxFunctionDocInput} T
|
|
254
|
+
* @param {T} def
|
|
255
|
+
* @returns {T & {type: 'function'}}
|
|
256
|
+
*/
|
|
257
|
+
export function createFunctionDoc(def) {
|
|
258
|
+
return /** @type {T & {type: 'function'}} */ ({...def, type: 'function'});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Author a generic reference/topic doc. Stamp-only: returns the def with
|
|
263
|
+
* `type: 'generic'` injected. Validation happens at the load boundary.
|
|
264
|
+
*
|
|
265
|
+
* @template {import('./types/doc').AstryxGenericDocInput} T
|
|
266
|
+
* @param {T} def
|
|
267
|
+
* @returns {T & {type: 'generic'}}
|
|
268
|
+
*/
|
|
269
|
+
export function createDoc(def) {
|
|
270
|
+
return /** @type {T & {type: 'generic'}} */ ({...def, type: 'generic'});
|
|
271
|
+
}
|
package/src/integration.mjs
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Type-preserving helper for an Astryx integration manifest.
|
|
5
|
+
*
|
|
6
|
+
* This is an intentionally tiny runtime identity function: it returns its
|
|
7
|
+
* argument unchanged. Its value is the exported TypeScript surface from
|
|
8
|
+
* `@astryxdesign/cli/integration`, so manifests get editor/type feedback
|
|
9
|
+
* without coupling to CLI internals. Validation is NOT performed here — it
|
|
10
|
+
* happens at the load boundary (see `loadModuleWithSchema` +
|
|
11
|
+
* `AstryxIntegrationSchema`).
|
|
12
|
+
*
|
|
13
|
+
* @template {import('./types/integration').AstryxIntegration} T
|
|
14
|
+
* @param {T} integration
|
|
15
|
+
* @returns {T}
|
|
7
16
|
*/
|
|
8
|
-
export
|
|
17
|
+
export function createIntegration(integration) {
|
|
18
|
+
return integration;
|
|
19
|
+
}
|
|
@@ -34,30 +34,15 @@ function getTargetDataAttributes(target) {
|
|
|
34
34
|
];
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
/**
|
|
38
|
-
* Escape a value for a markdown table cell.
|
|
39
|
-
*
|
|
40
|
-
* A prop type is a union, and a union is spelled with the same `|` that GFM
|
|
41
|
-
* uses to separate cells — backticks do not protect it. A row with more cells
|
|
42
|
-
* than the header has columns gets the excess *discarded*, so an unescaped
|
|
43
|
-
* `gap: 0 | 0.5 | ...` silently eats its own Default and Description columns.
|
|
44
|
-
* <!-- SYNC: packages/core/src/Markdown/parser.ts (splits on unescaped pipes) -->
|
|
45
|
-
*/
|
|
46
|
-
export function mdCell(value) {
|
|
47
|
-
return String(value ?? '').replace(/\|/g, '\\|');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
37
|
function formatPropsTable(props) {
|
|
51
38
|
if (!props || props.length === 0) return '';
|
|
52
39
|
const lines = [];
|
|
53
40
|
lines.push('| Prop | Type | Default | Description |');
|
|
54
41
|
lines.push('|------|------|---------|-------------|');
|
|
55
42
|
for (const p of props) {
|
|
56
|
-
const def = p.default ? `\`${
|
|
43
|
+
const def = p.default ? `\`${p.default}\`` : '—';
|
|
57
44
|
const req = p.required ? ' **(required)**' : '';
|
|
58
|
-
lines.push(
|
|
59
|
-
`| \`${mdCell(p.name)}\` | \`${mdCell(p.type)}\` | ${def} | ${mdCell(p.description)}${req} |`,
|
|
60
|
-
);
|
|
45
|
+
lines.push(`| \`${p.name}\` | \`${p.type}\` | ${def} | ${p.description}${req} |`);
|
|
61
46
|
}
|
|
62
47
|
return lines.join('\n');
|
|
63
48
|
}
|
|
@@ -192,7 +177,7 @@ export function formatFull(docs, options = {}) {
|
|
|
192
177
|
sections.push('|---------|----------|-------------|');
|
|
193
178
|
for (const el of docs.usage.anatomy) {
|
|
194
179
|
const req = el.required ? 'Yes' : 'No';
|
|
195
|
-
sections.push(`| ${
|
|
180
|
+
sections.push(`| ${el.name} | ${req} | ${el.description} |`);
|
|
196
181
|
}
|
|
197
182
|
sections.push('');
|
|
198
183
|
}
|
|
@@ -297,9 +282,7 @@ export function formatFull(docs, options = {}) {
|
|
|
297
282
|
varLines.push('| CSS Variable | Default | Description |');
|
|
298
283
|
varLines.push('|-------------|---------|-------------|');
|
|
299
284
|
for (const v of publicVars) {
|
|
300
|
-
varLines.push(
|
|
301
|
-
`| \`${mdCell(v.name)}\` | \`${mdCell(v.default)}\` | ${mdCell(v.description)} |`,
|
|
302
|
-
);
|
|
285
|
+
varLines.push(`| \`${v.name}\` | \`${v.default}\` | ${v.description} |`);
|
|
303
286
|
}
|
|
304
287
|
sections.push(varLines.join('\n') + '\n');
|
|
305
288
|
}
|
|
@@ -395,8 +378,8 @@ export function formatCompact(docs, componentName, importHint) {
|
|
|
395
378
|
propLines.push('| CSS Property | Sets |');
|
|
396
379
|
propLines.push('|-------------|------|');
|
|
397
380
|
for (const d of docs.theming.derived) {
|
|
398
|
-
const target = d.expand === 'container' ? 'container layout tokens' : (d.vars || []).map(v => `\`${
|
|
399
|
-
propLines.push(`| \`${
|
|
381
|
+
const target = d.expand === 'container' ? 'container layout tokens' : (d.vars || []).map(v => `\`${v}\``).join(', ');
|
|
382
|
+
propLines.push(`| \`${d.property}\` | ${target} |`);
|
|
400
383
|
}
|
|
401
384
|
sections.push(propLines.join('\n') + '\n');
|
|
402
385
|
}
|
|
@@ -412,19 +395,6 @@ export function formatCompact(docs, componentName, importHint) {
|
|
|
412
395
|
*
|
|
413
396
|
* For multi-component docs, extracts the entry matching componentName.
|
|
414
397
|
*/
|
|
415
|
-
/**
|
|
416
|
-
* Longest union `--detail brief` will spell out inside the one-line signature.
|
|
417
|
-
*
|
|
418
|
-
* The signature is a glance, not a reference: a six-member enum like
|
|
419
|
-
* `start|center|end|between|around|evenly` reads at a glance, an eleven-member
|
|
420
|
-
* spacing scale does not — and Stack carries four of them (gap, padding,
|
|
421
|
-
* paddingInline, paddingBlock), so it printed the same scale four times. Longer
|
|
422
|
-
* unions fall through to the terse prop list, exactly as they did when the type
|
|
423
|
-
* was still the bare name `SpacingStep`. The full values are always one
|
|
424
|
-
* `astryx component <Name>` away.
|
|
425
|
-
*/
|
|
426
|
-
const SIGNATURE_UNION_MAX_MEMBERS = 8;
|
|
427
|
-
|
|
428
398
|
export function formatBrief(docs, componentName, importHint, options = {}) {
|
|
429
399
|
const displayName = componentName.startsWith('XDS')
|
|
430
400
|
? componentName.slice(3)
|
|
@@ -451,15 +421,13 @@ export function formatBrief(docs, componentName, importHint, options = {}) {
|
|
|
451
421
|
const otherProps = [];
|
|
452
422
|
|
|
453
423
|
for (const prop of props) {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
if (values && values.length <= SIGNATURE_UNION_MAX_MEMBERS) {
|
|
462
|
-
signatureProps.push(`${prop.name}: ${values.join('|')}`);
|
|
424
|
+
if (prop.type.includes('|') && !prop.type.includes('ReactNode')) {
|
|
425
|
+
const values = prop.type
|
|
426
|
+
.replace(/['"]/g, '')
|
|
427
|
+
.split('|')
|
|
428
|
+
.map(v => v.trim())
|
|
429
|
+
.join('|');
|
|
430
|
+
signatureProps.push(`${prop.name}: ${values}`);
|
|
463
431
|
} else if (prop.required) {
|
|
464
432
|
otherProps.unshift(`${prop.name}: ${prop.type.split('|')[0].trim()}`);
|
|
465
433
|
} else {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
3
|
import {describe, it, expect} from 'vitest';
|
|
4
|
-
import {
|
|
4
|
+
import {formatFull} from './component-format.mjs';
|
|
5
5
|
|
|
6
6
|
describe('formatFull sub-component rendering', () => {
|
|
7
7
|
// Regression guard: sub-components are sometimes declared as a bare
|
|
@@ -93,97 +93,3 @@ describe('formatFull theming override keys', () => {
|
|
|
93
93
|
expect(out).not.toContain("'astryx-table-cell': {");
|
|
94
94
|
});
|
|
95
95
|
});
|
|
96
|
-
|
|
97
|
-
/** Pipes that actually separate cells — a `\|` is content, not a separator. */
|
|
98
|
-
function cellPipes(row) {
|
|
99
|
-
return (row.match(/(?<!\\)\|/g) || []).length;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
describe('markdown table cells escape their pipes', () => {
|
|
103
|
-
// A prop type is a union, and a union is spelled with `|` — the very
|
|
104
|
-
// character GFM uses to separate cells. Backticks do not protect it: a row
|
|
105
|
-
// with more cells than the header has columns gets its excess *discarded*,
|
|
106
|
-
// so an unescaped `gap: 0 | 0.5 | ...` silently eats its own Default and
|
|
107
|
-
// Description columns. See packages/core/src/Markdown/parser.ts, which
|
|
108
|
-
// splits on unescaped pipes and preserves `\|` as literal.
|
|
109
|
-
it('keeps a union-typed prop row at four cells', () => {
|
|
110
|
-
const docs = {
|
|
111
|
-
name: 'Grid',
|
|
112
|
-
description: 'A grid.',
|
|
113
|
-
props: [
|
|
114
|
-
{
|
|
115
|
-
name: 'gap',
|
|
116
|
-
type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
|
|
117
|
-
default: '2',
|
|
118
|
-
description: 'Spacing between all items.',
|
|
119
|
-
},
|
|
120
|
-
],
|
|
121
|
-
};
|
|
122
|
-
const row = formatFull(docs)
|
|
123
|
-
.split('\n')
|
|
124
|
-
.find(l => l.startsWith('| `gap`'));
|
|
125
|
-
|
|
126
|
-
// A 4-column row has exactly 5 separators. Unescaped, this row had 15.
|
|
127
|
-
expect(cellPipes(row)).toBe(5);
|
|
128
|
-
expect(row).toContain('\\|');
|
|
129
|
-
// The columns the unescaped pipes were swallowing.
|
|
130
|
-
expect(row).toContain('`2`');
|
|
131
|
-
expect(row).toContain('Spacing between all items.');
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it('escapes pipes in a description too', () => {
|
|
135
|
-
const docs = {
|
|
136
|
-
name: 'AppShell',
|
|
137
|
-
description: 'A shell.',
|
|
138
|
-
props: [
|
|
139
|
-
{
|
|
140
|
-
name: 'mobileNav',
|
|
141
|
-
type: 'ReactNode',
|
|
142
|
-
description: "Config. breakpoint is 'sm' | 'md' | 'lg' | 'none'.",
|
|
143
|
-
},
|
|
144
|
-
],
|
|
145
|
-
};
|
|
146
|
-
const row = formatFull(docs)
|
|
147
|
-
.split('\n')
|
|
148
|
-
.find(l => l.startsWith('| `mobileNav`'));
|
|
149
|
-
|
|
150
|
-
expect(cellPipes(row)).toBe(5);
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
describe('formatBrief signature stays terse', () => {
|
|
155
|
-
// `--detail brief` exists to be token-cheap. It hoists union-typed props into
|
|
156
|
-
// the signature because a short enum reads at a glance — but an 11-member
|
|
157
|
-
// spacing scale does not, and Stack carries four of them (gap, padding,
|
|
158
|
-
// paddingInline, paddingBlock), so the same scale got printed four times.
|
|
159
|
-
// Long unions stay in the terse prop list, exactly as they did when the type
|
|
160
|
-
// was still the bare name `SpacingStep`.
|
|
161
|
-
it('hoists a short enum but not a long scale', () => {
|
|
162
|
-
const docs = {
|
|
163
|
-
name: 'HStack',
|
|
164
|
-
description: 'A stack.',
|
|
165
|
-
props: [
|
|
166
|
-
{
|
|
167
|
-
name: 'gap',
|
|
168
|
-
type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
|
|
169
|
-
description: 'Gap.',
|
|
170
|
-
},
|
|
171
|
-
{name: 'wrap', type: "'nowrap' | 'wrap' | 'wrap-reverse'", description: 'Wrap.'},
|
|
172
|
-
{
|
|
173
|
-
name: 'hAlign',
|
|
174
|
-
type: "'start' | 'center' | 'end' | 'between' | 'around' | 'evenly'",
|
|
175
|
-
description: 'Align.',
|
|
176
|
-
},
|
|
177
|
-
],
|
|
178
|
-
};
|
|
179
|
-
const signature = formatBrief(docs, 'HStack', '').split('\n')[0];
|
|
180
|
-
|
|
181
|
-
// Short enums still earn their place in the signature.
|
|
182
|
-
expect(signature).toContain('wrap: nowrap|wrap|wrap-reverse');
|
|
183
|
-
expect(signature).toContain('hAlign: start|center|end|between|around|evenly');
|
|
184
|
-
// The long scale does not.
|
|
185
|
-
expect(signature).not.toContain('0|0.5|1|1.5|2|3|4|5|6|8|10');
|
|
186
|
-
// But the prop is still named, so it is not lost.
|
|
187
|
-
expect(formatBrief(docs, 'HStack', '')).toContain('gap');
|
|
188
|
-
});
|
|
189
|
-
});
|