@kosdev-code/kos-ui-cli 2.1.25 → 2.1.27

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
@@ -8,6 +8,7 @@ import { fileURLToPath } from "node:url";
8
8
  import { clearCache } from "./utils/cache.mjs";
9
9
  import { buildNonInteractiveCommand, isRunningInteractively } from "./utils/command-builder.mjs";
10
10
  import { getGeneratorMetadata } from "./utils/generator-loader.mjs";
11
+ import { displayGeneralHelp, displayGeneratorHelp } from "./utils/cli-help-display.mjs";
11
12
 
12
13
  const originalArgs = process.argv.slice(2);
13
14
  const parsedArgs = minimist(originalArgs);
@@ -37,6 +38,47 @@ const configPath = path.join(__dirname, "plopfile.mjs");
37
38
  const showBanner =
38
39
  command !== "help" && process.env.DISABLE_CLI_BANNER !== "true";
39
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
+
40
82
  async function launch() {
41
83
  // Check for help modes before initializing plop to avoid expensive model parsing
42
84
  const shouldShowHelp = parsedArgs.help;
@@ -44,25 +86,7 @@ async function launch() {
44
86
  if (!command || command === "help") {
45
87
  // For general help, we still need plop to list generators
46
88
  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
-
89
+ displayGeneralHelp(plop);
66
90
  return;
67
91
  }
68
92
 
@@ -71,151 +95,7 @@ async function launch() {
71
95
  const earlyMeta = await getGeneratorMetadata(command);
72
96
 
73
97
  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
- );
98
+ displayGeneratorHelp(command, earlyMeta);
219
99
  return;
220
100
  }
221
101
  }
@@ -228,13 +108,7 @@ async function launch() {
228
108
  Object.entries(namedArgumentsMap).forEach(([cliArg, promptName]) => {
229
109
  if (cliArg === "interactive") return;
230
110
  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;
111
+ providedAnswers[promptName] = parseArgumentValue(parsedArgs[cliArg]);
238
112
  }
239
113
  });
240
114
  const hasAnyAnswers = Object.keys(providedAnswers).length > 0;
@@ -242,150 +116,7 @@ async function launch() {
242
116
 
243
117
  // Show help for no-arguments case
244
118
  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
- );
119
+ displayGeneratorHelp(command, earlyMeta);
389
120
  return;
390
121
  }
391
122
  }
@@ -434,16 +165,7 @@ async function launch() {
434
165
  }
435
166
 
436
167
  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;
168
+ providedAnswers[promptName] = parseArgumentValue(parsedArgs[cliArg]);
447
169
  }
448
170
  });
449
171
 
@@ -478,148 +200,7 @@ async function launch() {
478
200
 
479
201
  // If no arguments provided and no interactive flag, show help
480
202
  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
- );
203
+ displayGeneratorHelp(command, meta, generator);
623
204
  return;
624
205
  }
625
206
 
@@ -0,0 +1,105 @@
1
+ import { generateApiTypes } from "./lib/generate-api.mjs";
2
+
3
+ /**
4
+ * Generator for creating OpenAPI service types
5
+ */
6
+ export default async function register(plop) {
7
+ plop.setGenerator("api:generate", {
8
+ description: "Generate OpenAPI service types for a project",
9
+ prompts: [
10
+ {
11
+ type: "input",
12
+ name: "project",
13
+ message: "Project name",
14
+ validate: (input) => {
15
+ if (!input || input.trim() === "") {
16
+ return "Project name is required";
17
+ }
18
+ return true;
19
+ },
20
+ },
21
+ {
22
+ type: "input",
23
+ name: "host",
24
+ message: "API host URL (or 'inherit' to use project.json)",
25
+ default: "inherit",
26
+ },
27
+ {
28
+ type: "input",
29
+ name: "apps",
30
+ message: "Apps to include (comma-separated, leave empty for all)",
31
+ default: "",
32
+ filter: (input) => {
33
+ if (!input || input.trim() === "") {
34
+ return [];
35
+ }
36
+ return input.split(",").map((s) => s.trim()).filter(Boolean);
37
+ },
38
+ },
39
+ {
40
+ type: "input",
41
+ name: "outputPath",
42
+ message: "Output path (or 'inherit' to use project.json)",
43
+ default: "inherit",
44
+ },
45
+ {
46
+ type: "input",
47
+ name: "defaultVersion",
48
+ message: "Default version (or 'inherit' to use project.json)",
49
+ default: "inherit",
50
+ },
51
+ {
52
+ type: "input",
53
+ name: "allAppsVersion",
54
+ message: "Override all apps to specific version (leave empty to use individual versions)",
55
+ default: "",
56
+ },
57
+ ],
58
+ actions: function (answers) {
59
+ return [
60
+ async function generateApi() {
61
+ try {
62
+ const options = {
63
+ project: answers.project,
64
+ host: answers.host === "inherit" ? undefined : answers.host,
65
+ studio: false, // Third-party projects don't use Studio endpoints
66
+ apps: answers.apps,
67
+ excludeCore: true, // Always exclude core services (already in published SDKs)
68
+ outputPath: answers.outputPath === "inherit" ? undefined : (answers.outputPath || undefined),
69
+ defaultVersion: answers.defaultVersion === "inherit" ? undefined : answers.defaultVersion,
70
+ allAppsVersion: answers.allAppsVersion || undefined,
71
+ // Pass CLI overrides if provided (they'll be merged with project.json in generate-api.mjs)
72
+ appVersions: answers.appVersions,
73
+ serviceExportAliases: answers.serviceExportAliases,
74
+ };
75
+
76
+ const result = await generateApiTypes(options);
77
+
78
+ return `[ok] Generated ${result.filesGenerated} files for ${result.apps.length} apps: ${result.apps.join(", ")}`;
79
+ } catch (error) {
80
+ console.error("Error generating API types:", error);
81
+ throw new Error(`Failed to generate API types: ${error.message}`);
82
+ }
83
+ },
84
+ ];
85
+ },
86
+ });
87
+ }
88
+
89
+ // Export metadata for CLI integration
90
+ export const metadata = {
91
+ key: "api:generate",
92
+ name: "Generate OpenAPI service types",
93
+ description: "Generate typed service helpers from OpenAPI specifications",
94
+ namedArguments: {
95
+ project: "project",
96
+ host: "host",
97
+ apps: "apps",
98
+ output: "outputPath",
99
+ "default-version": "defaultVersion",
100
+ "all-apps-version": "allAppsVersion",
101
+ // Advanced options that can be passed as JSON strings or key=value pairs
102
+ alias: "serviceExportAliases",
103
+ "app-version": "appVersions",
104
+ },
105
+ };