@astryxdesign/cli 0.1.1-canary.7e591d0 → 0.1.1-canary.8ae87ca
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 +8 -13
- package/src/api/discover.mjs +26 -78
- package/src/api/template.mjs +43 -134
- package/src/commands/gap-report.mjs +9 -17
- package/src/commands/gap-report.test.mjs +16 -21
- package/src/commands/init.mjs +8 -34
- package/src/commands/swizzle.mjs +23 -51
- package/src/commands/upgrade.mjs +70 -1
- package/src/lib/config.mjs +7 -34
- package/src/lib/config.test.mjs +2 -51
- package/src/lib/package-scanner.mjs +7 -31
- package/src/utils/github.mjs +27 -12
- package/src/commands/init.next-steps.test.mjs +0 -46
- package/src/config.mjs +0 -31
- package/src/config.test.mjs +0 -24
- package/src/lib/config-schema.mjs +0 -119
- package/src/lib/integrations.mjs +0 -155
- package/src/lib/integrations.test.mjs +0 -154
- package/src/types/config.d.ts +0 -99
- package/templates/blocks/components/CommandPaletteEmpty/CommandPaletteEmptyShowcase.doc.mjs +0 -15
- package/templates/blocks/components/CommandPaletteEmpty/CommandPaletteEmptyShowcase.tsx +0 -26
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/cli",
|
|
3
|
-
"version": "0.1.1-canary.
|
|
3
|
+
"version": "0.1.1-canary.8ae87ca",
|
|
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",
|
|
@@ -38,10 +38,6 @@
|
|
|
38
38
|
"./api": {
|
|
39
39
|
"types": "./src/types/api.d.ts",
|
|
40
40
|
"import": "./src/api/index.mjs"
|
|
41
|
-
},
|
|
42
|
-
"./config": {
|
|
43
|
-
"types": "./src/types/config.d.ts",
|
|
44
|
-
"import": "./src/config.mjs"
|
|
45
41
|
}
|
|
46
42
|
},
|
|
47
43
|
"files": [
|
|
@@ -55,13 +51,12 @@
|
|
|
55
51
|
"@clack/prompts": "^1.5.1",
|
|
56
52
|
"commander": "^12.1.0",
|
|
57
53
|
"jiti": "^2.7.0",
|
|
58
|
-
"jscodeshift": "^17.3.0"
|
|
59
|
-
"zod": "^4.4.3"
|
|
54
|
+
"jscodeshift": "^17.3.0"
|
|
60
55
|
},
|
|
61
56
|
"peerDependencies": {
|
|
62
|
-
"@astryxdesign/core": "0.1.1-canary.
|
|
63
|
-
"@astryxdesign/lab": "0.1.1-canary.
|
|
64
|
-
"@astryxdesign/theme-neutral": "0.1.1-canary.
|
|
57
|
+
"@astryxdesign/core": "0.1.1-canary.8ae87ca",
|
|
58
|
+
"@astryxdesign/lab": "0.1.1-canary.8ae87ca",
|
|
59
|
+
"@astryxdesign/theme-neutral": "0.1.1-canary.8ae87ca"
|
|
65
60
|
},
|
|
66
61
|
"peerDependenciesMeta": {
|
|
67
62
|
"@astryxdesign/core": {
|
|
@@ -75,9 +70,9 @@
|
|
|
75
70
|
}
|
|
76
71
|
},
|
|
77
72
|
"devDependencies": {
|
|
78
|
-
"@astryxdesign/core": "0.1.1-canary.
|
|
79
|
-
"@astryxdesign/lab": "0.1.1-canary.
|
|
80
|
-
"@astryxdesign/theme-neutral": "0.1.1-canary.
|
|
73
|
+
"@astryxdesign/core": "0.1.1-canary.8ae87ca",
|
|
74
|
+
"@astryxdesign/lab": "0.1.1-canary.8ae87ca",
|
|
75
|
+
"@astryxdesign/theme-neutral": "0.1.1-canary.8ae87ca"
|
|
81
76
|
},
|
|
82
77
|
"scripts": {
|
|
83
78
|
"astryx": "node bin/astryx.mjs",
|
package/src/api/discover.mjs
CHANGED
|
@@ -5,28 +5,19 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {loadConfig} from '../lib/config.mjs';
|
|
8
|
-
import {
|
|
9
|
-
scanAllPackages,
|
|
10
|
-
findComponentInPackages,
|
|
11
|
-
} from '../lib/package-scanner.mjs';
|
|
8
|
+
import {scanAllPackages, findComponentInPackages} from '../lib/package-scanner.mjs';
|
|
12
9
|
import {loadDocs} from '../lib/component-loader.mjs';
|
|
13
10
|
import {levenshteinDistance} from '../lib/string-utils.mjs';
|
|
14
11
|
import {AstryxError} from './error.mjs';
|
|
15
12
|
import {ERROR_CODES} from '../lib/error-codes.mjs';
|
|
16
13
|
|
|
17
14
|
function validateDocs(docs) {
|
|
18
|
-
if (!docs || typeof docs !== 'object')
|
|
19
|
-
|
|
20
|
-
if (typeof docs.
|
|
21
|
-
|
|
22
|
-
if (!docs.
|
|
23
|
-
|
|
24
|
-
if (docs.props && !Array.isArray(docs.props))
|
|
25
|
-
return 'docs.props must be an array';
|
|
26
|
-
if (docs.components && !Array.isArray(docs.components))
|
|
27
|
-
return 'docs.components must be an array';
|
|
28
|
-
if (docs.usage?.bestPractices && !Array.isArray(docs.usage.bestPractices))
|
|
29
|
-
return 'docs.usage.bestPractices must be an array';
|
|
15
|
+
if (!docs || typeof docs !== 'object') return 'docs export is missing or not an object';
|
|
16
|
+
if (typeof docs.name !== 'string' || !docs.name) return 'docs.name is missing or not a string';
|
|
17
|
+
if (!docs.usage || typeof docs.usage.description !== 'string') return 'docs.usage.description is missing or not a string';
|
|
18
|
+
if (docs.props && !Array.isArray(docs.props)) return 'docs.props must be an array';
|
|
19
|
+
if (docs.components && !Array.isArray(docs.components)) return 'docs.components must be an array';
|
|
20
|
+
if (docs.usage?.bestPractices && !Array.isArray(docs.usage.bestPractices)) return 'docs.usage.bestPractices must be an array';
|
|
30
21
|
return null;
|
|
31
22
|
}
|
|
32
23
|
|
|
@@ -41,7 +32,7 @@ function validateDocs(docs) {
|
|
|
41
32
|
export async function discover(query, options = {}) {
|
|
42
33
|
const {lang = null, zh = false} = options;
|
|
43
34
|
const config = await loadConfig();
|
|
44
|
-
const toEntry = pkg => ({
|
|
35
|
+
const toEntry = (pkg) => ({
|
|
45
36
|
name: pkg.name,
|
|
46
37
|
category: pkg.category,
|
|
47
38
|
components: pkg.components,
|
|
@@ -50,14 +41,11 @@ export async function discover(query, options = {}) {
|
|
|
50
41
|
displayName: pkg.displayName,
|
|
51
42
|
});
|
|
52
43
|
|
|
53
|
-
|
|
54
|
-
.map(integration => integration.package)
|
|
55
|
-
.filter(Boolean);
|
|
56
|
-
if (config.packages.length === 0 && explicitPackages.length === 0) {
|
|
44
|
+
if (config.packages.length === 0) {
|
|
57
45
|
return {type: 'discover.list', data: [], meta: {configured: false}};
|
|
58
46
|
}
|
|
59
47
|
|
|
60
|
-
const packages = scanAllPackages(config.packages
|
|
48
|
+
const packages = scanAllPackages(config.packages);
|
|
61
49
|
|
|
62
50
|
if (packages.length === 0) {
|
|
63
51
|
return {type: 'discover.list', data: [], meta: {configured: true}};
|
|
@@ -73,10 +61,7 @@ export async function discover(query, options = {}) {
|
|
|
73
61
|
if (slashIdx > 0) {
|
|
74
62
|
const pkgName = query.slice(0, slashIdx);
|
|
75
63
|
const compName = query.slice(slashIdx + 1);
|
|
76
|
-
return await resolveComponentDocs(packages, compName, pkgName, {
|
|
77
|
-
lang,
|
|
78
|
-
zh,
|
|
79
|
-
});
|
|
64
|
+
return await resolveComponentDocs(packages, compName, pkgName, {lang, zh});
|
|
80
65
|
}
|
|
81
66
|
|
|
82
67
|
const pkg = packages.find(p => p.name === query);
|
|
@@ -114,16 +99,7 @@ export async function discover(query, options = {}) {
|
|
|
114
99
|
}
|
|
115
100
|
|
|
116
101
|
if (substringMatches.length > 1) {
|
|
117
|
-
return {
|
|
118
|
-
type: 'discover.search',
|
|
119
|
-
data: {
|
|
120
|
-
query,
|
|
121
|
-
matches: substringMatches.map(m => ({
|
|
122
|
-
package: m.pkg.name,
|
|
123
|
-
component: m.comp,
|
|
124
|
-
})),
|
|
125
|
-
},
|
|
126
|
-
};
|
|
102
|
+
return {type: 'discover.search', data: {query, matches: substringMatches.map(m => ({package: m.pkg.name, component: m.comp}))}};
|
|
127
103
|
}
|
|
128
104
|
|
|
129
105
|
// Fuzzy fallback
|
|
@@ -134,10 +110,7 @@ export async function discover(query, options = {}) {
|
|
|
134
110
|
}
|
|
135
111
|
}
|
|
136
112
|
const fuzzyMatches = allComponents
|
|
137
|
-
.map(item => ({
|
|
138
|
-
...item,
|
|
139
|
-
distance: levenshteinDistance(lower, item.comp.toLowerCase()),
|
|
140
|
-
}))
|
|
113
|
+
.map(item => ({...item, distance: levenshteinDistance(lower, item.comp.toLowerCase())}))
|
|
141
114
|
.filter(m => m.distance <= 3)
|
|
142
115
|
.sort((a, b) => a.distance - b.distance)
|
|
143
116
|
.slice(0, 5);
|
|
@@ -145,46 +118,30 @@ export async function discover(query, options = {}) {
|
|
|
145
118
|
if (fuzzyMatches.length > 0) {
|
|
146
119
|
throw new AstryxError(
|
|
147
120
|
`"${query}" not found`,
|
|
148
|
-
fuzzyMatches.map(m => ({
|
|
149
|
-
name: m.pkg.name + '/' + m.comp,
|
|
150
|
-
reason: 'similar name',
|
|
151
|
-
})),
|
|
121
|
+
fuzzyMatches.map(m => ({name: m.pkg.name + '/' + m.comp, reason: 'similar name'})),
|
|
152
122
|
ERROR_CODES.ERR_NOT_FOUND,
|
|
153
123
|
);
|
|
154
124
|
}
|
|
155
125
|
|
|
156
|
-
throw new AstryxError(
|
|
157
|
-
`"${query}" not found in any package`,
|
|
158
|
-
undefined,
|
|
159
|
-
ERROR_CODES.ERR_NOT_FOUND,
|
|
160
|
-
);
|
|
126
|
+
throw new AstryxError(`"${query}" not found in any package`, undefined, ERROR_CODES.ERR_NOT_FOUND);
|
|
161
127
|
}
|
|
162
128
|
|
|
163
129
|
async function resolveComponentDocs(packages, compName, pkgName, {lang, zh}) {
|
|
164
130
|
const pkg = packages.find(p => p.name === pkgName);
|
|
165
|
-
if (!pkg)
|
|
166
|
-
throw new AstryxError(
|
|
167
|
-
`Package "${pkgName}" not found`,
|
|
168
|
-
undefined,
|
|
169
|
-
ERROR_CODES.ERR_UNKNOWN_PACKAGE,
|
|
170
|
-
);
|
|
131
|
+
if (!pkg) throw new AstryxError(`Package "${pkgName}" not found`, undefined, ERROR_CODES.ERR_UNKNOWN_PACKAGE);
|
|
171
132
|
|
|
172
133
|
const result = findComponentInPackages([pkg], compName);
|
|
173
134
|
if (!result) {
|
|
174
135
|
const lower = compName.toLowerCase();
|
|
175
136
|
const hits = pkg.components.filter(c => c.toLowerCase().includes(lower));
|
|
176
|
-
const suggestions =
|
|
177
|
-
hits
|
|
178
|
-
|
|
179
|
-
:
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
.filter(m => m.distance <= 3)
|
|
185
|
-
.sort((a, b) => a.distance - b.distance)
|
|
186
|
-
.slice(0, 5)
|
|
187
|
-
.map(m => m.name);
|
|
137
|
+
const suggestions = hits.length > 0
|
|
138
|
+
? hits
|
|
139
|
+
: pkg.components
|
|
140
|
+
.map(c => ({name: c, distance: levenshteinDistance(lower, c.toLowerCase())}))
|
|
141
|
+
.filter(m => m.distance <= 3)
|
|
142
|
+
.sort((a, b) => a.distance - b.distance)
|
|
143
|
+
.slice(0, 5)
|
|
144
|
+
.map(m => m.name);
|
|
188
145
|
throw new AstryxError(
|
|
189
146
|
`Component "${compName}" not found in ${pkgName}`,
|
|
190
147
|
suggestions.map(s => ({name: s, reason: 'similar name'})),
|
|
@@ -200,18 +157,9 @@ async function loadAndValidate(result, {lang, zh}) {
|
|
|
200
157
|
try {
|
|
201
158
|
docs = await loadDocs(result.docPath, {zh, lang});
|
|
202
159
|
} catch (e) {
|
|
203
|
-
throw new AstryxError(
|
|
204
|
-
`Failed to load docs for ${result.componentName}: ${e.message}`,
|
|
205
|
-
undefined,
|
|
206
|
-
ERROR_CODES.ERR_INVALID_DOC,
|
|
207
|
-
);
|
|
160
|
+
throw new AstryxError(`Failed to load docs for ${result.componentName}: ${e.message}`, undefined, ERROR_CODES.ERR_INVALID_DOC);
|
|
208
161
|
}
|
|
209
162
|
const err = validateDocs(docs);
|
|
210
|
-
if (err)
|
|
211
|
-
throw new AstryxError(
|
|
212
|
-
`Invalid docs for ${result.componentName}: ${err}`,
|
|
213
|
-
undefined,
|
|
214
|
-
ERROR_CODES.ERR_INVALID_DOC,
|
|
215
|
-
);
|
|
163
|
+
if (err) throw new AstryxError(`Invalid docs for ${result.componentName}: ${err}`, undefined, ERROR_CODES.ERR_INVALID_DOC);
|
|
216
164
|
return {type: 'discover.detail.doc', data: docs};
|
|
217
165
|
}
|
package/src/api/template.mjs
CHANGED
|
@@ -7,11 +7,7 @@
|
|
|
7
7
|
import * as fs from 'node:fs';
|
|
8
8
|
import * as path from 'node:path';
|
|
9
9
|
import {CLI_ROOT, discoverExternalPackages} from '../utils/paths.mjs';
|
|
10
|
-
import {
|
|
11
|
-
assertWithin,
|
|
12
|
-
isFilePathArg,
|
|
13
|
-
PathSafetyError,
|
|
14
|
-
} from '../utils/path-safety.mjs';
|
|
10
|
+
import {assertWithin, isFilePathArg, PathSafetyError} from '../utils/path-safety.mjs';
|
|
15
11
|
import {AstryxError} from './error.mjs';
|
|
16
12
|
import {ERROR_CODES} from '../lib/error-codes.mjs';
|
|
17
13
|
import {loadConfig} from '../lib/config.mjs';
|
|
@@ -27,7 +23,7 @@ const BLOCKS_DIR = path.join(TEMPLATES_DIR, 'blocks');
|
|
|
27
23
|
* or not. Mirrors apps/docsite/public/template-assets/placeholder.svg.
|
|
28
24
|
*/
|
|
29
25
|
const PLACEHOLDER_IMAGE =
|
|
30
|
-
|
|
26
|
+
"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20400%20300%22%20preserveAspectRatio%3D%22xMidYMid%20slice%22%3E%3Crect%20width%3D%22400%22%20height%3D%22300%22%20fill%3D%22%23f5f6f8%22%2F%3E%3Cg%20transform%3D%22translate%28200%20150%29%22%20fill%3D%22none%22%20stroke%3D%22%23c2cad6%22%20stroke-width%3D%225%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Crect%20x%3D%22-44%22%20y%3D%22-44%22%20width%3D%2288%22%20height%3D%2288%22%20rx%3D%2216%22%2F%3E%3Ccircle%20cx%3D%2218%22%20cy%3D%22-18%22%20r%3D%222.5%22%20fill%3D%22%23c2cad6%22%20stroke%3D%22none%22%2F%3E%3Cpath%20d%3D%22M-34%2030%20L-8%200%20L10%2018%20L20%208%20L34%2024%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E";
|
|
31
27
|
|
|
32
28
|
/**
|
|
33
29
|
* Demo-image sources to strip from scaffolded projects — Meta's lookaside CDN
|
|
@@ -64,12 +60,9 @@ export {discoverAll as discoverTemplates};
|
|
|
64
60
|
export function listTemplates() {
|
|
65
61
|
const all = [];
|
|
66
62
|
if (fs.existsSync(PAGES_DIR)) {
|
|
67
|
-
all.push(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
.filter(e => e.isDirectory())
|
|
71
|
-
.map(e => e.name),
|
|
72
|
-
);
|
|
63
|
+
all.push(...fs.readdirSync(PAGES_DIR, {withFileTypes: true})
|
|
64
|
+
.filter(e => e.isDirectory())
|
|
65
|
+
.map(e => e.name));
|
|
73
66
|
}
|
|
74
67
|
return all.sort();
|
|
75
68
|
}
|
|
@@ -90,8 +83,7 @@ function findDocFiles(dir, pattern) {
|
|
|
90
83
|
|
|
91
84
|
async function discoverPages() {
|
|
92
85
|
if (!fs.existsSync(PAGES_DIR)) return [];
|
|
93
|
-
const dirs = fs
|
|
94
|
-
.readdirSync(PAGES_DIR, {withFileTypes: true})
|
|
86
|
+
const dirs = fs.readdirSync(PAGES_DIR, {withFileTypes: true})
|
|
95
87
|
.filter(e => e.isDirectory());
|
|
96
88
|
|
|
97
89
|
const templates = [];
|
|
@@ -146,11 +138,7 @@ async function discoverBlocks() {
|
|
|
146
138
|
* @param {string} [cwd]
|
|
147
139
|
*/
|
|
148
140
|
async function discoverExternalBlocks(cwd = process.cwd()) {
|
|
149
|
-
const
|
|
150
|
-
const integrationPackages = (config.loadedIntegrations ?? [])
|
|
151
|
-
.map(integration => integration.package)
|
|
152
|
-
.filter(Boolean);
|
|
153
|
-
const externals = [...discoverExternalPackages(cwd), ...integrationPackages];
|
|
141
|
+
const externals = discoverExternalPackages(cwd);
|
|
154
142
|
const blocks = [];
|
|
155
143
|
|
|
156
144
|
for (const ext of externals) {
|
|
@@ -205,7 +193,9 @@ async function discoverAll() {
|
|
|
205
193
|
export async function findRelatedBlocks(componentName, cwd) {
|
|
206
194
|
const blocks = await discoverAllBlocks(cwd);
|
|
207
195
|
return blocks.filter(b =>
|
|
208
|
-
b.componentsUsed.some(c =>
|
|
196
|
+
b.componentsUsed.some(c =>
|
|
197
|
+
c.toLowerCase() === componentName.toLowerCase(),
|
|
198
|
+
),
|
|
209
199
|
);
|
|
210
200
|
}
|
|
211
201
|
|
|
@@ -228,7 +218,7 @@ export async function findShowcase(componentName, cwd, options) {
|
|
|
228
218
|
return true;
|
|
229
219
|
});
|
|
230
220
|
|
|
231
|
-
const toResult = b => ({
|
|
221
|
+
const toResult = (b) => ({
|
|
232
222
|
name: b.name,
|
|
233
223
|
aspectRatio: b.aspectRatio,
|
|
234
224
|
filePath: b.filePath,
|
|
@@ -252,14 +242,8 @@ export async function findShowcase(componentName, cwd, options) {
|
|
|
252
242
|
}
|
|
253
243
|
|
|
254
244
|
const UBIQUITOUS = new Set([
|
|
255
|
-
'Text',
|
|
256
|
-
'
|
|
257
|
-
'Button',
|
|
258
|
-
'HStack',
|
|
259
|
-
'VStack',
|
|
260
|
-
'Link',
|
|
261
|
-
'StackItem',
|
|
262
|
-
'Icon',
|
|
245
|
+
'Text', 'Heading', 'Button', 'HStack', 'VStack', 'Link',
|
|
246
|
+
'StackItem', 'Icon',
|
|
263
247
|
]);
|
|
264
248
|
|
|
265
249
|
export function extractComponents(pagePath) {
|
|
@@ -275,60 +259,26 @@ export function extractComponents(pagePath) {
|
|
|
275
259
|
while ((m = tagRegex.exec(src)) !== null) {
|
|
276
260
|
matches.push(m[2]);
|
|
277
261
|
}
|
|
278
|
-
return [
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
/(Item|Section|Header|Content|Footer|Panel|Heading|CollapseButton|Column|Sortable|Selection|Group|Source)$/,
|
|
286
|
-
'',
|
|
287
|
-
),
|
|
288
|
-
)
|
|
289
|
-
.filter(Boolean),
|
|
290
|
-
),
|
|
291
|
-
].sort();
|
|
262
|
+
return [...new Set(
|
|
263
|
+
matches
|
|
264
|
+
.filter(n => !['Theme', 'ThemeProvider'].includes(n))
|
|
265
|
+
.filter(n => !UBIQUITOUS.has(n))
|
|
266
|
+
.map(n => n.replace(/(Item|Section|Header|Content|Footer|Panel|Heading|CollapseButton|Column|Sortable|Selection|Group|Source)$/, ''))
|
|
267
|
+
.filter(Boolean),
|
|
268
|
+
)].sort();
|
|
292
269
|
}
|
|
293
270
|
|
|
294
271
|
const STRUCTURAL = new Set([
|
|
295
|
-
'AppShell',
|
|
296
|
-
'
|
|
297
|
-
'
|
|
298
|
-
'
|
|
299
|
-
'LayoutPanel',
|
|
300
|
-
'LayoutFooter',
|
|
301
|
-
'Card',
|
|
302
|
-
'Section',
|
|
303
|
-
'Grid',
|
|
304
|
-
'GridSpan',
|
|
305
|
-
'List',
|
|
306
|
-
'Table',
|
|
307
|
-
'TabList',
|
|
308
|
-
'Toolbar',
|
|
309
|
-
'SideNav',
|
|
310
|
-
'TopNav',
|
|
311
|
-
'Dialog',
|
|
312
|
-
'FormLayout',
|
|
313
|
-
'Center',
|
|
272
|
+
'AppShell', 'Layout', 'LayoutHeader', 'LayoutContent', 'LayoutPanel',
|
|
273
|
+
'LayoutFooter', 'Card', 'Section', 'Grid', 'GridSpan', 'List',
|
|
274
|
+
'Table', 'TabList', 'Toolbar', 'SideNav', 'TopNav', 'Dialog',
|
|
275
|
+
'FormLayout', 'Center',
|
|
314
276
|
]);
|
|
315
277
|
|
|
316
278
|
const SPATIAL_PROPS = [
|
|
317
|
-
'padding',
|
|
318
|
-
'
|
|
319
|
-
'
|
|
320
|
-
'rowGap',
|
|
321
|
-
'columnGap',
|
|
322
|
-
'columns',
|
|
323
|
-
'minChildWidth',
|
|
324
|
-
'hasDivider',
|
|
325
|
-
'defaultHasDividers',
|
|
326
|
-
'variant',
|
|
327
|
-
'density',
|
|
328
|
-
'role',
|
|
329
|
-
'height',
|
|
330
|
-
'width',
|
|
331
|
-
'maxWidth',
|
|
279
|
+
'padding', 'contentPadding', 'gap', 'rowGap', 'columnGap',
|
|
280
|
+
'columns', 'minChildWidth', 'hasDivider', 'defaultHasDividers',
|
|
281
|
+
'variant', 'density', 'role', 'height', 'width', 'maxWidth',
|
|
332
282
|
];
|
|
333
283
|
|
|
334
284
|
/**
|
|
@@ -396,18 +346,11 @@ function extractSkeleton(source) {
|
|
|
396
346
|
for (let i = 0; i < lines.length; i++) {
|
|
397
347
|
const t = lines[i].trim();
|
|
398
348
|
|
|
399
|
-
if (t.match(/^export\s+default\s+function/)) {
|
|
400
|
-
|
|
401
|
-
continue;
|
|
402
|
-
}
|
|
403
|
-
if (inDefaultExport && t.match(/^return\s*\(/)) {
|
|
404
|
-
capturing = true;
|
|
405
|
-
continue;
|
|
406
|
-
}
|
|
349
|
+
if (t.match(/^export\s+default\s+function/)) { inDefaultExport = true; continue; }
|
|
350
|
+
if (inDefaultExport && t.match(/^return\s*\(/)) { capturing = true; continue; }
|
|
407
351
|
if (!capturing) continue;
|
|
408
352
|
if (out.length >= MAX_LINES) {
|
|
409
|
-
if (!out[out.length - 1]?.includes('...'))
|
|
410
|
-
out.push(' '.repeat(depth) + '...');
|
|
353
|
+
if (!out[out.length - 1]?.includes('...')) out.push(' '.repeat(depth) + '...');
|
|
411
354
|
continue;
|
|
412
355
|
}
|
|
413
356
|
|
|
@@ -429,9 +372,7 @@ function extractSkeleton(source) {
|
|
|
429
372
|
const hasSpatialProps = props.length > 0;
|
|
430
373
|
const propStr = hasSpatialProps ? ' ' + props.join(' ') : '';
|
|
431
374
|
const isVStack = comp === 'VStack' || comp === 'HStack';
|
|
432
|
-
const isSelfClosing = tagText.match(
|
|
433
|
-
new RegExp('<' + tagName + '[^>]*/>', 's'),
|
|
434
|
-
);
|
|
375
|
+
const isSelfClosing = tagText.match(new RegExp('<' + tagName + '[^>]*/>', 's'));
|
|
435
376
|
|
|
436
377
|
if (isVStack && !hasSpatialProps) continue;
|
|
437
378
|
|
|
@@ -458,18 +399,13 @@ function extractSkeleton(source) {
|
|
|
458
399
|
continue;
|
|
459
400
|
}
|
|
460
401
|
|
|
461
|
-
const slotMatch = t.match(
|
|
462
|
-
/^(header|content|footer|start|end|sideNav|topNav)\s*=\s*\{/,
|
|
463
|
-
);
|
|
402
|
+
const slotMatch = t.match(/^(header|content|footer|start|end|sideNav|topNav)\s*=\s*\{/);
|
|
464
403
|
if (slotMatch) {
|
|
465
404
|
out.push(' '.repeat(depth) + `/* ${slotMatch[1]}: */`);
|
|
466
405
|
continue;
|
|
467
406
|
}
|
|
468
407
|
|
|
469
|
-
if (
|
|
470
|
-
t.startsWith('<div') &&
|
|
471
|
-
(t.includes('padding') || t.includes('maxWidth') || t.includes('gap:'))
|
|
472
|
-
) {
|
|
408
|
+
if (t.startsWith('<div') && (t.includes('padding') || t.includes('maxWidth') || t.includes('gap:'))) {
|
|
473
409
|
const styleProps = [];
|
|
474
410
|
const divText = lines.slice(i, Math.min(i + 5, lines.length)).join(' ');
|
|
475
411
|
const pp = divText.match(/padding[^:]*:\s*['"]?([^'"},)]+)/);
|
|
@@ -509,7 +445,7 @@ export async function getTemplateById(id, options = {}) {
|
|
|
509
445
|
'Add a template.get function to astryx.config.mjs:\n\n' +
|
|
510
446
|
' export default {\n' +
|
|
511
447
|
' template: {\n' +
|
|
512
|
-
|
|
448
|
+
" get: async (id) => { /* return template source string */ },\n" +
|
|
513
449
|
' },\n' +
|
|
514
450
|
' };',
|
|
515
451
|
undefined,
|
|
@@ -522,11 +458,7 @@ export async function getTemplateById(id, options = {}) {
|
|
|
522
458
|
source = await getter(id);
|
|
523
459
|
} catch (err) {
|
|
524
460
|
const detail = err instanceof Error ? err.message : String(err);
|
|
525
|
-
throw new AstryxError(
|
|
526
|
-
`template.get("${id}") threw an error: ${detail}`,
|
|
527
|
-
undefined,
|
|
528
|
-
ERROR_CODES.ERR_TEMPLATE_GET,
|
|
529
|
-
);
|
|
461
|
+
throw new AstryxError(`template.get("${id}") threw an error: ${detail}`, undefined, ERROR_CODES.ERR_TEMPLATE_GET);
|
|
530
462
|
}
|
|
531
463
|
|
|
532
464
|
if (source == null) {
|
|
@@ -568,14 +500,7 @@ export async function getTemplateById(id, options = {}) {
|
|
|
568
500
|
* @returns {Promise<{type: string, data: unknown}>}
|
|
569
501
|
*/
|
|
570
502
|
export async function template(name, options = {}) {
|
|
571
|
-
const {
|
|
572
|
-
list = false,
|
|
573
|
-
skeleton = false,
|
|
574
|
-
show = false,
|
|
575
|
-
targetPath,
|
|
576
|
-
type,
|
|
577
|
-
cwd = process.cwd(),
|
|
578
|
-
} = options;
|
|
503
|
+
const {list = false, skeleton = false, show = false, targetPath, type, cwd = process.cwd()} = options;
|
|
579
504
|
const templates = await discoverAll();
|
|
580
505
|
|
|
581
506
|
if (list || (!name && !skeleton)) {
|
|
@@ -612,11 +537,7 @@ export async function template(name, options = {}) {
|
|
|
612
537
|
);
|
|
613
538
|
}
|
|
614
539
|
if (!fs.existsSync(match.filePath)) {
|
|
615
|
-
throw new AstryxError(
|
|
616
|
-
`No source file found for template "${name}"`,
|
|
617
|
-
undefined,
|
|
618
|
-
ERROR_CODES.ERR_NO_SOURCE,
|
|
619
|
-
);
|
|
540
|
+
throw new AstryxError(`No source file found for template "${name}"`, undefined, ERROR_CODES.ERR_NO_SOURCE);
|
|
620
541
|
}
|
|
621
542
|
const src = fs.readFileSync(match.filePath, 'utf-8');
|
|
622
543
|
return {
|
|
@@ -631,11 +552,7 @@ export async function template(name, options = {}) {
|
|
|
631
552
|
}
|
|
632
553
|
|
|
633
554
|
if (!fs.existsSync(match.filePath)) {
|
|
634
|
-
throw new AstryxError(
|
|
635
|
-
`No source file found for template "${name}"`,
|
|
636
|
-
undefined,
|
|
637
|
-
ERROR_CODES.ERR_NO_SOURCE,
|
|
638
|
-
);
|
|
555
|
+
throw new AstryxError(`No source file found for template "${name}"`, undefined, ERROR_CODES.ERR_NO_SOURCE);
|
|
639
556
|
}
|
|
640
557
|
|
|
641
558
|
if (show || !targetPath) {
|
|
@@ -662,11 +579,7 @@ export async function template(name, options = {}) {
|
|
|
662
579
|
});
|
|
663
580
|
} catch (err) {
|
|
664
581
|
if (err instanceof PathSafetyError) {
|
|
665
|
-
throw new AstryxError(
|
|
666
|
-
err.message,
|
|
667
|
-
undefined,
|
|
668
|
-
ERROR_CODES.ERR_PATH_TRAVERSAL,
|
|
669
|
-
);
|
|
582
|
+
throw new AstryxError(err.message, undefined, ERROR_CODES.ERR_PATH_TRAVERSAL);
|
|
670
583
|
}
|
|
671
584
|
throw err;
|
|
672
585
|
}
|
|
@@ -683,8 +596,9 @@ export async function template(name, options = {}) {
|
|
|
683
596
|
outputFilePath = resolvedTarget;
|
|
684
597
|
} else {
|
|
685
598
|
outputDir = resolvedTarget;
|
|
686
|
-
outputFileName =
|
|
687
|
-
|
|
599
|
+
outputFileName = match.type === 'block'
|
|
600
|
+
? path.basename(match.filePath)
|
|
601
|
+
: 'page.tsx';
|
|
688
602
|
outputFilePath = path.join(outputDir, outputFileName);
|
|
689
603
|
}
|
|
690
604
|
|
|
@@ -699,11 +613,6 @@ export async function template(name, options = {}) {
|
|
|
699
613
|
const relOutput = path.relative(cwd, outputDir) || '.';
|
|
700
614
|
return {
|
|
701
615
|
type: 'template.copy',
|
|
702
|
-
data: {
|
|
703
|
-
template: name,
|
|
704
|
-
outputDir: relOutput,
|
|
705
|
-
fileName: outputFileName,
|
|
706
|
-
filesCopied: 1,
|
|
707
|
-
},
|
|
616
|
+
data: {template: name, outputDir: relOutput, fileName: outputFileName, filesCopied: 1},
|
|
708
617
|
};
|
|
709
618
|
}
|
|
@@ -132,7 +132,7 @@ export function registerGapReport(program) {
|
|
|
132
132
|
.action(async () => {
|
|
133
133
|
p.intro('Gap report setup');
|
|
134
134
|
|
|
135
|
-
const config =
|
|
135
|
+
const config = loadGapReportConfig();
|
|
136
136
|
const currentMode = !config.enabled
|
|
137
137
|
? 'disabled'
|
|
138
138
|
: config.command
|
|
@@ -252,14 +252,9 @@ export function registerGapReport(program) {
|
|
|
252
252
|
return;
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
-
const config =
|
|
255
|
+
const config = loadGapReportConfig();
|
|
256
256
|
if (!config.enabled) {
|
|
257
|
-
if (json)
|
|
258
|
-
return jsonError(
|
|
259
|
-
'Gap reporting is disabled',
|
|
260
|
-
undefined,
|
|
261
|
-
ERROR_CODES.ERR_GAP_REPORT_FAILED,
|
|
262
|
-
);
|
|
257
|
+
if (json) return jsonError('Gap reporting is disabled', undefined, ERROR_CODES.ERR_GAP_REPORT_FAILED);
|
|
263
258
|
humanLog(
|
|
264
259
|
`Gap reporting is disabled (ASTRYX_GAP_REPORT=off or astryx.config.mjs).\n` +
|
|
265
260
|
`Run \`${getRunPrefix()} astryx gap-report setup\` to configure.`,
|
|
@@ -300,7 +295,7 @@ export function registerGapReport(program) {
|
|
|
300
295
|
return;
|
|
301
296
|
}
|
|
302
297
|
|
|
303
|
-
const preview =
|
|
298
|
+
const preview = buildGapReportPreview({
|
|
304
299
|
component: options.component,
|
|
305
300
|
category: options.category,
|
|
306
301
|
intention: options.reason,
|
|
@@ -331,7 +326,7 @@ export function registerGapReport(program) {
|
|
|
331
326
|
}
|
|
332
327
|
|
|
333
328
|
try {
|
|
334
|
-
const url =
|
|
329
|
+
const url = createGapReport({
|
|
335
330
|
component: options.component,
|
|
336
331
|
category: options.category,
|
|
337
332
|
intention: options.reason,
|
|
@@ -348,9 +343,7 @@ export function registerGapReport(program) {
|
|
|
348
343
|
humanLog('\nGap reporting is disabled via configuration.\n');
|
|
349
344
|
}
|
|
350
345
|
} catch (err) {
|
|
351
|
-
cliError(`Filing gap report failed: ${err.message}`, {
|
|
352
|
-
code: ERROR_CODES.ERR_GAP_REPORT_FAILED,
|
|
353
|
-
});
|
|
346
|
+
cliError(`Filing gap report failed: ${err.message}`, {code: ERROR_CODES.ERR_GAP_REPORT_FAILED});
|
|
354
347
|
return;
|
|
355
348
|
}
|
|
356
349
|
return;
|
|
@@ -393,8 +386,7 @@ export function registerGapReport(program) {
|
|
|
393
386
|
placeholder:
|
|
394
387
|
'e.g. "Need a compact variant for use in dense data tables"',
|
|
395
388
|
validate: val => {
|
|
396
|
-
if (!val.trim())
|
|
397
|
-
return 'Please describe what you were trying to do';
|
|
389
|
+
if (!val.trim()) return 'Please describe what you were trying to do';
|
|
398
390
|
},
|
|
399
391
|
}),
|
|
400
392
|
);
|
|
@@ -414,7 +406,7 @@ export function registerGapReport(program) {
|
|
|
414
406
|
source: 'interactive',
|
|
415
407
|
};
|
|
416
408
|
|
|
417
|
-
const preview =
|
|
409
|
+
const preview = buildGapReportPreview(previewArgs);
|
|
418
410
|
|
|
419
411
|
// Always show the user exactly what would be filed before sending.
|
|
420
412
|
p.note(
|
|
@@ -448,7 +440,7 @@ export function registerGapReport(program) {
|
|
|
448
440
|
s.start('Filing gap report');
|
|
449
441
|
|
|
450
442
|
try {
|
|
451
|
-
const url =
|
|
443
|
+
const url = createGapReport(previewArgs);
|
|
452
444
|
s.stop(url ? 'Gap report filed' : 'Gap reporting is disabled');
|
|
453
445
|
if (url) {
|
|
454
446
|
p.log.success(url);
|