@kosdev-code/kos-ui-cli 0.1.0-dev.5053

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.
Files changed (47) hide show
  1. package/README.md +7 -0
  2. package/package.json +30 -0
  3. package/src/index.d.ts +2 -0
  4. package/src/index.d.ts.map +1 -0
  5. package/src/index.js +1 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/cli.mjs +803 -0
  8. package/src/lib/generators/cache/index.mjs +18 -0
  9. package/src/lib/generators/component/index.mjs +55 -0
  10. package/src/lib/generators/env/index.mjs +42 -0
  11. package/src/lib/generators/i18n/namespace.mjs +61 -0
  12. package/src/lib/generators/kab/index.mjs +82 -0
  13. package/src/lib/generators/metadata.json +341 -0
  14. package/src/lib/generators/model/add-future.mjs +96 -0
  15. package/src/lib/generators/model/companion.mjs +117 -0
  16. package/src/lib/generators/model/container.mjs +96 -0
  17. package/src/lib/generators/model/context.mjs +77 -0
  18. package/src/lib/generators/model/hook.mjs +77 -0
  19. package/src/lib/generators/model/model.mjs +79 -0
  20. package/src/lib/generators/plugin/index.mjs +195 -0
  21. package/src/lib/generators/project/app.mjs +39 -0
  22. package/src/lib/generators/project/content.mjs +41 -0
  23. package/src/lib/generators/project/i18n.mjs +38 -0
  24. package/src/lib/generators/project/plugin.mjs +38 -0
  25. package/src/lib/generators/project/splash.mjs +39 -0
  26. package/src/lib/generators/project/theme.mjs +38 -0
  27. package/src/lib/generators/serve/index.mjs +74 -0
  28. package/src/lib/generators/version/index.mjs +182 -0
  29. package/src/lib/generators/workspace/index.mjs +40 -0
  30. package/src/lib/generators/workspace/list-models.mjs +64 -0
  31. package/src/lib/generators/workspace/list-projects.mjs +167 -0
  32. package/src/lib/plopfile.mjs +67 -0
  33. package/src/lib/routing-plopfile.mjs +53 -0
  34. package/src/lib/scripts/generate-metadata.mjs +39 -0
  35. package/src/lib/utils/action-factory.mjs +9 -0
  36. package/src/lib/utils/cache.mjs +128 -0
  37. package/src/lib/utils/command-builder.mjs +94 -0
  38. package/src/lib/utils/exec.mjs +18 -0
  39. package/src/lib/utils/generator-loader.mjs +65 -0
  40. package/src/lib/utils/index.mjs +1 -0
  41. package/src/lib/utils/java-home.mjs +55 -0
  42. package/src/lib/utils/logger.mjs +0 -0
  43. package/src/lib/utils/nx-context.mjs +395 -0
  44. package/src/lib/utils/prompts.mjs +75 -0
  45. package/src/lib/utils/studio-home.mjs +12 -0
  46. package/src/lib/utils/utils.mjs +126 -0
  47. package/src/lib/utils/validators.mjs +10 -0
@@ -0,0 +1,803 @@
1
+ #!/usr/bin/env node
2
+ import chalk from "chalk";
3
+ import figlet from "figlet";
4
+ import minimist from "minimist";
5
+ import nodePlop from "node-plop";
6
+ import path, { dirname } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { clearCache } from "./utils/cache.mjs";
9
+ import { buildNonInteractiveCommand, isRunningInteractively } from "./utils/command-builder.mjs";
10
+ import { getGeneratorMetadata } from "./utils/generator-loader.mjs";
11
+
12
+ const originalArgs = process.argv.slice(2);
13
+ const parsedArgs = minimist(originalArgs);
14
+ const command = parsedArgs._[0] || "help";
15
+
16
+ const customFlags = ["--no-cache", "--refresh", "--quiet"];
17
+ const hasNoCache = originalArgs.includes("--no-cache");
18
+ const hasRefresh = originalArgs.includes("--refresh");
19
+ const hasQuiet = originalArgs.includes("--quiet");
20
+
21
+ if (hasNoCache || hasRefresh) {
22
+ process.env.DISABLE_CACHE = "true";
23
+ if (!hasQuiet) {
24
+ console.log("[kos-cli] Cache disabled or refresh forced. Clearing cache...");
25
+ }
26
+ clearCache();
27
+ }
28
+
29
+ if (hasQuiet) {
30
+ process.env.DISABLE_CLI_BANNER = "true";
31
+ process.env.KOS_CLI_QUIET = "true";
32
+ }
33
+
34
+ const __dirname = dirname(fileURLToPath(import.meta.url));
35
+ const configPath = path.join(__dirname, "plopfile.mjs");
36
+
37
+ const showBanner =
38
+ command !== "help" && process.env.DISABLE_CLI_BANNER !== "true";
39
+
40
+ async function launch() {
41
+ // Check for help modes before initializing plop to avoid expensive model parsing
42
+ const shouldShowHelp = parsedArgs.help;
43
+
44
+ if (!command || command === "help") {
45
+ // For general help, we still need plop to list generators
46
+ const plop = await nodePlop(configPath);
47
+
48
+ console.warn("--- KOS CLI Help ---");
49
+ console.log("\nAvailable Generators:");
50
+ plop.getGeneratorList().forEach((g) => {
51
+ console.log(`- ${g.name}: ${g.description}`);
52
+ });
53
+
54
+ console.log("\nUsage:");
55
+ console.log(" kosui <generator> [options]");
56
+ console.log(" kosui <generator> --help # Show generator-specific help");
57
+ console.log("\nGlobal Options:");
58
+ console.log(" --no-cache Disable cache");
59
+ console.log(" --refresh Clear cache and refresh");
60
+ console.log(" --quiet Suppress banner and debug output");
61
+ console.log(
62
+ " --interactive, -i Force interactive mode (ignore provided arguments)"
63
+ );
64
+ console.log(" --help Show this help");
65
+
66
+ return;
67
+ }
68
+
69
+ // For generator-specific help, check if we can show help without full plop initialization
70
+ if (shouldShowHelp) {
71
+ const earlyMeta = await getGeneratorMetadata(command);
72
+
73
+ if (earlyMeta && Object.keys(earlyMeta).length > 0) {
74
+ // Show help immediately for --help flag
75
+ console.log(`--- ${earlyMeta?.name || command} Help ---`);
76
+ console.log(earlyMeta.description || "No description available");
77
+
78
+ if (earlyMeta?.namedArguments) {
79
+ console.log("\nNamed Arguments:");
80
+ Object.entries(earlyMeta.namedArguments).forEach(
81
+ ([cliArg, promptName]) => {
82
+ console.log(` --${cliArg} Maps to prompt: ${promptName}`);
83
+ }
84
+ );
85
+ }
86
+
87
+ console.log("\nExamples:");
88
+
89
+ // Generate examples dynamically from the generator's named arguments
90
+ if (earlyMeta?.namedArguments) {
91
+ const args = Object.keys(earlyMeta.namedArguments);
92
+
93
+ // Generate example values based on argument names
94
+ const generateExampleValue = (argName) => {
95
+ switch (argName) {
96
+ case "name":
97
+ case "componentName":
98
+ return command.includes("plugin") ? "MyPlugin" : "MyComponent";
99
+ case "modelName":
100
+ return "my-model";
101
+ case "workspaceName":
102
+ return "my-workspace";
103
+ case "project":
104
+ case "componentProject":
105
+ case "modelProject":
106
+ return "my-ui-lib";
107
+ case "registrationProject":
108
+ return "my-lib";
109
+ case "companionParent":
110
+ return "parent-model";
111
+ case "extensionPoint":
112
+ return "utility";
113
+ case "group":
114
+ return "appearance";
115
+ case "locale":
116
+ return "en";
117
+ case "container":
118
+ case "singleton":
119
+ case "parentAware":
120
+ case "dataServices":
121
+ return true;
122
+ case "dryRun":
123
+ return true;
124
+ default:
125
+ return `my-${argName}`;
126
+ }
127
+ };
128
+
129
+ // Create basic example with most important arguments
130
+ const basicArgs = [];
131
+ const booleanArgs = [
132
+ "container",
133
+ "singleton",
134
+ "parentAware",
135
+ "dataServices",
136
+ "dryRun",
137
+ ];
138
+
139
+ // Pick the most relevant arguments for each generator type
140
+ const getRelevantArgs = (args, command) => {
141
+ const relevantArgs = [];
142
+
143
+ // Always prefer name-type arguments first
144
+ const nameArgs = [
145
+ "name",
146
+ "componentName",
147
+ "modelName",
148
+ "workspaceName",
149
+ ].filter((arg) => args.includes(arg));
150
+ if (nameArgs.length > 0) {
151
+ relevantArgs.push(nameArgs[0]); // Take the first name argument
152
+ }
153
+
154
+ // Then add project-type arguments
155
+ const projectArgs = [
156
+ "project",
157
+ "componentProject",
158
+ "modelProject",
159
+ "registrationProject",
160
+ ].filter((arg) => args.includes(arg));
161
+ if (projectArgs.length > 0) {
162
+ relevantArgs.push(projectArgs[0]); // Take the first project argument
163
+ }
164
+
165
+ // Add other specific arguments
166
+ const otherArgs = args.filter(
167
+ (arg) =>
168
+ !nameArgs.includes(arg) &&
169
+ !projectArgs.includes(arg) &&
170
+ !booleanArgs.includes(arg) &&
171
+ arg !== "interactive"
172
+ );
173
+
174
+ // Add up to 2 more relevant arguments
175
+ relevantArgs.push(...otherArgs.slice(0, 2));
176
+
177
+ return relevantArgs;
178
+ };
179
+
180
+ const relevantArgs = getRelevantArgs(args, command);
181
+
182
+ relevantArgs.forEach((argName) => {
183
+ const value = generateExampleValue(argName);
184
+ basicArgs.push(`--${argName} ${value}`);
185
+ });
186
+
187
+ // Create advanced example with boolean flags
188
+ const advancedBooleanArgs = [];
189
+ booleanArgs.forEach((argName) => {
190
+ if (args.includes(argName)) {
191
+ advancedBooleanArgs.push(`--${argName}`);
192
+ }
193
+ });
194
+
195
+ // Show basic example
196
+ if (basicArgs.length > 0) {
197
+ console.log(` kosui ${command} ${basicArgs.join(" ")}`);
198
+ }
199
+
200
+ // Show advanced example with boolean flags if any exist
201
+ if (basicArgs.length > 0 && advancedBooleanArgs.length > 0) {
202
+ console.log(
203
+ ` kosui ${command} ${basicArgs
204
+ .slice(0, 2)
205
+ .join(" ")} ${advancedBooleanArgs.join(" ")}`
206
+ );
207
+ }
208
+
209
+ // If no basic args, show a minimal example
210
+ if (basicArgs.length === 0) {
211
+ console.log(` kosui ${command} [options]`);
212
+ }
213
+ }
214
+
215
+ console.log(` kosui ${command} --interactive # Force interactive mode`);
216
+ console.log(
217
+ ` kosui ${command} -i # Force interactive mode (short form)`
218
+ );
219
+ return;
220
+ }
221
+ }
222
+
223
+ // Also check for no-arguments case (when only global flags like --no-cache are present)
224
+ const earlyMeta = await getGeneratorMetadata(command);
225
+ if (earlyMeta?.namedArguments) {
226
+ const namedArgumentsMap = earlyMeta.namedArguments;
227
+ const providedAnswers = {};
228
+ Object.entries(namedArgumentsMap).forEach(([cliArg, promptName]) => {
229
+ if (cliArg === "interactive") return;
230
+ if (parsedArgs[cliArg] !== undefined) {
231
+ let value = parsedArgs[cliArg];
232
+ if (value === "true" || value === "y" || value === "yes") {
233
+ value = true;
234
+ } else if (value === "false" || value === "n" || value === "no") {
235
+ value = false;
236
+ }
237
+ providedAnswers[promptName] = value;
238
+ }
239
+ });
240
+ const hasAnyAnswers = Object.keys(providedAnswers).length > 0;
241
+ const forceInteractive = parsedArgs.interactive || parsedArgs.i;
242
+
243
+ // Show help for no-arguments case
244
+ if (!hasAnyAnswers && !forceInteractive) {
245
+ console.log(`--- ${earlyMeta?.name || command} Help ---`);
246
+ console.log(earlyMeta.description || "No description available");
247
+
248
+ if (earlyMeta?.namedArguments) {
249
+ console.log("\nNamed Arguments:");
250
+ Object.entries(earlyMeta.namedArguments).forEach(
251
+ ([cliArg, promptName]) => {
252
+ console.log(` --${cliArg} Maps to prompt: ${promptName}`);
253
+ }
254
+ );
255
+ }
256
+
257
+ console.log("\nExamples:");
258
+
259
+ // Generate examples dynamically from the generator's named arguments
260
+ if (earlyMeta?.namedArguments) {
261
+ const args = Object.keys(earlyMeta.namedArguments);
262
+
263
+ // Generate example values based on argument names
264
+ const generateExampleValue = (argName) => {
265
+ switch (argName) {
266
+ case "name":
267
+ case "componentName":
268
+ return command.includes("plugin") ? "MyPlugin" : "MyComponent";
269
+ case "modelName":
270
+ return "my-model";
271
+ case "workspaceName":
272
+ return "my-workspace";
273
+ case "project":
274
+ case "componentProject":
275
+ case "modelProject":
276
+ return "my-ui-lib";
277
+ case "registrationProject":
278
+ return "my-lib";
279
+ case "companionParent":
280
+ return "parent-model";
281
+ case "extensionPoint":
282
+ return "utility";
283
+ case "group":
284
+ return "appearance";
285
+ case "locale":
286
+ return "en";
287
+ case "container":
288
+ case "singleton":
289
+ case "parentAware":
290
+ case "dataServices":
291
+ return true;
292
+ case "dryRun":
293
+ return true;
294
+ default:
295
+ return `my-${argName}`;
296
+ }
297
+ };
298
+
299
+ // Create basic example with most important arguments
300
+ const basicArgs = [];
301
+ const booleanArgs = [
302
+ "container",
303
+ "singleton",
304
+ "parentAware",
305
+ "dataServices",
306
+ "dryRun",
307
+ ];
308
+
309
+ // Pick the most relevant arguments for each generator type
310
+ const getRelevantArgs = (args, command) => {
311
+ const relevantArgs = [];
312
+
313
+ // Always prefer name-type arguments first
314
+ const nameArgs = [
315
+ "name",
316
+ "componentName",
317
+ "modelName",
318
+ "workspaceName",
319
+ ].filter((arg) => args.includes(arg));
320
+ if (nameArgs.length > 0) {
321
+ relevantArgs.push(nameArgs[0]); // Take the first name argument
322
+ }
323
+
324
+ // Then add project-type arguments
325
+ const projectArgs = [
326
+ "project",
327
+ "componentProject",
328
+ "modelProject",
329
+ "registrationProject",
330
+ ].filter((arg) => args.includes(arg));
331
+ if (projectArgs.length > 0) {
332
+ relevantArgs.push(projectArgs[0]); // Take the first project argument
333
+ }
334
+
335
+ // Add other specific arguments
336
+ const otherArgs = args.filter(
337
+ (arg) =>
338
+ !nameArgs.includes(arg) &&
339
+ !projectArgs.includes(arg) &&
340
+ !booleanArgs.includes(arg) &&
341
+ arg !== "interactive"
342
+ );
343
+
344
+ // Add up to 2 more relevant arguments
345
+ relevantArgs.push(...otherArgs.slice(0, 2));
346
+
347
+ return relevantArgs;
348
+ };
349
+
350
+ const relevantArgs = getRelevantArgs(args, command);
351
+
352
+ relevantArgs.forEach((argName) => {
353
+ const value = generateExampleValue(argName);
354
+ basicArgs.push(`--${argName} ${value}`);
355
+ });
356
+
357
+ // Create advanced example with boolean flags
358
+ const advancedBooleanArgs = [];
359
+ booleanArgs.forEach((argName) => {
360
+ if (args.includes(argName)) {
361
+ advancedBooleanArgs.push(`--${argName}`);
362
+ }
363
+ });
364
+
365
+ // Show basic example
366
+ if (basicArgs.length > 0) {
367
+ console.log(` kosui ${command} ${basicArgs.join(" ")}`);
368
+ }
369
+
370
+ // Show advanced example with boolean flags if any exist
371
+ if (basicArgs.length > 0 && advancedBooleanArgs.length > 0) {
372
+ console.log(
373
+ ` kosui ${command} ${basicArgs
374
+ .slice(0, 2)
375
+ .join(" ")} ${advancedBooleanArgs.join(" ")}`
376
+ );
377
+ }
378
+
379
+ // If no basic args, show a minimal example
380
+ if (basicArgs.length === 0) {
381
+ console.log(` kosui ${command} [options]`);
382
+ }
383
+ }
384
+
385
+ console.log(` kosui ${command} --interactive # Force interactive mode`);
386
+ console.log(
387
+ ` kosui ${command} -i # Force interactive mode (short form)`
388
+ );
389
+ return;
390
+ }
391
+ }
392
+
393
+ const plop = await nodePlop(configPath);
394
+
395
+ plop.setActionType("clearCache", (answers, config, plop) => {
396
+ clearCache();
397
+ return "[ok] CLI cache cleared successfully.";
398
+ });
399
+
400
+ const generator = plop.getGenerator(command);
401
+
402
+ if (!generator) {
403
+ console.error(`[kos-cli] Generator "${command}" not found.`);
404
+ process.exit(1);
405
+ }
406
+
407
+ const meta = await getGeneratorMetadata(command);
408
+
409
+ if (showBanner && !hasQuiet) {
410
+ console.log(`--- Running KOS Generator: ${meta?.name || command} ---`);
411
+ }
412
+
413
+ try {
414
+ // Extract positional arguments for prompt bypass
415
+ const positionalArgs = parsedArgs._.slice(1); // Remove command from args
416
+
417
+ // Get named arguments mapping from generator metadata
418
+ const namedArgumentsMap = meta?.namedArguments || {};
419
+
420
+ // Convert CLI args to answer format using generator's mapping
421
+ const providedAnswers = {};
422
+
423
+ // Ensure we have the metadata with named arguments
424
+ if (!namedArgumentsMap || Object.keys(namedArgumentsMap).length === 0) {
425
+ console.warn(
426
+ `[kos-cli] Warning: No named arguments mapping found for generator '${command}'`
427
+ );
428
+ }
429
+
430
+ Object.entries(namedArgumentsMap).forEach(([cliArg, promptName]) => {
431
+ // Skip 'interactive' - it's handled as a special CLI flag, not a prompt argument
432
+ if (cliArg === "interactive") {
433
+ return;
434
+ }
435
+
436
+ if (parsedArgs[cliArg] !== undefined) {
437
+ let value = parsedArgs[cliArg];
438
+
439
+ // Convert string boolean values to actual booleans
440
+ if (value === "true" || value === "y" || value === "yes") {
441
+ value = true;
442
+ } else if (value === "false" || value === "n" || value === "no") {
443
+ value = false;
444
+ }
445
+
446
+ providedAnswers[promptName] = value;
447
+ }
448
+ });
449
+
450
+ // Also handle positional arguments
451
+ const prompts = generator.prompts || [];
452
+ positionalArgs.forEach((arg, index) => {
453
+ if (prompts[index] && !prompts[index].when) {
454
+ // Only non-conditional prompts
455
+ const promptName = prompts[index].name;
456
+ if (providedAnswers[promptName] === undefined) {
457
+ let value = arg;
458
+
459
+ // Convert string boolean values to actual booleans
460
+ if (value === "true" || value === "y" || value === "yes") {
461
+ value = true;
462
+ } else if (value === "false" || value === "n" || value === "no") {
463
+ value = false;
464
+ }
465
+
466
+ providedAnswers[promptName] = value;
467
+ }
468
+ }
469
+ });
470
+
471
+ // Check if we have ANY provided answers
472
+ const hasAnyAnswers = Object.keys(providedAnswers).length > 0;
473
+
474
+ let answers;
475
+
476
+ // Check if user wants to force interactive mode
477
+ const forceInteractive = parsedArgs.interactive || parsedArgs.i;
478
+
479
+ // If no arguments provided and no interactive flag, show help
480
+ if (!hasAnyAnswers && !forceInteractive) {
481
+ console.log(`--- ${meta?.name || command} Help ---`);
482
+ console.log(generator.description || "No description available");
483
+
484
+ if (meta?.namedArguments) {
485
+ console.log("\nNamed Arguments:");
486
+ Object.entries(meta.namedArguments).forEach(([cliArg, promptName]) => {
487
+ console.log(` --${cliArg} Maps to prompt: ${promptName}`);
488
+ });
489
+ }
490
+
491
+ console.log("\nExamples:");
492
+
493
+ // Generate examples dynamically from the generator's named arguments
494
+ if (meta?.namedArguments) {
495
+ const args = Object.keys(meta.namedArguments);
496
+
497
+ // Generate example values based on argument names
498
+ const generateExampleValue = (argName) => {
499
+ switch (argName) {
500
+ case "name":
501
+ case "componentName":
502
+ return command.includes("plugin") ? "MyPlugin" : "MyComponent";
503
+ case "modelName":
504
+ return "my-model";
505
+ case "workspaceName":
506
+ return "my-workspace";
507
+ case "project":
508
+ case "componentProject":
509
+ case "modelProject":
510
+ return "my-ui-lib";
511
+ case "registrationProject":
512
+ return "my-lib";
513
+ case "companionParent":
514
+ return "parent-model";
515
+ case "extensionPoint":
516
+ return "utility";
517
+ case "group":
518
+ return "appearance";
519
+ case "locale":
520
+ return "en";
521
+ case "container":
522
+ case "singleton":
523
+ case "parentAware":
524
+ case "dataServices":
525
+ return true;
526
+ case "dryRun":
527
+ return true;
528
+ default:
529
+ return `my-${argName}`;
530
+ }
531
+ };
532
+
533
+ // Create basic example with most important arguments
534
+ const basicArgs = [];
535
+ const booleanArgs = [
536
+ "container",
537
+ "singleton",
538
+ "parentAware",
539
+ "dataServices",
540
+ "dryRun",
541
+ ];
542
+
543
+ // Pick the most relevant arguments for each generator type
544
+ const getRelevantArgs = (args, command) => {
545
+ const relevantArgs = [];
546
+
547
+ // Always prefer name-type arguments first
548
+ const nameArgs = [
549
+ "name",
550
+ "componentName",
551
+ "modelName",
552
+ "workspaceName",
553
+ ].filter((arg) => args.includes(arg));
554
+ if (nameArgs.length > 0) {
555
+ relevantArgs.push(nameArgs[0]); // Take the first name argument
556
+ }
557
+
558
+ // Then add project-type arguments
559
+ const projectArgs = [
560
+ "project",
561
+ "componentProject",
562
+ "modelProject",
563
+ "registrationProject",
564
+ ].filter((arg) => args.includes(arg));
565
+ if (projectArgs.length > 0) {
566
+ relevantArgs.push(projectArgs[0]); // Take the first project argument
567
+ }
568
+
569
+ // Add other specific arguments
570
+ const otherArgs = args.filter(
571
+ (arg) =>
572
+ !nameArgs.includes(arg) &&
573
+ !projectArgs.includes(arg) &&
574
+ !booleanArgs.includes(arg) &&
575
+ arg !== "interactive"
576
+ );
577
+
578
+ // Add up to 2 more relevant arguments
579
+ relevantArgs.push(...otherArgs.slice(0, 2));
580
+
581
+ return relevantArgs;
582
+ };
583
+
584
+ const relevantArgs = getRelevantArgs(args, command);
585
+
586
+ relevantArgs.forEach((argName) => {
587
+ const value = generateExampleValue(argName);
588
+ basicArgs.push(`--${argName} ${value}`);
589
+ });
590
+
591
+ // Create advanced example with boolean flags
592
+ const advancedBooleanArgs = [];
593
+ booleanArgs.forEach((argName) => {
594
+ if (args.includes(argName)) {
595
+ advancedBooleanArgs.push(`--${argName}`);
596
+ }
597
+ });
598
+
599
+ // Show basic example
600
+ if (basicArgs.length > 0) {
601
+ console.log(` kosui ${command} ${basicArgs.join(" ")}`);
602
+ }
603
+
604
+ // Show advanced example with boolean flags if any exist
605
+ if (basicArgs.length > 0 && advancedBooleanArgs.length > 0) {
606
+ console.log(
607
+ ` kosui ${command} ${basicArgs
608
+ .slice(0, 2)
609
+ .join(" ")} ${advancedBooleanArgs.join(" ")}`
610
+ );
611
+ }
612
+
613
+ // If no basic args, show a minimal example
614
+ if (basicArgs.length === 0) {
615
+ console.log(` kosui ${command} [options]`);
616
+ }
617
+ }
618
+
619
+ console.log(` kosui ${command} --interactive # Force interactive mode`);
620
+ console.log(
621
+ ` kosui ${command} -i # Force interactive mode (short form)`
622
+ );
623
+ return;
624
+ }
625
+
626
+ if (hasAnyAnswers && !forceInteractive) {
627
+ // Create bypass array for all prompts
628
+ const bypassArgs = [];
629
+
630
+ // Fill bypass array with provided values or defaults
631
+ for (const prompt of prompts) {
632
+ if (prompt.when) {
633
+ // Skip conditional prompts - they can't be bypassed
634
+ continue;
635
+ }
636
+
637
+ const providedValue = providedAnswers[prompt.name];
638
+ if (providedValue !== undefined) {
639
+ bypassArgs.push(providedValue);
640
+ } else {
641
+ // Check for workspace defaults for project-type prompts
642
+ let defaultValue = prompt.default;
643
+
644
+ // Use workspace defaults for project selection prompts
645
+ if (
646
+ prompt.name &&
647
+ (prompt.name.includes("Project") || prompt.name === "project")
648
+ ) {
649
+ const { getDefaultProjectForType } = await import(
650
+ "./utils/nx-context.mjs"
651
+ );
652
+
653
+ // Determine project type based on command
654
+ let projectType = null;
655
+ if (command === "model" || command.startsWith("model:")) {
656
+ projectType = "model";
657
+ } else if (command === "component") {
658
+ projectType = "model-component";
659
+ } else if (command === "i18n:namespace") {
660
+ projectType = "i18n";
661
+ } else if (command.startsWith("plugin")) {
662
+ projectType = "plugin";
663
+ }
664
+
665
+ if (projectType) {
666
+ const workspaceDefault = await getDefaultProjectForType(
667
+ projectType
668
+ );
669
+ if (workspaceDefault) {
670
+ defaultValue = workspaceDefault;
671
+ }
672
+ }
673
+ }
674
+
675
+ // For required project prompts without a default value, we can't bypass - need to show help
676
+ if (
677
+ defaultValue === undefined &&
678
+ (prompt.name === "modelProject" ||
679
+ prompt.name === "project" ||
680
+ prompt.name === "componentProject" ||
681
+ prompt.name === "registrationProject")
682
+ ) {
683
+ // Can't bypass required project prompts without a value - show help instead
684
+ console.log(
685
+ `\nError: Missing required argument '--project' (no default project configured)`
686
+ );
687
+ console.log(`\n--- ${meta?.name || command} Help ---`);
688
+ console.log(meta.description || "No description available");
689
+
690
+ if (meta?.namedArguments) {
691
+ console.log("\nNamed Arguments:");
692
+ Object.entries(meta.namedArguments).forEach(
693
+ ([cliArg, promptName]) => {
694
+ console.log(` --${cliArg} Maps to prompt: ${promptName}`);
695
+ }
696
+ );
697
+ }
698
+
699
+ console.log("\nTo fix this, you can:");
700
+ console.log(
701
+ "1. Specify a project: kosui " +
702
+ command +
703
+ " --name MyModel --project my-model-lib"
704
+ );
705
+ console.log(
706
+ "2. Configure a default in .kos.json at the workspace root:"
707
+ );
708
+ console.log(
709
+ ' { "type": "root", "generator": { "defaults": { "model": "my-default-project" } } }'
710
+ );
711
+ console.log(
712
+ "3. Run in interactive mode: kosui " + command + " --interactive"
713
+ );
714
+
715
+ process.exit(1);
716
+ }
717
+
718
+ // Use default value or skip with "_"
719
+ bypassArgs.push(defaultValue !== undefined ? defaultValue : "_");
720
+ }
721
+ }
722
+
723
+ // Only bypass if we have all non-conditional prompts
724
+ const nonConditionalPrompts = prompts.filter((p) => !p.when);
725
+ // Debug: Check if we're trying to bypass with "_" for required fields
726
+ const hasInvalidBypass = bypassArgs.includes("_");
727
+ if (hasInvalidBypass) {
728
+ console.log(
729
+ "[kos-cli] Cannot bypass prompts - missing required values"
730
+ );
731
+ // Fall back to interactive mode
732
+ console.log(
733
+ "Note: Running interactive mode. Your provided arguments will be applied automatically."
734
+ );
735
+ answers = await generator.runPrompts();
736
+ Object.assign(answers, providedAnswers);
737
+ } else if (bypassArgs.length === nonConditionalPrompts.length) {
738
+ // We can bypass all non-conditional prompts
739
+ answers = await generator.runPrompts(bypassArgs);
740
+
741
+ // Add any conditional prompt values that were provided
742
+ Object.assign(answers, providedAnswers);
743
+ } else {
744
+ // Fall back to interactive mode
745
+ console.log(
746
+ "Note: Running interactive mode. Your provided arguments will be applied automatically."
747
+ );
748
+ answers = await generator.runPrompts();
749
+ Object.assign(answers, providedAnswers);
750
+ }
751
+ } else {
752
+ // Run prompts interactively
753
+ if (forceInteractive) {
754
+ console.log(
755
+ "Note: Running in interactive mode. All prompts will be shown."
756
+ );
757
+
758
+ // In interactive mode, ignore provided arguments and run all prompts
759
+ answers = await generator.runPrompts();
760
+ } else {
761
+ answers = await generator.runPrompts();
762
+ }
763
+ }
764
+
765
+ const results = await generator.runActions(answers);
766
+
767
+ if (!hasQuiet) {
768
+ console.log("[kos-cli] Generator completed successfully!");
769
+ results.changes.forEach((result) => {
770
+ console.log(result.path || result.message);
771
+ });
772
+ }
773
+
774
+ results.failures.forEach((fail) => {
775
+ console.error(fail.error || fail.message);
776
+ });
777
+
778
+ // Output equivalent non-interactive command when running interactively
779
+ if (isRunningInteractively(parsedArgs, hasAnyAnswers) && results.failures.length === 0 && !hasQuiet) {
780
+ const commandString = buildNonInteractiveCommand(command, answers, meta);
781
+ if (commandString) {
782
+ console.log(chalk.cyan("\n[kos-cli] Equivalent non-interactive command:"));
783
+ console.log(chalk.green(` ${commandString}`));
784
+ }
785
+ }
786
+ } catch (err) {
787
+ console.error("[kos-cli] Generator run failed:", err.message);
788
+ process.exit(1);
789
+ }
790
+ }
791
+
792
+ if (showBanner) {
793
+ figlet("KOS CLI", function (err, data) {
794
+ if (err) {
795
+ console.error("Figlet failed:", err);
796
+ return launch(); // even if figlet fails, run!
797
+ }
798
+ console.log(data);
799
+ launch();
800
+ });
801
+ } else {
802
+ launch();
803
+ }