@kosdev-code/kos-ui-cli 2.0.44 → 2.0.45

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/src/lib/cli.mjs CHANGED
@@ -1,11 +1,14 @@
1
1
  #!/usr/bin/env node
2
+ import chalk from "chalk";
2
3
  import figlet from "figlet";
3
4
  import minimist from "minimist";
4
5
  import nodePlop from "node-plop";
5
6
  import path, { dirname } from "node:path";
6
7
  import { fileURLToPath } from "node:url";
7
8
  import { clearCache } from "./utils/cache.mjs";
9
+ import { buildNonInteractiveCommand, isRunningInteractively } from "./utils/command-builder.mjs";
8
10
  import { getGeneratorMetadata } from "./utils/generator-loader.mjs";
11
+ import { displayGeneralHelp, displayGeneratorHelp } from "./utils/cli-help-display.mjs";
9
12
 
10
13
  const originalArgs = process.argv.slice(2);
11
14
  const parsedArgs = minimist(originalArgs);
@@ -35,6 +38,47 @@ const configPath = path.join(__dirname, "plopfile.mjs");
35
38
  const showBanner =
36
39
  command !== "help" && process.env.DISABLE_CLI_BANNER !== "true";
37
40
 
41
+ /**
42
+ * Parse CLI argument values, handling booleans, JSON, key=value pairs, and repeated arguments
43
+ * @param {*} value - The raw argument value from minimist
44
+ * @returns {*} - The parsed value (boolean, object, or original value)
45
+ */
46
+ function parseArgumentValue(value) {
47
+ // Convert string boolean values to actual booleans
48
+ if (value === "true" || value === "y" || value === "yes") {
49
+ return true;
50
+ } else if (value === "false" || value === "n" || value === "no") {
51
+ return false;
52
+ } else if (typeof value === "string" && (value.startsWith("{") || value.startsWith("["))) {
53
+ // Try to parse JSON for complex arguments like --app-version='{"ice-app":"v2"}'
54
+ try {
55
+ return JSON.parse(value);
56
+ } catch (e) {
57
+ // If JSON parsing fails, keep as string
58
+ return value;
59
+ }
60
+ } else if (Array.isArray(value)) {
61
+ // Handle repeated arguments like --app-version ice-app=v2 --app-version apc=v3
62
+ // Minimist stores repeated args as arrays
63
+ const parsedObject = {};
64
+ value.forEach(item => {
65
+ if (typeof item === 'string' && item.includes('=')) {
66
+ const [key, val] = item.split('=');
67
+ parsedObject[key.trim()] = val.trim();
68
+ }
69
+ });
70
+ return Object.keys(parsedObject).length > 0 ? parsedObject : value;
71
+ } else if (typeof value === "string" && value.includes("=")) {
72
+ // Handle single key=value format like --app-version ice-app=v2
73
+ const parsedObject = {};
74
+ const [key, val] = value.split('=');
75
+ parsedObject[key.trim()] = val.trim();
76
+ return parsedObject;
77
+ }
78
+
79
+ return value;
80
+ }
81
+
38
82
  async function launch() {
39
83
  // Check for help modes before initializing plop to avoid expensive model parsing
40
84
  const shouldShowHelp = parsedArgs.help;
@@ -42,25 +86,7 @@ async function launch() {
42
86
  if (!command || command === "help") {
43
87
  // For general help, we still need plop to list generators
44
88
  const plop = await nodePlop(configPath);
45
-
46
- console.warn("--- KOS CLI Help ---");
47
- console.log("\nAvailable Generators:");
48
- plop.getGeneratorList().forEach((g) => {
49
- console.log(`- ${g.name}: ${g.description}`);
50
- });
51
-
52
- console.log("\nUsage:");
53
- console.log(" kosui <generator> [options]");
54
- console.log(" kosui <generator> --help # Show generator-specific help");
55
- console.log("\nGlobal Options:");
56
- console.log(" --no-cache Disable cache");
57
- console.log(" --refresh Clear cache and refresh");
58
- console.log(" --quiet Suppress banner and debug output");
59
- console.log(
60
- " --interactive, -i Force interactive mode (ignore provided arguments)"
61
- );
62
- console.log(" --help Show this help");
63
-
89
+ displayGeneralHelp(plop);
64
90
  return;
65
91
  }
66
92
 
@@ -69,151 +95,7 @@ async function launch() {
69
95
  const earlyMeta = await getGeneratorMetadata(command);
70
96
 
71
97
  if (earlyMeta && Object.keys(earlyMeta).length > 0) {
72
- // Show help immediately for --help flag
73
- console.log(`--- ${earlyMeta?.name || command} Help ---`);
74
- console.log(earlyMeta.description || "No description available");
75
-
76
- if (earlyMeta?.namedArguments) {
77
- console.log("\nNamed Arguments:");
78
- Object.entries(earlyMeta.namedArguments).forEach(
79
- ([cliArg, promptName]) => {
80
- console.log(` --${cliArg} Maps to prompt: ${promptName}`);
81
- }
82
- );
83
- }
84
-
85
- console.log("\nExamples:");
86
-
87
- // Generate examples dynamically from the generator's named arguments
88
- if (earlyMeta?.namedArguments) {
89
- const args = Object.keys(earlyMeta.namedArguments);
90
-
91
- // Generate example values based on argument names
92
- const generateExampleValue = (argName) => {
93
- switch (argName) {
94
- case "name":
95
- case "componentName":
96
- return command.includes("plugin") ? "MyPlugin" : "MyComponent";
97
- case "modelName":
98
- return "my-model";
99
- case "workspaceName":
100
- return "my-workspace";
101
- case "project":
102
- case "componentProject":
103
- case "modelProject":
104
- return "my-ui-lib";
105
- case "registrationProject":
106
- return "my-lib";
107
- case "companionParent":
108
- return "parent-model";
109
- case "extensionPoint":
110
- return "utility";
111
- case "group":
112
- return "appearance";
113
- case "locale":
114
- return "en";
115
- case "container":
116
- case "singleton":
117
- case "parentAware":
118
- case "dataServices":
119
- return true;
120
- case "dryRun":
121
- return true;
122
- default:
123
- return `my-${argName}`;
124
- }
125
- };
126
-
127
- // Create basic example with most important arguments
128
- const basicArgs = [];
129
- const booleanArgs = [
130
- "container",
131
- "singleton",
132
- "parentAware",
133
- "dataServices",
134
- "dryRun",
135
- ];
136
-
137
- // Pick the most relevant arguments for each generator type
138
- const getRelevantArgs = (args, command) => {
139
- const relevantArgs = [];
140
-
141
- // Always prefer name-type arguments first
142
- const nameArgs = [
143
- "name",
144
- "componentName",
145
- "modelName",
146
- "workspaceName",
147
- ].filter((arg) => args.includes(arg));
148
- if (nameArgs.length > 0) {
149
- relevantArgs.push(nameArgs[0]); // Take the first name argument
150
- }
151
-
152
- // Then add project-type arguments
153
- const projectArgs = [
154
- "project",
155
- "componentProject",
156
- "modelProject",
157
- "registrationProject",
158
- ].filter((arg) => args.includes(arg));
159
- if (projectArgs.length > 0) {
160
- relevantArgs.push(projectArgs[0]); // Take the first project argument
161
- }
162
-
163
- // Add other specific arguments
164
- const otherArgs = args.filter(
165
- (arg) =>
166
- !nameArgs.includes(arg) &&
167
- !projectArgs.includes(arg) &&
168
- !booleanArgs.includes(arg) &&
169
- arg !== "interactive"
170
- );
171
-
172
- // Add up to 2 more relevant arguments
173
- relevantArgs.push(...otherArgs.slice(0, 2));
174
-
175
- return relevantArgs;
176
- };
177
-
178
- const relevantArgs = getRelevantArgs(args, command);
179
-
180
- relevantArgs.forEach((argName) => {
181
- const value = generateExampleValue(argName);
182
- basicArgs.push(`--${argName} ${value}`);
183
- });
184
-
185
- // Create advanced example with boolean flags
186
- const advancedBooleanArgs = [];
187
- booleanArgs.forEach((argName) => {
188
- if (args.includes(argName)) {
189
- advancedBooleanArgs.push(`--${argName}`);
190
- }
191
- });
192
-
193
- // Show basic example
194
- if (basicArgs.length > 0) {
195
- console.log(` kosui ${command} ${basicArgs.join(" ")}`);
196
- }
197
-
198
- // Show advanced example with boolean flags if any exist
199
- if (basicArgs.length > 0 && advancedBooleanArgs.length > 0) {
200
- console.log(
201
- ` kosui ${command} ${basicArgs
202
- .slice(0, 2)
203
- .join(" ")} ${advancedBooleanArgs.join(" ")}`
204
- );
205
- }
206
-
207
- // If no basic args, show a minimal example
208
- if (basicArgs.length === 0) {
209
- console.log(` kosui ${command} [options]`);
210
- }
211
- }
212
-
213
- console.log(` kosui ${command} --interactive # Force interactive mode`);
214
- console.log(
215
- ` kosui ${command} -i # Force interactive mode (short form)`
216
- );
98
+ displayGeneratorHelp(command, earlyMeta);
217
99
  return;
218
100
  }
219
101
  }
@@ -226,13 +108,7 @@ async function launch() {
226
108
  Object.entries(namedArgumentsMap).forEach(([cliArg, promptName]) => {
227
109
  if (cliArg === "interactive") return;
228
110
  if (parsedArgs[cliArg] !== undefined) {
229
- let value = parsedArgs[cliArg];
230
- if (value === "true" || value === "y" || value === "yes") {
231
- value = true;
232
- } else if (value === "false" || value === "n" || value === "no") {
233
- value = false;
234
- }
235
- providedAnswers[promptName] = value;
111
+ providedAnswers[promptName] = parseArgumentValue(parsedArgs[cliArg]);
236
112
  }
237
113
  });
238
114
  const hasAnyAnswers = Object.keys(providedAnswers).length > 0;
@@ -240,150 +116,7 @@ async function launch() {
240
116
 
241
117
  // Show help for no-arguments case
242
118
  if (!hasAnyAnswers && !forceInteractive) {
243
- console.log(`--- ${earlyMeta?.name || command} Help ---`);
244
- console.log(earlyMeta.description || "No description available");
245
-
246
- if (earlyMeta?.namedArguments) {
247
- console.log("\nNamed Arguments:");
248
- Object.entries(earlyMeta.namedArguments).forEach(
249
- ([cliArg, promptName]) => {
250
- console.log(` --${cliArg} Maps to prompt: ${promptName}`);
251
- }
252
- );
253
- }
254
-
255
- console.log("\nExamples:");
256
-
257
- // Generate examples dynamically from the generator's named arguments
258
- if (earlyMeta?.namedArguments) {
259
- const args = Object.keys(earlyMeta.namedArguments);
260
-
261
- // Generate example values based on argument names
262
- const generateExampleValue = (argName) => {
263
- switch (argName) {
264
- case "name":
265
- case "componentName":
266
- return command.includes("plugin") ? "MyPlugin" : "MyComponent";
267
- case "modelName":
268
- return "my-model";
269
- case "workspaceName":
270
- return "my-workspace";
271
- case "project":
272
- case "componentProject":
273
- case "modelProject":
274
- return "my-ui-lib";
275
- case "registrationProject":
276
- return "my-lib";
277
- case "companionParent":
278
- return "parent-model";
279
- case "extensionPoint":
280
- return "utility";
281
- case "group":
282
- return "appearance";
283
- case "locale":
284
- return "en";
285
- case "container":
286
- case "singleton":
287
- case "parentAware":
288
- case "dataServices":
289
- return true;
290
- case "dryRun":
291
- return true;
292
- default:
293
- return `my-${argName}`;
294
- }
295
- };
296
-
297
- // Create basic example with most important arguments
298
- const basicArgs = [];
299
- const booleanArgs = [
300
- "container",
301
- "singleton",
302
- "parentAware",
303
- "dataServices",
304
- "dryRun",
305
- ];
306
-
307
- // Pick the most relevant arguments for each generator type
308
- const getRelevantArgs = (args, command) => {
309
- const relevantArgs = [];
310
-
311
- // Always prefer name-type arguments first
312
- const nameArgs = [
313
- "name",
314
- "componentName",
315
- "modelName",
316
- "workspaceName",
317
- ].filter((arg) => args.includes(arg));
318
- if (nameArgs.length > 0) {
319
- relevantArgs.push(nameArgs[0]); // Take the first name argument
320
- }
321
-
322
- // Then add project-type arguments
323
- const projectArgs = [
324
- "project",
325
- "componentProject",
326
- "modelProject",
327
- "registrationProject",
328
- ].filter((arg) => args.includes(arg));
329
- if (projectArgs.length > 0) {
330
- relevantArgs.push(projectArgs[0]); // Take the first project argument
331
- }
332
-
333
- // Add other specific arguments
334
- const otherArgs = args.filter(
335
- (arg) =>
336
- !nameArgs.includes(arg) &&
337
- !projectArgs.includes(arg) &&
338
- !booleanArgs.includes(arg) &&
339
- arg !== "interactive"
340
- );
341
-
342
- // Add up to 2 more relevant arguments
343
- relevantArgs.push(...otherArgs.slice(0, 2));
344
-
345
- return relevantArgs;
346
- };
347
-
348
- const relevantArgs = getRelevantArgs(args, command);
349
-
350
- relevantArgs.forEach((argName) => {
351
- const value = generateExampleValue(argName);
352
- basicArgs.push(`--${argName} ${value}`);
353
- });
354
-
355
- // Create advanced example with boolean flags
356
- const advancedBooleanArgs = [];
357
- booleanArgs.forEach((argName) => {
358
- if (args.includes(argName)) {
359
- advancedBooleanArgs.push(`--${argName}`);
360
- }
361
- });
362
-
363
- // Show basic example
364
- if (basicArgs.length > 0) {
365
- console.log(` kosui ${command} ${basicArgs.join(" ")}`);
366
- }
367
-
368
- // Show advanced example with boolean flags if any exist
369
- if (basicArgs.length > 0 && advancedBooleanArgs.length > 0) {
370
- console.log(
371
- ` kosui ${command} ${basicArgs
372
- .slice(0, 2)
373
- .join(" ")} ${advancedBooleanArgs.join(" ")}`
374
- );
375
- }
376
-
377
- // If no basic args, show a minimal example
378
- if (basicArgs.length === 0) {
379
- console.log(` kosui ${command} [options]`);
380
- }
381
- }
382
-
383
- console.log(` kosui ${command} --interactive # Force interactive mode`);
384
- console.log(
385
- ` kosui ${command} -i # Force interactive mode (short form)`
386
- );
119
+ displayGeneratorHelp(command, earlyMeta);
387
120
  return;
388
121
  }
389
122
  }
@@ -432,16 +165,7 @@ async function launch() {
432
165
  }
433
166
 
434
167
  if (parsedArgs[cliArg] !== undefined) {
435
- let value = parsedArgs[cliArg];
436
-
437
- // Convert string boolean values to actual booleans
438
- if (value === "true" || value === "y" || value === "yes") {
439
- value = true;
440
- } else if (value === "false" || value === "n" || value === "no") {
441
- value = false;
442
- }
443
-
444
- providedAnswers[promptName] = value;
168
+ providedAnswers[promptName] = parseArgumentValue(parsedArgs[cliArg]);
445
169
  }
446
170
  });
447
171
 
@@ -476,148 +200,7 @@ async function launch() {
476
200
 
477
201
  // If no arguments provided and no interactive flag, show help
478
202
  if (!hasAnyAnswers && !forceInteractive) {
479
- console.log(`--- ${meta?.name || command} Help ---`);
480
- console.log(generator.description || "No description available");
481
-
482
- if (meta?.namedArguments) {
483
- console.log("\nNamed Arguments:");
484
- Object.entries(meta.namedArguments).forEach(([cliArg, promptName]) => {
485
- console.log(` --${cliArg} Maps to prompt: ${promptName}`);
486
- });
487
- }
488
-
489
- console.log("\nExamples:");
490
-
491
- // Generate examples dynamically from the generator's named arguments
492
- if (meta?.namedArguments) {
493
- const args = Object.keys(meta.namedArguments);
494
-
495
- // Generate example values based on argument names
496
- const generateExampleValue = (argName) => {
497
- switch (argName) {
498
- case "name":
499
- case "componentName":
500
- return command.includes("plugin") ? "MyPlugin" : "MyComponent";
501
- case "modelName":
502
- return "my-model";
503
- case "workspaceName":
504
- return "my-workspace";
505
- case "project":
506
- case "componentProject":
507
- case "modelProject":
508
- return "my-ui-lib";
509
- case "registrationProject":
510
- return "my-lib";
511
- case "companionParent":
512
- return "parent-model";
513
- case "extensionPoint":
514
- return "utility";
515
- case "group":
516
- return "appearance";
517
- case "locale":
518
- return "en";
519
- case "container":
520
- case "singleton":
521
- case "parentAware":
522
- case "dataServices":
523
- return true;
524
- case "dryRun":
525
- return true;
526
- default:
527
- return `my-${argName}`;
528
- }
529
- };
530
-
531
- // Create basic example with most important arguments
532
- const basicArgs = [];
533
- const booleanArgs = [
534
- "container",
535
- "singleton",
536
- "parentAware",
537
- "dataServices",
538
- "dryRun",
539
- ];
540
-
541
- // Pick the most relevant arguments for each generator type
542
- const getRelevantArgs = (args, command) => {
543
- const relevantArgs = [];
544
-
545
- // Always prefer name-type arguments first
546
- const nameArgs = [
547
- "name",
548
- "componentName",
549
- "modelName",
550
- "workspaceName",
551
- ].filter((arg) => args.includes(arg));
552
- if (nameArgs.length > 0) {
553
- relevantArgs.push(nameArgs[0]); // Take the first name argument
554
- }
555
-
556
- // Then add project-type arguments
557
- const projectArgs = [
558
- "project",
559
- "componentProject",
560
- "modelProject",
561
- "registrationProject",
562
- ].filter((arg) => args.includes(arg));
563
- if (projectArgs.length > 0) {
564
- relevantArgs.push(projectArgs[0]); // Take the first project argument
565
- }
566
-
567
- // Add other specific arguments
568
- const otherArgs = args.filter(
569
- (arg) =>
570
- !nameArgs.includes(arg) &&
571
- !projectArgs.includes(arg) &&
572
- !booleanArgs.includes(arg) &&
573
- arg !== "interactive"
574
- );
575
-
576
- // Add up to 2 more relevant arguments
577
- relevantArgs.push(...otherArgs.slice(0, 2));
578
-
579
- return relevantArgs;
580
- };
581
-
582
- const relevantArgs = getRelevantArgs(args, command);
583
-
584
- relevantArgs.forEach((argName) => {
585
- const value = generateExampleValue(argName);
586
- basicArgs.push(`--${argName} ${value}`);
587
- });
588
-
589
- // Create advanced example with boolean flags
590
- const advancedBooleanArgs = [];
591
- booleanArgs.forEach((argName) => {
592
- if (args.includes(argName)) {
593
- advancedBooleanArgs.push(`--${argName}`);
594
- }
595
- });
596
-
597
- // Show basic example
598
- if (basicArgs.length > 0) {
599
- console.log(` kosui ${command} ${basicArgs.join(" ")}`);
600
- }
601
-
602
- // Show advanced example with boolean flags if any exist
603
- if (basicArgs.length > 0 && advancedBooleanArgs.length > 0) {
604
- console.log(
605
- ` kosui ${command} ${basicArgs
606
- .slice(0, 2)
607
- .join(" ")} ${advancedBooleanArgs.join(" ")}`
608
- );
609
- }
610
-
611
- // If no basic args, show a minimal example
612
- if (basicArgs.length === 0) {
613
- console.log(` kosui ${command} [options]`);
614
- }
615
- }
616
-
617
- console.log(` kosui ${command} --interactive # Force interactive mode`);
618
- console.log(
619
- ` kosui ${command} -i # Force interactive mode (short form)`
620
- );
203
+ displayGeneratorHelp(command, meta, generator);
621
204
  return;
622
205
  }
623
206
 
@@ -772,6 +355,15 @@ async function launch() {
772
355
  results.failures.forEach((fail) => {
773
356
  console.error(fail.error || fail.message);
774
357
  });
358
+
359
+ // Output equivalent non-interactive command when running interactively
360
+ if (isRunningInteractively(parsedArgs, hasAnyAnswers) && results.failures.length === 0 && !hasQuiet) {
361
+ const commandString = buildNonInteractiveCommand(command, answers, meta);
362
+ if (commandString) {
363
+ console.log(chalk.cyan("\n[kos-cli] Equivalent non-interactive command:"));
364
+ console.log(chalk.green(` ${commandString}`));
365
+ }
366
+ }
775
367
  } catch (err) {
776
368
  console.error("[kos-cli] Generator run failed:", err.message);
777
369
  process.exit(1);