@astryxdesign/cli 0.1.1-canary.4b92876 → 0.1.1-canary.7e591d0

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