magicpath-ai 1.3.0-beta.13 → 1.3.0-beta.15

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.
@@ -31,14 +31,14 @@ export function registerAddCommand(program) {
31
31
  program
32
32
  .command('add')
33
33
  .description('Add a MagicPath component to your project')
34
- .argument('<generatedName>', 'The generated name of the component (e.g., wispy-river-5234)')
34
+ .argument('<generatedNames...>', 'One or more generated names of components (e.g., wispy-river-5234 bold-moon-2031)')
35
35
  .option('-y, --yes', 'Skip confirmation prompts', false)
36
36
  .option('--overwrite', 'Overwrite existing files', false)
37
37
  .option('-p, --path <path>', `Custom path for components (default: ${DEFAULT_COMPONENTS_PATH})`)
38
38
  .option('-d, --debug', 'Enable debug logging', false)
39
39
  .option('--dry-run', 'Show what would happen without writing files', false)
40
40
  .option('--inspect', 'Show component source code without installing (implies --dry-run)', false)
41
- .action(async (generatedName, options) => {
41
+ .action(async (generatedNames, options) => {
42
42
  try {
43
43
  let addOptions;
44
44
  try {
@@ -58,7 +58,12 @@ export function registerAddCommand(program) {
58
58
  const json = isJsonMode();
59
59
  if (json)
60
60
  addOptions.yes = true;
61
- logger.debug({ generatedName, options: addOptions });
61
+ const isBatch = generatedNames.length > 1;
62
+ // For batch mode, force --yes to avoid repetitive prompts
63
+ if (isBatch) {
64
+ addOptions.yes = true;
65
+ }
66
+ logger.debug({ generatedNames, options: addOptions });
62
67
  const cwd = process.cwd();
63
68
  // Check if we're in a valid project directory
64
69
  const packageJsonPath = path.join(cwd, 'package.json');
@@ -68,271 +73,381 @@ export function registerAddCommand(program) {
68
73
  suggestion: 'Run this command from the root of your project where package.json is located.',
69
74
  });
70
75
  }
71
- // Step 1: Fetch component from registry
72
- const fetchSpinner = json
73
- ? null
74
- : brandSpinner(`Fetching component "${generatedName}"...`).start();
75
- let componentData;
76
- try {
77
- componentData = await fetchComponent(generatedName);
78
- fetchSpinner?.succeed(`Found component: ${componentData.name}`);
76
+ // Batch results collector
77
+ const batchResults = [];
78
+ if (isBatch && !json) {
79
+ console.log(`\n Adding ${generatedNames.length} components...\n`);
79
80
  }
80
- catch (err) {
81
- if (err instanceof AuthRequiredError) {
82
- fetchSpinner?.fail('Not logged in');
83
- if (json) {
84
- throw new MagicPathError('Not authenticated. Set MAGICPATH_TOKEN or run `magicpath-ai login`.', {
85
- code: 'NOT_AUTHENTICATED',
86
- suggestion: 'Run `magicpath-ai login` or set the MAGICPATH_TOKEN environment variable.',
87
- });
81
+ for (const generatedName of generatedNames) {
82
+ try {
83
+ // Step 1: Fetch component from registry
84
+ const fetchSpinner = json
85
+ ? null
86
+ : brandSpinner(`Fetching component "${generatedName}"...`).start();
87
+ let componentData;
88
+ try {
89
+ componentData = await fetchComponent(generatedName);
90
+ fetchSpinner?.succeed(`Found component: ${componentData.name}`);
91
+ }
92
+ catch (err) {
93
+ if (err instanceof AuthRequiredError) {
94
+ fetchSpinner?.fail('Not logged in');
95
+ if (json || isBatch) {
96
+ throw new MagicPathError('Not authenticated. Set MAGICPATH_TOKEN or run `magicpath-ai login`.', {
97
+ code: 'NOT_AUTHENTICATED',
98
+ suggestion: 'Run `magicpath-ai login` or set the MAGICPATH_TOKEN environment variable.',
99
+ });
100
+ }
101
+ const { shouldLogin } = await prompts({
102
+ type: 'confirm',
103
+ name: 'shouldLogin',
104
+ message: 'You need to be logged in to install components. Log in now?',
105
+ initial: true,
106
+ });
107
+ if (!shouldLogin) {
108
+ console.log('Installation cancelled.');
109
+ return;
110
+ }
111
+ const loggedIn = await runLoginFlow();
112
+ if (!loggedIn) {
113
+ console.log('Installation cancelled.');
114
+ return;
115
+ }
116
+ // Retry fetch after login
117
+ const retrySpinner = brandSpinner(`Fetching component "${generatedName}"...`).start();
118
+ try {
119
+ componentData = await fetchComponent(generatedName);
120
+ retrySpinner.succeed(`Found component: ${componentData.name}`);
121
+ }
122
+ catch (retryErr) {
123
+ retrySpinner.fail('Failed to fetch component');
124
+ if (retryErr instanceof MagicPathError) {
125
+ throw retryErr;
126
+ }
127
+ throw new MagicPathError('Something went wrong!');
128
+ }
129
+ }
130
+ else {
131
+ fetchSpinner?.fail('Failed to fetch component');
132
+ if (isBatch) {
133
+ const errMsg = err instanceof MagicPathError ? err.message : 'Something went wrong!';
134
+ batchResults.push({
135
+ component: generatedName,
136
+ generatedName,
137
+ filesWritten: [],
138
+ dependenciesInstalled: [],
139
+ dryRun: addOptions.dryRun,
140
+ error: errMsg,
141
+ });
142
+ if (!json)
143
+ console.log(` āš ļø Skipping "${generatedName}": ${errMsg}`);
144
+ continue;
145
+ }
146
+ if (err instanceof MagicPathError) {
147
+ throw err;
148
+ }
149
+ throw new MagicPathError('Something went wrong!');
150
+ }
88
151
  }
89
- const { shouldLogin } = await prompts({
90
- type: 'confirm',
91
- name: 'shouldLogin',
92
- message: 'You need to be logged in to install components. Log in now?',
93
- initial: true,
152
+ // Resolve paths - include component-specific subfolder
153
+ const basePath = resolveComponentsPath(addOptions.path, cwd);
154
+ const componentFolderName = sanitizeComponentFolderName(componentData.name);
155
+ const componentsPath = path.join(basePath, componentFolderName);
156
+ const utilsPath = resolveUtilsPath(cwd);
157
+ logger.debug({
158
+ basePath,
159
+ componentFolderName,
160
+ componentsPath,
161
+ utilsPath,
94
162
  });
95
- if (!shouldLogin) {
96
- console.log('Installation cancelled.');
97
- return;
163
+ // Step 2: Check for existing files
164
+ const existingFiles = checkExistingFiles(componentData.files, componentsPath);
165
+ if (existingFiles.length > 0 && !addOptions.overwrite) {
166
+ if (!json) {
167
+ console.log('\nThe following files already exist:');
168
+ existingFiles.forEach((file) => console.log(` - ${file}`));
169
+ }
170
+ if (!addOptions.yes) {
171
+ const { shouldOverwrite } = await prompts({
172
+ type: 'confirm',
173
+ name: 'shouldOverwrite',
174
+ message: 'Do you want to overwrite these files?',
175
+ initial: false,
176
+ });
177
+ if (!shouldOverwrite) {
178
+ console.log('Installation cancelled.');
179
+ return;
180
+ }
181
+ }
182
+ else {
183
+ if (isBatch) {
184
+ if (!json)
185
+ console.log(` āš ļø Skipping "${componentData.name}": files already exist. Use --overwrite.`);
186
+ batchResults.push({
187
+ component: componentData.name,
188
+ generatedName: componentData.generatedName,
189
+ filesWritten: [],
190
+ dependenciesInstalled: [],
191
+ dryRun: addOptions.dryRun,
192
+ error: 'Files already exist. Use --overwrite to replace.',
193
+ });
194
+ continue;
195
+ }
196
+ if (json) {
197
+ throw new MagicPathError(`Files already exist: ${existingFiles.join(', ')}. Use --overwrite to replace.`, {
198
+ code: 'FILES_EXIST',
199
+ suggestion: 'Pass `--overwrite` to replace existing files.',
200
+ });
201
+ }
202
+ console.log('Use --overwrite to replace existing files.');
203
+ return;
204
+ }
98
205
  }
99
- const loggedIn = await runLoginFlow();
100
- if (!loggedIn) {
101
- console.log('Installation cancelled.');
102
- return;
206
+ // Step 3: Confirm installation (unless --yes flag)
207
+ if (!addOptions.yes && existingFiles.length === 0) {
208
+ console.log(`\nThis will install:`);
209
+ console.log(` Component: ${componentData.name}`);
210
+ console.log(` Files: ${componentData.files.length} file(s) to ${path.relative(cwd, componentsPath) || '.'}`);
211
+ console.log(` Dependencies: ${componentData.dependencies.length} package(s)`);
212
+ const { shouldProceed } = await prompts({
213
+ type: 'confirm',
214
+ name: 'shouldProceed',
215
+ message: 'Proceed with installation?',
216
+ initial: true,
217
+ });
218
+ if (!shouldProceed) {
219
+ console.log('Installation cancelled.');
220
+ return;
221
+ }
103
222
  }
104
- // Retry fetch after login
105
- const retrySpinner = brandSpinner(`Fetching component "${generatedName}"...`).start();
106
- try {
107
- componentData = await fetchComponent(generatedName);
108
- retrySpinner.succeed(`Found component: ${componentData.name}`);
223
+ // Build result data for JSON output
224
+ const filesWritten = componentData.files.map((f) => path.relative(cwd, path.join(componentsPath, f.path)));
225
+ // Extract import info
226
+ const mainFile = componentData.files[0];
227
+ let importStatement;
228
+ let usageStr;
229
+ if (mainFile) {
230
+ const fileName = mainFile.name.replace(/\.tsx?$/, '');
231
+ const importPath = addOptions.path
232
+ ? `@/${addOptions.path}/${componentFolderName}/${fileName}`
233
+ : `@/components/magicpath/${componentFolderName}/${fileName}`;
234
+ const exportInfo = extractComponentExports(mainFile.content);
235
+ if (exportInfo) {
236
+ importStatement = generateImportStatement(exportInfo, importPath);
237
+ usageStr = generateUsageExample(exportInfo);
238
+ }
239
+ else {
240
+ importStatement = `import { ${fileName} } from '${importPath}';`;
241
+ }
109
242
  }
110
- catch (retryErr) {
111
- retrySpinner.fail('Failed to fetch component');
112
- if (retryErr instanceof MagicPathError) {
113
- throw retryErr;
243
+ // Dry run: return what would happen without writing
244
+ if (addOptions.dryRun) {
245
+ const fileContents = addOptions.inspect || json
246
+ ? componentData.files.map((f) => ({
247
+ path: f.path,
248
+ name: f.name,
249
+ content: f.content,
250
+ }))
251
+ : undefined;
252
+ if (!isBatch && json) {
253
+ jsonResult({
254
+ component: componentData.name,
255
+ generatedName: componentData.generatedName,
256
+ filesWritten,
257
+ files: fileContents,
258
+ dependenciesInstalled: componentData.dependencies,
259
+ importStatement,
260
+ usage: usageStr,
261
+ dryRun: true,
262
+ });
114
263
  }
115
- throw new MagicPathError('Something went wrong!');
264
+ batchResults.push({
265
+ component: componentData.name,
266
+ generatedName: componentData.generatedName,
267
+ filesWritten,
268
+ dependenciesInstalled: componentData.dependencies,
269
+ importStatement,
270
+ usage: usageStr,
271
+ dryRun: true,
272
+ });
273
+ if (!json) {
274
+ console.log('\n[Dry run] Would install:');
275
+ console.log(` Component: ${componentData.name}`);
276
+ console.log(` Files: ${filesWritten.join(', ')}`);
277
+ console.log(` Dependencies: ${componentData.dependencies.join(', ') || 'none'}`);
278
+ if (importStatement)
279
+ console.log(` Import: ${importStatement}`);
280
+ // --inspect: show file contents
281
+ if (addOptions.inspect) {
282
+ for (const f of componentData.files) {
283
+ console.log(`\n${'─'.repeat(60)}`);
284
+ console.log(` ${f.path}`);
285
+ console.log(`${'─'.repeat(60)}`);
286
+ console.log(f.content);
287
+ }
288
+ }
289
+ }
290
+ continue;
116
291
  }
117
- }
118
- else {
119
- fetchSpinner?.fail('Failed to fetch component');
120
- if (err instanceof MagicPathError) {
121
- throw err;
292
+ // Step 4: Write component files
293
+ const writeSpinner = json
294
+ ? null
295
+ : brandSpinner('Writing component files...').start();
296
+ try {
297
+ writeComponentFiles(componentData.files, componentsPath, true);
298
+ writeSpinner?.succeed(`Wrote ${componentData.files.length} file(s) to ${path.relative(cwd, componentsPath) || '.'}`);
122
299
  }
123
- throw new MagicPathError('Something went wrong!');
124
- }
125
- }
126
- // Resolve paths - include component-specific subfolder
127
- const basePath = resolveComponentsPath(addOptions.path, cwd);
128
- const componentFolderName = sanitizeComponentFolderName(componentData.name);
129
- const componentsPath = path.join(basePath, componentFolderName);
130
- const utilsPath = resolveUtilsPath(cwd);
131
- logger.debug({
132
- basePath,
133
- componentFolderName,
134
- componentsPath,
135
- utilsPath,
136
- });
137
- // Step 2: Check for existing files
138
- const existingFiles = checkExistingFiles(componentData.files, componentsPath);
139
- if (existingFiles.length > 0 && !addOptions.overwrite) {
140
- if (!json) {
141
- console.log('\nThe following files already exist:');
142
- existingFiles.forEach((file) => console.log(` - ${file}`));
143
- }
144
- if (!addOptions.yes) {
145
- const { shouldOverwrite } = await prompts({
146
- type: 'confirm',
147
- name: 'shouldOverwrite',
148
- message: 'Do you want to overwrite these files?',
149
- initial: false,
150
- });
151
- if (!shouldOverwrite) {
152
- console.log('Installation cancelled.');
153
- return;
300
+ catch (err) {
301
+ writeSpinner?.fail('Failed to write component files');
302
+ if (isBatch) {
303
+ batchResults.push({
304
+ component: componentData.name,
305
+ generatedName: componentData.generatedName,
306
+ filesWritten: [],
307
+ dependenciesInstalled: [],
308
+ dryRun: false,
309
+ error: 'Failed to write component files.',
310
+ });
311
+ continue;
312
+ }
313
+ throw new MagicPathError('Failed to write component files. Please check permissions and try again.');
154
314
  }
155
- }
156
- else {
157
- if (json) {
158
- throw new MagicPathError(`Files already exist: ${existingFiles.join(', ')}. Use --overwrite to replace.`, {
159
- code: 'FILES_EXIST',
160
- suggestion: 'Pass `--overwrite` to replace existing files.',
161
- });
315
+ // Step 5: Install utils if needed
316
+ const utilsExists = checkUtilsExists(utilsPath);
317
+ if (!utilsExists && componentData.utils.content) {
318
+ const utilsSpinner = json
319
+ ? null
320
+ : brandSpinner('Installing utilities...').start();
321
+ try {
322
+ writeUtilsFile(componentData.utils.content, utilsPath);
323
+ utilsSpinner?.succeed(`Wrote utils.ts to ${path.relative(cwd, utilsPath) || '.'}`);
324
+ }
325
+ catch (err) {
326
+ utilsSpinner?.fail('Failed to write utils file');
327
+ logger.debug({ err });
328
+ if (!json) {
329
+ console.log(' Warning: Could not write utils.ts. You may need to create it manually.');
330
+ }
331
+ }
162
332
  }
163
- console.log('Use --overwrite to replace existing files.');
164
- return;
165
- }
166
- }
167
- // Step 3: Confirm installation (unless --yes flag)
168
- if (!addOptions.yes && existingFiles.length === 0) {
169
- console.log(`\nThis will install:`);
170
- console.log(` Component: ${componentData.name}`);
171
- console.log(` Files: ${componentData.files.length} file(s) to ${path.relative(cwd, componentsPath) || '.'}`);
172
- console.log(` Dependencies: ${componentData.dependencies.length} package(s)`);
173
- const { shouldProceed } = await prompts({
174
- type: 'confirm',
175
- name: 'shouldProceed',
176
- message: 'Proceed with installation?',
177
- initial: true,
178
- });
179
- if (!shouldProceed) {
180
- console.log('Installation cancelled.');
181
- return;
182
- }
183
- }
184
- // Build result data for JSON output
185
- const filesWritten = componentData.files.map((f) => path.relative(cwd, path.join(componentsPath, f.path)));
186
- // Extract import info
187
- const mainFile = componentData.files[0];
188
- let importStatement;
189
- let usageStr;
190
- if (mainFile) {
191
- const fileName = mainFile.name.replace(/\.tsx?$/, '');
192
- const importPath = addOptions.path
193
- ? `@/${addOptions.path}/${componentFolderName}/${fileName}`
194
- : `@/components/magicpath/${componentFolderName}/${fileName}`;
195
- const exportInfo = extractComponentExports(mainFile.content);
196
- if (exportInfo) {
197
- importStatement = generateImportStatement(exportInfo, importPath);
198
- usageStr = generateUsageExample(exportInfo);
199
- }
200
- else {
201
- importStatement = `import { ${fileName} } from '${importPath}';`;
202
- }
203
- }
204
- // Dry run: return what would happen without writing
205
- if (addOptions.dryRun) {
206
- const fileContents = addOptions.inspect || json
207
- ? componentData.files.map((f) => ({
208
- path: f.path,
209
- name: f.name,
210
- content: f.content,
211
- }))
212
- : undefined;
213
- if (json) {
214
- jsonResult({
333
+ // Step 6: Install dependencies
334
+ const installedDeps = [];
335
+ if (componentData.dependencies.length > 0) {
336
+ try {
337
+ const { installed, skipped } = installPackages(componentData.dependencies, cwd);
338
+ installedDeps.push(...installed);
339
+ if (skipped.length > 0) {
340
+ logger.debug({
341
+ message: 'Skipped already installed packages',
342
+ skipped,
343
+ });
344
+ }
345
+ }
346
+ catch (err) {
347
+ if (err instanceof MagicPathError) {
348
+ if (!json)
349
+ console.log(`\nāš ļø ${err.message}`);
350
+ }
351
+ }
352
+ }
353
+ batchResults.push({
215
354
  component: componentData.name,
216
355
  generatedName: componentData.generatedName,
217
356
  filesWritten,
218
- files: fileContents,
219
- dependenciesInstalled: componentData.dependencies,
357
+ dependenciesInstalled: installedDeps,
220
358
  importStatement,
221
359
  usage: usageStr,
222
- dryRun: true,
360
+ dryRun: false,
223
361
  });
224
- }
225
- console.log('\n[Dry run] Would install:');
226
- console.log(` Component: ${componentData.name}`);
227
- console.log(` Files: ${filesWritten.join(', ')}`);
228
- console.log(` Dependencies: ${componentData.dependencies.join(', ') || 'none'}`);
229
- if (importStatement)
230
- console.log(` Import: ${importStatement}`);
231
- // --inspect: show file contents
232
- if (addOptions.inspect) {
233
- for (const f of componentData.files) {
234
- console.log(`\n${'─'.repeat(60)}`);
235
- console.log(` ${f.path}`);
236
- console.log(`${'─'.repeat(60)}`);
237
- console.log(f.content);
362
+ // Single-component JSON output (non-batch)
363
+ if (!isBatch && json) {
364
+ jsonResult({
365
+ component: componentData.name,
366
+ generatedName: componentData.generatedName,
367
+ filesWritten,
368
+ dependenciesInstalled: installedDeps,
369
+ importStatement,
370
+ usage: usageStr,
371
+ dryRun: false,
372
+ });
238
373
  }
239
- }
240
- return;
241
- }
242
- // Step 4: Write component files
243
- const writeSpinner = json
244
- ? null
245
- : brandSpinner('Writing component files...').start();
246
- try {
247
- writeComponentFiles(componentData.files, componentsPath, true);
248
- writeSpinner?.succeed(`Wrote ${componentData.files.length} file(s) to ${path.relative(cwd, componentsPath) || '.'}`);
249
- }
250
- catch (err) {
251
- writeSpinner?.fail('Failed to write component files');
252
- throw new MagicPathError('Failed to write component files. Please check permissions and try again.');
253
- }
254
- // Step 5: Install utils if needed
255
- const utilsExists = checkUtilsExists(utilsPath);
256
- if (!utilsExists && componentData.utils.content) {
257
- const utilsSpinner = json
258
- ? null
259
- : brandSpinner('Installing utilities...').start();
260
- try {
261
- writeUtilsFile(componentData.utils.content, utilsPath);
262
- utilsSpinner?.succeed(`Wrote utils.ts to ${path.relative(cwd, utilsPath) || '.'}`);
263
- }
264
- catch (err) {
265
- utilsSpinner?.fail('Failed to write utils file');
266
- logger.debug({ err });
374
+ // Step 7: Show success message and write usage file
267
375
  if (!json) {
268
- console.log(' Warning: Could not write utils.ts. You may need to create it manually.');
376
+ console.log(`\nāœ… Successfully added "${componentData.name}" to your project!`);
377
+ if (!isBatch) {
378
+ console.log(`\nUsage:`);
379
+ }
269
380
  }
270
- }
271
- }
272
- // Step 6: Install dependencies
273
- const installedDeps = [];
274
- if (componentData.dependencies.length > 0) {
275
- try {
276
- const { installed, skipped } = installPackages(componentData.dependencies, cwd);
277
- installedDeps.push(...installed);
278
- if (skipped.length > 0) {
279
- logger.debug({
280
- message: 'Skipped already installed packages',
281
- skipped,
282
- });
381
+ if (mainFile) {
382
+ const fileName = mainFile.name.replace(/\.tsx?$/, '');
383
+ const importPath = addOptions.path
384
+ ? `@/${addOptions.path}/${componentFolderName}/${fileName}`
385
+ : `@/components/magicpath/${componentFolderName}/${fileName}`;
386
+ const exportInfo = extractComponentExports(mainFile.content);
387
+ if (!isBatch && !json) {
388
+ if (exportInfo) {
389
+ console.log(` ${generateImportStatement(exportInfo, importPath)}`);
390
+ console.log('');
391
+ console.log(` ${generateUsageExample(exportInfo)}`);
392
+ if (exportInfo.requiredProps.length > 0) {
393
+ console.log('');
394
+ console.log(` Required props: ${exportInfo.requiredProps.join(', ')}`);
395
+ }
396
+ }
397
+ else {
398
+ console.log(` import { ${fileName} } from '${importPath}';`);
399
+ }
400
+ }
401
+ try {
402
+ writeUsageFile(componentsPath, {
403
+ componentName: componentData.name,
404
+ importPath,
405
+ exportInfo,
406
+ fileName,
407
+ });
408
+ if (!isBatch && !json) {
409
+ console.log(`\nšŸ“„ Usage documentation written to ${path.relative(cwd, path.join(componentsPath, 'usage.md'))}`);
410
+ }
411
+ }
412
+ catch (err) {
413
+ logger.debug({ err });
414
+ }
283
415
  }
284
416
  }
285
417
  catch (err) {
286
- if (err instanceof MagicPathError) {
418
+ // In batch mode, record error and continue
419
+ if (isBatch) {
420
+ const errMsg = err instanceof Error ? err.message : String(err);
421
+ batchResults.push({
422
+ component: generatedName,
423
+ generatedName,
424
+ filesWritten: [],
425
+ dependenciesInstalled: [],
426
+ dryRun: addOptions.dryRun,
427
+ error: errMsg,
428
+ });
287
429
  if (!json)
288
- console.log(`\nāš ļø ${err.message}`);
430
+ console.log(` āš ļø Failed "${generatedName}": ${errMsg}`);
431
+ continue;
289
432
  }
433
+ throw err;
290
434
  }
291
435
  }
292
- // JSON output
293
- if (json) {
294
- jsonResult({
295
- component: componentData.name,
296
- generatedName: componentData.generatedName,
297
- filesWritten,
298
- dependenciesInstalled: installedDeps,
299
- importStatement,
300
- usage: usageStr,
301
- dryRun: false,
302
- });
303
- }
304
- // Step 7: Show success message and write usage file
305
- console.log(`\nāœ… Successfully added "${componentData.name}" to your project!`);
306
- console.log(`\nUsage:`);
307
- if (mainFile) {
308
- const fileName = mainFile.name.replace(/\.tsx?$/, '');
309
- const importPath = addOptions.path
310
- ? `@/${addOptions.path}/${componentFolderName}/${fileName}`
311
- : `@/components/magicpath/${componentFolderName}/${fileName}`;
312
- const exportInfo = extractComponentExports(mainFile.content);
313
- if (exportInfo) {
314
- console.log(` ${generateImportStatement(exportInfo, importPath)}`);
315
- console.log('');
316
- console.log(` ${generateUsageExample(exportInfo)}`);
317
- if (exportInfo.requiredProps.length > 0) {
318
- console.log('');
319
- console.log(` Required props: ${exportInfo.requiredProps.join(', ')}`);
320
- }
321
- }
322
- else {
323
- console.log(` import { ${fileName} } from '${importPath}';`);
324
- }
325
- try {
326
- writeUsageFile(componentsPath, {
327
- componentName: componentData.name,
328
- importPath,
329
- exportInfo,
330
- fileName,
436
+ // Batch summary
437
+ if (isBatch) {
438
+ if (json) {
439
+ jsonResult({
440
+ results: batchResults,
441
+ total: batchResults.length,
442
+ succeeded: batchResults.filter((r) => !r.error).length,
443
+ failed: batchResults.filter((r) => r.error).length,
331
444
  });
332
- console.log(`\nšŸ“„ Usage documentation written to ${path.relative(cwd, path.join(componentsPath, 'usage.md'))}`);
333
445
  }
334
- catch (err) {
335
- logger.debug({ err });
446
+ const succeeded = batchResults.filter((r) => !r.error);
447
+ const failed = batchResults.filter((r) => r.error);
448
+ console.log(`\n ${succeeded.length} of ${batchResults.length} component(s) added successfully.`);
449
+ if (failed.length > 0) {
450
+ console.log(` ${failed.length} failed: ${failed.map((f) => f.generatedName).join(', ')}`);
336
451
  }
337
452
  }
338
453
  }