@kosdev-code/kos-ui-cli 2.0.33 → 2.0.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kosdev-code/kos-ui-cli",
3
- "version": "2.0.33",
3
+ "version": "2.0.34",
4
4
  "bin": {
5
5
  "kosui": "./src/lib/cli.mjs"
6
6
  },
@@ -20,7 +20,7 @@
20
20
  "main": "./src/index.js",
21
21
  "kos": {
22
22
  "build": {
23
- "gitHash": "f20219f379424dadeb9920b5996c2acdb8840dfa"
23
+ "gitHash": "abc125566178e7aa50012d17d649f46b4b511cc4"
24
24
  }
25
25
  },
26
26
  "publishConfig": {
package/src/lib/cli.mjs CHANGED
@@ -30,11 +30,11 @@ const showBanner =
30
30
  async function launch() {
31
31
  // Check for help modes before initializing plop to avoid expensive model parsing
32
32
  const shouldShowHelp = parsedArgs.help;
33
-
33
+
34
34
  if (!command || command === "help") {
35
35
  // For general help, we still need plop to list generators
36
36
  const plop = await nodePlop(configPath);
37
-
37
+
38
38
  console.warn("--- KOS CLI Help ---");
39
39
  console.log("\nAvailable Generators:");
40
40
  plop.getGeneratorList().forEach((g) => {
@@ -58,128 +58,153 @@ async function launch() {
58
58
  // For generator-specific help, check if we can show help without full plop initialization
59
59
  if (shouldShowHelp) {
60
60
  const earlyMeta = await getGeneratorMetadata(command);
61
-
61
+
62
62
  if (earlyMeta && Object.keys(earlyMeta).length > 0) {
63
63
  // Show help immediately for --help flag
64
- console.log(`--- ${earlyMeta?.name || command} Help ---`);
65
- console.log(earlyMeta.description || "No description available");
64
+ console.log(`--- ${earlyMeta?.name || command} Help ---`);
65
+ console.log(earlyMeta.description || "No description available");
66
66
 
67
- if (earlyMeta?.namedArguments) {
68
- console.log("\nNamed Arguments:");
69
- Object.entries(earlyMeta.namedArguments).forEach(([cliArg, promptName]) => {
67
+ if (earlyMeta?.namedArguments) {
68
+ console.log("\nNamed Arguments:");
69
+ Object.entries(earlyMeta.namedArguments).forEach(
70
+ ([cliArg, promptName]) => {
70
71
  console.log(` --${cliArg} Maps to prompt: ${promptName}`);
71
- });
72
- }
72
+ }
73
+ );
74
+ }
75
+
76
+ console.log("\nExamples:");
77
+
78
+ // Generate examples dynamically from the generator's named arguments
79
+ if (earlyMeta?.namedArguments) {
80
+ const args = Object.keys(earlyMeta.namedArguments);
73
81
 
74
- console.log("\nExamples:");
75
-
76
- // Generate examples dynamically from the generator's named arguments
77
- if (earlyMeta?.namedArguments) {
78
- const args = Object.keys(earlyMeta.namedArguments);
79
-
80
82
  // Generate example values based on argument names
81
83
  const generateExampleValue = (argName) => {
82
84
  switch (argName) {
83
- case 'name':
84
- case 'componentName':
85
- return command.includes('plugin') ? 'MyPlugin' : 'MyComponent';
86
- case 'modelName':
87
- return 'my-model';
88
- case 'workspaceName':
89
- return 'my-workspace';
90
- case 'project':
91
- case 'componentProject':
92
- case 'modelProject':
93
- return 'my-ui-lib';
94
- case 'registrationProject':
95
- return 'my-lib';
96
- case 'companionParent':
97
- return 'parent-model';
98
- case 'extensionPoint':
99
- return 'utility';
100
- case 'group':
101
- return 'appearance';
102
- case 'locale':
103
- return 'en';
104
- case 'container':
105
- case 'singleton':
106
- case 'parentAware':
107
- case 'dataServices':
85
+ case "name":
86
+ case "componentName":
87
+ return command.includes("plugin") ? "MyPlugin" : "MyComponent";
88
+ case "modelName":
89
+ return "my-model";
90
+ case "workspaceName":
91
+ return "my-workspace";
92
+ case "project":
93
+ case "componentProject":
94
+ case "modelProject":
95
+ return "my-ui-lib";
96
+ case "registrationProject":
97
+ return "my-lib";
98
+ case "companionParent":
99
+ return "parent-model";
100
+ case "extensionPoint":
101
+ return "utility";
102
+ case "group":
103
+ return "appearance";
104
+ case "locale":
105
+ return "en";
106
+ case "container":
107
+ case "singleton":
108
+ case "parentAware":
109
+ case "dataServices":
108
110
  return true;
109
- case 'dryRun':
111
+ case "dryRun":
110
112
  return true;
111
113
  default:
112
114
  return `my-${argName}`;
113
115
  }
114
116
  };
115
-
117
+
116
118
  // Create basic example with most important arguments
117
119
  const basicArgs = [];
118
- const booleanArgs = ['container', 'singleton', 'parentAware', 'dataServices', 'dryRun'];
119
-
120
+ const booleanArgs = [
121
+ "container",
122
+ "singleton",
123
+ "parentAware",
124
+ "dataServices",
125
+ "dryRun",
126
+ ];
127
+
120
128
  // Pick the most relevant arguments for each generator type
121
129
  const getRelevantArgs = (args, command) => {
122
130
  const relevantArgs = [];
123
-
131
+
124
132
  // Always prefer name-type arguments first
125
- const nameArgs = ['name', 'componentName', 'modelName', 'workspaceName'].filter(arg => args.includes(arg));
133
+ const nameArgs = [
134
+ "name",
135
+ "componentName",
136
+ "modelName",
137
+ "workspaceName",
138
+ ].filter((arg) => args.includes(arg));
126
139
  if (nameArgs.length > 0) {
127
140
  relevantArgs.push(nameArgs[0]); // Take the first name argument
128
141
  }
129
-
142
+
130
143
  // Then add project-type arguments
131
- const projectArgs = ['project', 'componentProject', 'modelProject', 'registrationProject'].filter(arg => args.includes(arg));
144
+ const projectArgs = [
145
+ "project",
146
+ "componentProject",
147
+ "modelProject",
148
+ "registrationProject",
149
+ ].filter((arg) => args.includes(arg));
132
150
  if (projectArgs.length > 0) {
133
151
  relevantArgs.push(projectArgs[0]); // Take the first project argument
134
152
  }
135
-
153
+
136
154
  // Add other specific arguments
137
- const otherArgs = args.filter(arg =>
138
- !nameArgs.includes(arg) &&
139
- !projectArgs.includes(arg) &&
140
- !booleanArgs.includes(arg) &&
141
- arg !== 'interactive'
155
+ const otherArgs = args.filter(
156
+ (arg) =>
157
+ !nameArgs.includes(arg) &&
158
+ !projectArgs.includes(arg) &&
159
+ !booleanArgs.includes(arg) &&
160
+ arg !== "interactive"
142
161
  );
143
-
162
+
144
163
  // Add up to 2 more relevant arguments
145
164
  relevantArgs.push(...otherArgs.slice(0, 2));
146
-
165
+
147
166
  return relevantArgs;
148
167
  };
149
-
168
+
150
169
  const relevantArgs = getRelevantArgs(args, command);
151
-
152
- relevantArgs.forEach(argName => {
170
+
171
+ relevantArgs.forEach((argName) => {
153
172
  const value = generateExampleValue(argName);
154
173
  basicArgs.push(`--${argName} ${value}`);
155
174
  });
156
-
175
+
157
176
  // Create advanced example with boolean flags
158
177
  const advancedBooleanArgs = [];
159
- booleanArgs.forEach(argName => {
178
+ booleanArgs.forEach((argName) => {
160
179
  if (args.includes(argName)) {
161
180
  advancedBooleanArgs.push(`--${argName}`);
162
181
  }
163
182
  });
164
-
183
+
165
184
  // Show basic example
166
185
  if (basicArgs.length > 0) {
167
- console.log(` kosui ${command} ${basicArgs.join(' ')}`);
186
+ console.log(` kosui ${command} ${basicArgs.join(" ")}`);
168
187
  }
169
-
188
+
170
189
  // Show advanced example with boolean flags if any exist
171
190
  if (basicArgs.length > 0 && advancedBooleanArgs.length > 0) {
172
- console.log(` kosui ${command} ${basicArgs.slice(0, 2).join(' ')} ${advancedBooleanArgs.join(' ')}`);
191
+ console.log(
192
+ ` kosui ${command} ${basicArgs
193
+ .slice(0, 2)
194
+ .join(" ")} ${advancedBooleanArgs.join(" ")}`
195
+ );
173
196
  }
174
-
197
+
175
198
  // If no basic args, show a minimal example
176
199
  if (basicArgs.length === 0) {
177
200
  console.log(` kosui ${command} [options]`);
178
201
  }
179
202
  }
180
-
203
+
181
204
  console.log(` kosui ${command} --interactive # Force interactive mode`);
182
- console.log(` kosui ${command} -i # Force interactive mode (short form)`);
205
+ console.log(
206
+ ` kosui ${command} -i # Force interactive mode (short form)`
207
+ );
183
208
  return;
184
209
  }
185
210
  }
@@ -203,7 +228,7 @@ async function launch() {
203
228
  });
204
229
  const hasAnyAnswers = Object.keys(providedAnswers).length > 0;
205
230
  const forceInteractive = parsedArgs.interactive || parsedArgs.i;
206
-
231
+
207
232
  // Show help for no-arguments case
208
233
  if (!hasAnyAnswers && !forceInteractive) {
209
234
  console.log(`--- ${earlyMeta?.name || command} Help ---`);
@@ -211,120 +236,145 @@ async function launch() {
211
236
 
212
237
  if (earlyMeta?.namedArguments) {
213
238
  console.log("\nNamed Arguments:");
214
- Object.entries(earlyMeta.namedArguments).forEach(([cliArg, promptName]) => {
215
- console.log(` --${cliArg} Maps to prompt: ${promptName}`);
216
- });
239
+ Object.entries(earlyMeta.namedArguments).forEach(
240
+ ([cliArg, promptName]) => {
241
+ console.log(` --${cliArg} Maps to prompt: ${promptName}`);
242
+ }
243
+ );
217
244
  }
218
245
 
219
246
  console.log("\nExamples:");
220
-
247
+
221
248
  // Generate examples dynamically from the generator's named arguments
222
249
  if (earlyMeta?.namedArguments) {
223
250
  const args = Object.keys(earlyMeta.namedArguments);
224
-
251
+
225
252
  // Generate example values based on argument names
226
253
  const generateExampleValue = (argName) => {
227
254
  switch (argName) {
228
- case 'name':
229
- case 'componentName':
230
- return command.includes('plugin') ? 'MyPlugin' : 'MyComponent';
231
- case 'modelName':
232
- return 'my-model';
233
- case 'workspaceName':
234
- return 'my-workspace';
235
- case 'project':
236
- case 'componentProject':
237
- case 'modelProject':
238
- return 'my-ui-lib';
239
- case 'registrationProject':
240
- return 'my-lib';
241
- case 'companionParent':
242
- return 'parent-model';
243
- case 'extensionPoint':
244
- return 'utility';
245
- case 'group':
246
- return 'appearance';
247
- case 'locale':
248
- return 'en';
249
- case 'container':
250
- case 'singleton':
251
- case 'parentAware':
252
- case 'dataServices':
255
+ case "name":
256
+ case "componentName":
257
+ return command.includes("plugin") ? "MyPlugin" : "MyComponent";
258
+ case "modelName":
259
+ return "my-model";
260
+ case "workspaceName":
261
+ return "my-workspace";
262
+ case "project":
263
+ case "componentProject":
264
+ case "modelProject":
265
+ return "my-ui-lib";
266
+ case "registrationProject":
267
+ return "my-lib";
268
+ case "companionParent":
269
+ return "parent-model";
270
+ case "extensionPoint":
271
+ return "utility";
272
+ case "group":
273
+ return "appearance";
274
+ case "locale":
275
+ return "en";
276
+ case "container":
277
+ case "singleton":
278
+ case "parentAware":
279
+ case "dataServices":
253
280
  return true;
254
- case 'dryRun':
281
+ case "dryRun":
255
282
  return true;
256
283
  default:
257
284
  return `my-${argName}`;
258
285
  }
259
286
  };
260
-
287
+
261
288
  // Create basic example with most important arguments
262
289
  const basicArgs = [];
263
- const booleanArgs = ['container', 'singleton', 'parentAware', 'dataServices', 'dryRun'];
264
-
290
+ const booleanArgs = [
291
+ "container",
292
+ "singleton",
293
+ "parentAware",
294
+ "dataServices",
295
+ "dryRun",
296
+ ];
297
+
265
298
  // Pick the most relevant arguments for each generator type
266
299
  const getRelevantArgs = (args, command) => {
267
300
  const relevantArgs = [];
268
-
301
+
269
302
  // Always prefer name-type arguments first
270
- const nameArgs = ['name', 'componentName', 'modelName', 'workspaceName'].filter(arg => args.includes(arg));
303
+ const nameArgs = [
304
+ "name",
305
+ "componentName",
306
+ "modelName",
307
+ "workspaceName",
308
+ ].filter((arg) => args.includes(arg));
271
309
  if (nameArgs.length > 0) {
272
310
  relevantArgs.push(nameArgs[0]); // Take the first name argument
273
311
  }
274
-
312
+
275
313
  // Then add project-type arguments
276
- const projectArgs = ['project', 'componentProject', 'modelProject', 'registrationProject'].filter(arg => args.includes(arg));
314
+ const projectArgs = [
315
+ "project",
316
+ "componentProject",
317
+ "modelProject",
318
+ "registrationProject",
319
+ ].filter((arg) => args.includes(arg));
277
320
  if (projectArgs.length > 0) {
278
321
  relevantArgs.push(projectArgs[0]); // Take the first project argument
279
322
  }
280
-
323
+
281
324
  // Add other specific arguments
282
- const otherArgs = args.filter(arg =>
283
- !nameArgs.includes(arg) &&
284
- !projectArgs.includes(arg) &&
285
- !booleanArgs.includes(arg) &&
286
- arg !== 'interactive'
325
+ const otherArgs = args.filter(
326
+ (arg) =>
327
+ !nameArgs.includes(arg) &&
328
+ !projectArgs.includes(arg) &&
329
+ !booleanArgs.includes(arg) &&
330
+ arg !== "interactive"
287
331
  );
288
-
332
+
289
333
  // Add up to 2 more relevant arguments
290
334
  relevantArgs.push(...otherArgs.slice(0, 2));
291
-
335
+
292
336
  return relevantArgs;
293
337
  };
294
-
338
+
295
339
  const relevantArgs = getRelevantArgs(args, command);
296
-
297
- relevantArgs.forEach(argName => {
340
+
341
+ relevantArgs.forEach((argName) => {
298
342
  const value = generateExampleValue(argName);
299
343
  basicArgs.push(`--${argName} ${value}`);
300
344
  });
301
-
345
+
302
346
  // Create advanced example with boolean flags
303
347
  const advancedBooleanArgs = [];
304
- booleanArgs.forEach(argName => {
348
+ booleanArgs.forEach((argName) => {
305
349
  if (args.includes(argName)) {
306
350
  advancedBooleanArgs.push(`--${argName}`);
307
351
  }
308
352
  });
309
-
353
+
310
354
  // Show basic example
311
355
  if (basicArgs.length > 0) {
312
- console.log(` kosui ${command} ${basicArgs.join(' ')}`);
356
+ console.log(` kosui ${command} ${basicArgs.join(" ")}`);
313
357
  }
314
-
358
+
315
359
  // Show advanced example with boolean flags if any exist
316
360
  if (basicArgs.length > 0 && advancedBooleanArgs.length > 0) {
317
- console.log(` kosui ${command} ${basicArgs.slice(0, 2).join(' ')} ${advancedBooleanArgs.join(' ')}`);
361
+ console.log(
362
+ ` kosui ${command} ${basicArgs
363
+ .slice(0, 2)
364
+ .join(" ")} ${advancedBooleanArgs.join(" ")}`
365
+ );
318
366
  }
319
-
367
+
320
368
  // If no basic args, show a minimal example
321
369
  if (basicArgs.length === 0) {
322
370
  console.log(` kosui ${command} [options]`);
323
371
  }
324
372
  }
325
-
373
+
326
374
  console.log(` kosui ${command} --interactive # Force interactive mode`);
327
- console.log(` kosui ${command} -i # Force interactive mode (short form)`);
375
+ console.log(
376
+ ` kosui ${command} -i # Force interactive mode (short form)`
377
+ );
328
378
  return;
329
379
  }
330
380
  }
@@ -345,7 +395,6 @@ async function launch() {
345
395
 
346
396
  const meta = await getGeneratorMetadata(command);
347
397
 
348
-
349
398
  if (showBanner) {
350
399
  console.log(`--- Running KOS Generator: ${meta?.name || command} ---`);
351
400
  }
@@ -359,6 +408,14 @@ async function launch() {
359
408
 
360
409
  // Convert CLI args to answer format using generator's mapping
361
410
  const providedAnswers = {};
411
+
412
+ // Ensure we have the metadata with named arguments
413
+ if (!namedArgumentsMap || Object.keys(namedArgumentsMap).length === 0) {
414
+ console.warn(
415
+ `[kos-cli] Warning: No named arguments mapping found for generator '${command}'`
416
+ );
417
+ }
418
+
362
419
  Object.entries(namedArgumentsMap).forEach(([cliArg, promptName]) => {
363
420
  // Skip 'interactive' - it's handled as a special CLI flag, not a prompt argument
364
421
  if (cliArg === "interactive") {
@@ -421,114 +478,137 @@ async function launch() {
421
478
  }
422
479
 
423
480
  console.log("\nExamples:");
424
-
481
+
425
482
  // Generate examples dynamically from the generator's named arguments
426
483
  if (meta?.namedArguments) {
427
484
  const args = Object.keys(meta.namedArguments);
428
-
485
+
429
486
  // Generate example values based on argument names
430
487
  const generateExampleValue = (argName) => {
431
488
  switch (argName) {
432
- case 'name':
433
- case 'componentName':
434
- return command.includes('plugin') ? 'MyPlugin' : 'MyComponent';
435
- case 'modelName':
436
- return 'my-model';
437
- case 'workspaceName':
438
- return 'my-workspace';
439
- case 'project':
440
- case 'componentProject':
441
- case 'modelProject':
442
- return 'my-ui-lib';
443
- case 'registrationProject':
444
- return 'my-lib';
445
- case 'companionParent':
446
- return 'parent-model';
447
- case 'extensionPoint':
448
- return 'utility';
449
- case 'group':
450
- return 'appearance';
451
- case 'locale':
452
- return 'en';
453
- case 'container':
454
- case 'singleton':
455
- case 'parentAware':
456
- case 'dataServices':
489
+ case "name":
490
+ case "componentName":
491
+ return command.includes("plugin") ? "MyPlugin" : "MyComponent";
492
+ case "modelName":
493
+ return "my-model";
494
+ case "workspaceName":
495
+ return "my-workspace";
496
+ case "project":
497
+ case "componentProject":
498
+ case "modelProject":
499
+ return "my-ui-lib";
500
+ case "registrationProject":
501
+ return "my-lib";
502
+ case "companionParent":
503
+ return "parent-model";
504
+ case "extensionPoint":
505
+ return "utility";
506
+ case "group":
507
+ return "appearance";
508
+ case "locale":
509
+ return "en";
510
+ case "container":
511
+ case "singleton":
512
+ case "parentAware":
513
+ case "dataServices":
457
514
  return true;
458
- case 'dryRun':
515
+ case "dryRun":
459
516
  return true;
460
517
  default:
461
518
  return `my-${argName}`;
462
519
  }
463
520
  };
464
-
521
+
465
522
  // Create basic example with most important arguments
466
523
  const basicArgs = [];
467
- const booleanArgs = ['container', 'singleton', 'parentAware', 'dataServices', 'dryRun'];
468
-
524
+ const booleanArgs = [
525
+ "container",
526
+ "singleton",
527
+ "parentAware",
528
+ "dataServices",
529
+ "dryRun",
530
+ ];
531
+
469
532
  // Pick the most relevant arguments for each generator type
470
533
  const getRelevantArgs = (args, command) => {
471
534
  const relevantArgs = [];
472
-
535
+
473
536
  // Always prefer name-type arguments first
474
- const nameArgs = ['name', 'componentName', 'modelName', 'workspaceName'].filter(arg => args.includes(arg));
537
+ const nameArgs = [
538
+ "name",
539
+ "componentName",
540
+ "modelName",
541
+ "workspaceName",
542
+ ].filter((arg) => args.includes(arg));
475
543
  if (nameArgs.length > 0) {
476
544
  relevantArgs.push(nameArgs[0]); // Take the first name argument
477
545
  }
478
-
546
+
479
547
  // Then add project-type arguments
480
- const projectArgs = ['project', 'componentProject', 'modelProject', 'registrationProject'].filter(arg => args.includes(arg));
548
+ const projectArgs = [
549
+ "project",
550
+ "componentProject",
551
+ "modelProject",
552
+ "registrationProject",
553
+ ].filter((arg) => args.includes(arg));
481
554
  if (projectArgs.length > 0) {
482
555
  relevantArgs.push(projectArgs[0]); // Take the first project argument
483
556
  }
484
-
557
+
485
558
  // Add other specific arguments
486
- const otherArgs = args.filter(arg =>
487
- !nameArgs.includes(arg) &&
488
- !projectArgs.includes(arg) &&
489
- !booleanArgs.includes(arg) &&
490
- arg !== 'interactive'
559
+ const otherArgs = args.filter(
560
+ (arg) =>
561
+ !nameArgs.includes(arg) &&
562
+ !projectArgs.includes(arg) &&
563
+ !booleanArgs.includes(arg) &&
564
+ arg !== "interactive"
491
565
  );
492
-
566
+
493
567
  // Add up to 2 more relevant arguments
494
568
  relevantArgs.push(...otherArgs.slice(0, 2));
495
-
569
+
496
570
  return relevantArgs;
497
571
  };
498
-
572
+
499
573
  const relevantArgs = getRelevantArgs(args, command);
500
-
501
- relevantArgs.forEach(argName => {
574
+
575
+ relevantArgs.forEach((argName) => {
502
576
  const value = generateExampleValue(argName);
503
577
  basicArgs.push(`--${argName} ${value}`);
504
578
  });
505
-
579
+
506
580
  // Create advanced example with boolean flags
507
581
  const advancedBooleanArgs = [];
508
- booleanArgs.forEach(argName => {
582
+ booleanArgs.forEach((argName) => {
509
583
  if (args.includes(argName)) {
510
584
  advancedBooleanArgs.push(`--${argName}`);
511
585
  }
512
586
  });
513
-
587
+
514
588
  // Show basic example
515
589
  if (basicArgs.length > 0) {
516
- console.log(` kosui ${command} ${basicArgs.join(' ')}`);
590
+ console.log(` kosui ${command} ${basicArgs.join(" ")}`);
517
591
  }
518
-
592
+
519
593
  // Show advanced example with boolean flags if any exist
520
594
  if (basicArgs.length > 0 && advancedBooleanArgs.length > 0) {
521
- console.log(` kosui ${command} ${basicArgs.slice(0, 2).join(' ')} ${advancedBooleanArgs.join(' ')}`);
595
+ console.log(
596
+ ` kosui ${command} ${basicArgs
597
+ .slice(0, 2)
598
+ .join(" ")} ${advancedBooleanArgs.join(" ")}`
599
+ );
522
600
  }
523
-
601
+
524
602
  // If no basic args, show a minimal example
525
603
  if (basicArgs.length === 0) {
526
604
  console.log(` kosui ${command} [options]`);
527
605
  }
528
606
  }
529
-
607
+
530
608
  console.log(` kosui ${command} --interactive # Force interactive mode`);
531
- console.log(` kosui ${command} -i # Force interactive mode (short form)`);
609
+ console.log(
610
+ ` kosui ${command} -i # Force interactive mode (short form)`
611
+ );
532
612
  return;
533
613
  }
534
614
 
@@ -547,14 +627,103 @@ async function launch() {
547
627
  if (providedValue !== undefined) {
548
628
  bypassArgs.push(providedValue);
549
629
  } else {
630
+ // Check for workspace defaults for project-type prompts
631
+ let defaultValue = prompt.default;
632
+
633
+ // Use workspace defaults for project selection prompts
634
+ if (
635
+ prompt.name &&
636
+ (prompt.name.includes("Project") || prompt.name === "project")
637
+ ) {
638
+ const { getDefaultProjectForType } = await import(
639
+ "./utils/nx-context.mjs"
640
+ );
641
+
642
+ // Determine project type based on command
643
+ let projectType = null;
644
+ if (command === "model" || command.startsWith("model:")) {
645
+ projectType = "model";
646
+ } else if (command === "component") {
647
+ projectType = "model-component";
648
+ } else if (command === "i18n:namespace") {
649
+ projectType = "i18n";
650
+ } else if (command.startsWith("plugin")) {
651
+ projectType = "plugin";
652
+ }
653
+
654
+ if (projectType) {
655
+ const workspaceDefault = await getDefaultProjectForType(
656
+ projectType
657
+ );
658
+ if (workspaceDefault) {
659
+ defaultValue = workspaceDefault;
660
+ }
661
+ }
662
+ }
663
+
664
+ // For required project prompts without a default value, we can't bypass - need to show help
665
+ if (
666
+ defaultValue === undefined &&
667
+ (prompt.name === "modelProject" ||
668
+ prompt.name === "project" ||
669
+ prompt.name === "componentProject" ||
670
+ prompt.name === "registrationProject")
671
+ ) {
672
+ // Can't bypass required project prompts without a value - show help instead
673
+ console.log(
674
+ `\nError: Missing required argument '--project' (no default project configured)`
675
+ );
676
+ console.log(`\n--- ${meta?.name || command} Help ---`);
677
+ console.log(meta.description || "No description available");
678
+
679
+ if (meta?.namedArguments) {
680
+ console.log("\nNamed Arguments:");
681
+ Object.entries(meta.namedArguments).forEach(
682
+ ([cliArg, promptName]) => {
683
+ console.log(` --${cliArg} Maps to prompt: ${promptName}`);
684
+ }
685
+ );
686
+ }
687
+
688
+ console.log("\nTo fix this, you can:");
689
+ console.log(
690
+ "1. Specify a project: kosui " +
691
+ command +
692
+ " --name MyModel --project my-model-lib"
693
+ );
694
+ console.log(
695
+ "2. Configure a default in .kos.json at the workspace root:"
696
+ );
697
+ console.log(
698
+ ' { "type": "root", "generator": { "defaults": { "model": "my-default-project" } } }'
699
+ );
700
+ console.log(
701
+ "3. Run in interactive mode: kosui " + command + " --interactive"
702
+ );
703
+
704
+ process.exit(1);
705
+ }
706
+
550
707
  // Use default value or skip with "_"
551
- bypassArgs.push(prompt.default !== undefined ? prompt.default : "_");
708
+ bypassArgs.push(defaultValue !== undefined ? defaultValue : "_");
552
709
  }
553
710
  }
554
711
 
555
712
  // Only bypass if we have all non-conditional prompts
556
713
  const nonConditionalPrompts = prompts.filter((p) => !p.when);
557
- if (bypassArgs.length === nonConditionalPrompts.length) {
714
+ // Debug: Check if we're trying to bypass with "_" for required fields
715
+ const hasInvalidBypass = bypassArgs.includes("_");
716
+ if (hasInvalidBypass) {
717
+ console.log(
718
+ "[kos-cli] Cannot bypass prompts - missing required values"
719
+ );
720
+ // Fall back to interactive mode
721
+ console.log(
722
+ "Note: Running interactive mode. Your provided arguments will be applied automatically."
723
+ );
724
+ answers = await generator.runPrompts();
725
+ Object.assign(answers, providedAnswers);
726
+ } else if (bypassArgs.length === nonConditionalPrompts.length) {
558
727
  // We can bypass all non-conditional prompts
559
728
  answers = await generator.runPrompts(bypassArgs);
560
729
 
@@ -1,7 +1,7 @@
1
1
  // generators/component/index.mjs
2
2
  import { actionFactory } from "../../utils/action-factory.mjs";
3
3
  import { execute } from "../../utils/exec.mjs";
4
- import { getAllProjects } from "../../utils/nx-context.mjs";
4
+ import { getComponentCompatibleProjectsWithFallback } from "../../utils/nx-context.mjs";
5
5
  import { required } from "../../utils/validators.mjs";
6
6
 
7
7
  export const metadata = {
@@ -16,7 +16,7 @@ export const metadata = {
16
16
  };
17
17
 
18
18
  export default async function (plop) {
19
- const allProjects = await getAllProjects();
19
+ const componentProjects = await getComponentCompatibleProjectsWithFallback();
20
20
 
21
21
  plop.setActionType("createComponent", async function (answers) {
22
22
  const command = `npx nx generate @kosdev-code/kos-nx-plugin:kos-component \
@@ -47,7 +47,7 @@ export default async function (plop) {
47
47
  type: "list",
48
48
  name: "componentProject",
49
49
  message: "Which project should the component be created in?",
50
- choices: allProjects,
50
+ choices: componentProjects,
51
51
  },
52
52
  ],
53
53
  actions: actionFactory("createComponent", metadata),
@@ -2,7 +2,7 @@
2
2
  import fs from "fs";
3
3
  import path from "path";
4
4
  import { actionFactory } from "../../utils/action-factory.mjs";
5
- import { getAllProjects, getProjectDetails } from "../../utils/nx-context.mjs";
5
+ import { getI18nProjectsWithFallback, getProjectDetails } from "../../utils/nx-context.mjs";
6
6
  import { required } from "../../utils/validators.mjs";
7
7
 
8
8
  export const metadata = {
@@ -16,7 +16,7 @@ export const metadata = {
16
16
  };
17
17
 
18
18
  export default async function (plop) {
19
- const allProjects = await getAllProjects();
19
+ const i18nProjects = await getI18nProjectsWithFallback();
20
20
 
21
21
  plop.setActionType("addNamespace", async function (answers) {
22
22
  const i18nProject = await getProjectDetails(answers.project);
@@ -53,7 +53,7 @@ export default async function (plop) {
53
53
  type: "list",
54
54
  name: "project",
55
55
  message: "Which project should the namespace be created in?",
56
- choices: allProjects,
56
+ choices: i18nProjects,
57
57
  },
58
58
  ],
59
59
  actions: actionFactory("addNamespace", metadata),
@@ -3,7 +3,7 @@ import { actionFactory } from "../../utils/action-factory.mjs";
3
3
  import { execute } from "../../utils/exec.mjs";
4
4
  import {
5
5
  getAllModels,
6
- getLibraryProjects,
6
+ getModelProjectsWithFallback,
7
7
  getProjectDetails,
8
8
  } from "../../utils/nx-context.mjs";
9
9
  import {
@@ -34,7 +34,7 @@ export const metadata = {
34
34
 
35
35
  export default async function (plop) {
36
36
  const allModels = await getAllModels();
37
- const libraryProjects = await getLibraryProjects();
37
+ const libraryProjects = await getModelProjectsWithFallback();
38
38
  const modelChoices = allModels.map((m) => ({
39
39
  name: `${m.model} (${m.project})`,
40
40
  value: m.model,
@@ -2,7 +2,7 @@
2
2
  import { actionFactory } from "../../utils/action-factory.mjs";
3
3
  import { execute } from "../../utils/exec.mjs";
4
4
  import {
5
- getLibraryProjects,
5
+ getModelProjectsWithFallback,
6
6
  getProjectDetails,
7
7
  } from "../../utils/nx-context.mjs";
8
8
  import { DEFAULT_PROMPTS, MODEL_PROMPTS } from "../../utils/prompts.mjs";
@@ -28,7 +28,7 @@ export const metadata = {
28
28
  };
29
29
 
30
30
  export default async function (plop) {
31
- const libraryProjects = await getLibraryProjects();
31
+ const libraryProjects = await getModelProjectsWithFallback();
32
32
 
33
33
  plop.setActionType("createModel", async function (answers) {
34
34
  const modelProject = await getProjectDetails(answers.modelProject);
@@ -1,7 +1,7 @@
1
1
  // generators/plugin/plugin.mjs
2
2
 
3
3
  import { execute } from "../../utils/exec.mjs";
4
- import { getAllProjects } from "../../utils/nx-context.mjs";
4
+ import { getPluginProjectsWithFallback } from "../../utils/nx-context.mjs";
5
5
  import { required } from "../../utils/validators.mjs";
6
6
  export const metadata = [
7
7
  {
@@ -79,7 +79,7 @@ export const metadata = [
79
79
  ];
80
80
 
81
81
  export default async function (plop) {
82
- const allProjects = await getAllProjects();
82
+ const pluginProjects = await getPluginProjectsWithFallback();
83
83
 
84
84
  plop.setActionType("createPluginComponent", async function (answers) {
85
85
  const pluginType = answers.extensionPoint;
@@ -105,7 +105,7 @@ export default async function (plop) {
105
105
  type: "list",
106
106
  name: "componentProject",
107
107
  message: "Which project should the component be created in?",
108
- choices: allProjects,
108
+ choices: pluginProjects,
109
109
  },
110
110
  ];
111
111
 
@@ -1,6 +1,6 @@
1
1
  // utils/nx-context.mjs
2
2
  import { execSync } from "child_process";
3
- import { existsSync, readFileSync } from "fs";
3
+ import { existsSync, readFileSync, readdirSync, statSync } from "fs";
4
4
  import path from "path";
5
5
  import { fileURLToPath } from "url";
6
6
  import { getCached, setCached } from "./cache.mjs";
@@ -12,6 +12,45 @@ function execJson(cmd) {
12
12
  return JSON.parse(output);
13
13
  }
14
14
 
15
+ function findKosJsonFiles(dir = process.cwd(), files = []) {
16
+ try {
17
+ const entries = readdirSync(dir);
18
+
19
+ for (const entry of entries) {
20
+ // Skip common directories that won't have .kos.json files
21
+ // BUT don't skip .kos.json files themselves!
22
+ if (entry === 'node_modules' || entry === '.git' || entry === '.nx' ||
23
+ entry === 'dist' || entry === 'coverage' || entry === '.vscode' ||
24
+ entry === '.idea' || (entry.startsWith('.') && entry !== '.kos.json')) {
25
+ continue;
26
+ }
27
+
28
+ const fullPath = path.join(dir, entry);
29
+
30
+ try {
31
+ const stat = statSync(fullPath);
32
+
33
+ if (stat.isFile() && entry === '.kos.json') {
34
+ files.push(fullPath);
35
+ } else if (stat.isDirectory()) {
36
+ // Only recurse into directories that might contain projects
37
+ // Skip common build/temp directories
38
+ if (entry !== 'tmp' && entry !== 'temp' && entry !== 'build') {
39
+ findKosJsonFiles(fullPath, files);
40
+ }
41
+ }
42
+ } catch (error) {
43
+ // Skip files/directories we can't access
44
+ continue;
45
+ }
46
+ }
47
+ } catch (error) {
48
+ // Skip directories we can't read
49
+ }
50
+
51
+ return files;
52
+ }
53
+
15
54
  let _workspaceDetected;
16
55
 
17
56
  export async function detectWorkspace(startDir = process.cwd()) {
@@ -61,43 +100,270 @@ export async function getPluginProjects() {
61
100
  return filtered;
62
101
  }
63
102
 
64
- export async function getAllModels() {
65
- const cached = getCached("allModels");
103
+ export async function getAllKosProjects() {
104
+ const cached = getCached("allKosProjects");
66
105
  if (cached) return cached;
67
- console.warn(`parsing projects for models...`);
68
- const allProjects = await getAllProjects();
69
- const models = await allProjects.reduce(async (acc, curr) => {
70
- console.warn(`[kos-cli] Pulling modles from project: ${curr}`);
71
- const project = await getProjectDetails(curr);
72
- const list = await acc;
73
-
74
- const kosConfigPath = path.join(project.root, ".kos.json");
75
-
76
- if (!existsSync(kosConfigPath)) {
77
- console.warn(
78
- `[kos-cli] No .kos.json found in project ${curr}, skipping...`
79
- );
80
- return list;
81
- }
82
- const kosConfig = JSON.parse(readFileSync(kosConfigPath, "utf-8"));
83
- if (kosConfig.models) {
84
- Object.keys(kosConfig.models).forEach((model) => {
85
- list.push({ model: model, project: project.name });
86
- });
106
+
107
+ console.warn(`[kos-cli] Discovering KOS projects by scanning .kos.json files...`);
108
+
109
+ // In an NX workspace, focus on common project directories first
110
+ const projectDirs = ['apps', 'libs', 'packages'];
111
+ const kosJsonFiles = [];
112
+ const workspaceRoot = process.cwd();
113
+
114
+ // First scan common project directories
115
+ for (const dir of projectDirs) {
116
+ const dirPath = path.join(workspaceRoot, dir);
117
+ if (existsSync(dirPath)) {
118
+ findKosJsonFiles(dirPath, kosJsonFiles);
87
119
  }
120
+ }
121
+
122
+ // Also check the workspace root for any .kos.json files
123
+ const rootKosJson = path.join(workspaceRoot, '.kos.json');
124
+ if (existsSync(rootKosJson)) {
125
+ kosJsonFiles.push(rootKosJson);
126
+ }
127
+
128
+ // If we didn't find any in the common directories, fall back to full scan
129
+ if (kosJsonFiles.length === 0) {
130
+ console.warn(`[kos-cli] No .kos.json files found in common directories, performing full workspace scan...`);
131
+ findKosJsonFiles(workspaceRoot, kosJsonFiles);
132
+ }
133
+
134
+ const kosProjects = [];
88
135
 
89
- return list.sort((a, b) => {
90
- if (a.project === b.project) {
91
- return a.model.localeCompare(b.model);
136
+ for (const kosJsonPath of kosJsonFiles) {
137
+ try {
138
+ const kosConfig = JSON.parse(readFileSync(kosJsonPath, "utf-8"));
139
+ const projectDir = path.dirname(kosJsonPath);
140
+
141
+ // Try to determine the project name by looking for project.json
142
+ let projectName = null;
143
+ const projectJsonPath = path.join(projectDir, 'project.json');
144
+
145
+ if (existsSync(projectJsonPath)) {
146
+ try {
147
+ const projectJson = JSON.parse(readFileSync(projectJsonPath, "utf-8"));
148
+ projectName = projectJson.name;
149
+ } catch (error) {
150
+ // If we can't read project.json, use directory name as fallback
151
+ projectName = path.basename(projectDir);
152
+ }
92
153
  } else {
93
- return a.project.localeCompare(b.project);
154
+ // Use directory name as fallback
155
+ projectName = path.basename(projectDir);
94
156
  }
157
+
158
+ // Skip root-type configurations as they are not projects
159
+ if (kosConfig.type === 'root') {
160
+ continue;
161
+ }
162
+
163
+ kosProjects.push({
164
+ name: projectName,
165
+ path: projectDir,
166
+ kosJsonPath,
167
+ config: kosConfig,
168
+ projectType: kosConfig.generator?.projectType
169
+ });
170
+ } catch (error) {
171
+ console.warn(`[kos-cli] Error reading ${kosJsonPath}: ${error.message}`);
172
+ }
173
+ }
174
+
175
+ setCached("allKosProjects", kosProjects);
176
+ return kosProjects;
177
+ }
178
+
179
+ export async function getProjectsByType(targetType) {
180
+ const cacheKey = `projectsByType:${targetType}`;
181
+ const cached = getCached(cacheKey);
182
+ if (cached) return cached;
183
+
184
+ console.warn(`[kos-cli] Filtering projects for ${targetType} projectType...`);
185
+ const allKosProjects = await getAllKosProjects();
186
+ const filteredProjects = [];
187
+
188
+ for (const kosProject of allKosProjects) {
189
+ const projectType = kosProject.projectType;
190
+
191
+ // Handle both string and array formats for projectType
192
+ const hasTargetType = Array.isArray(projectType)
193
+ ? projectType.includes(targetType)
194
+ : projectType === targetType;
195
+
196
+ if (hasTargetType) {
197
+ filteredProjects.push(kosProject.name);
198
+ }
199
+ }
200
+
201
+ setCached(cacheKey, filteredProjects);
202
+ return filteredProjects;
203
+ }
204
+
205
+ export async function getModelProjects() {
206
+ return await getProjectsByType("model");
207
+ }
208
+
209
+ export async function getUIProjects() {
210
+ return await getProjectsByType("ui");
211
+ }
212
+
213
+ export async function getModelComponentProjects() {
214
+ return await getProjectsByType("model-component");
215
+ }
216
+
217
+ export async function getI18nProjects() {
218
+ return await getProjectsByType("i18n");
219
+ }
220
+
221
+
222
+ export async function getProjectsByTypeWithFallback(targetType, fallbackFunction = getLibraryProjects) {
223
+ const filteredProjects = await getProjectsByType(targetType);
224
+
225
+ if (filteredProjects.length > 0) {
226
+ console.warn(`[kos-cli] Found ${filteredProjects.length} ${targetType} projects`);
227
+ return filteredProjects;
228
+ } else {
229
+ console.warn(`[kos-cli] No ${targetType} projects found, showing fallback projects`);
230
+ return await fallbackFunction();
231
+ }
232
+ }
233
+
234
+ export async function getModelProjectsWithFallback() {
235
+ return await getProjectsByTypeWithFallback("model", getLibraryProjects);
236
+ }
237
+
238
+ export async function getModelComponentProjectsWithFallback() {
239
+ return await getProjectsByTypeWithFallback("model-component", getAllProjects);
240
+ }
241
+
242
+ export async function getProjectsByMultipleTypes(targetTypes) {
243
+ const cacheKey = `projectsByMultipleTypes:${targetTypes.join(',')}`;
244
+ const cached = getCached(cacheKey);
245
+ if (cached) return cached;
246
+
247
+ console.warn(`[kos-cli] Filtering projects for projectTypes: ${targetTypes.join(', ')}...`);
248
+ const allKosProjects = await getAllKosProjects();
249
+ const filteredProjects = [];
250
+
251
+ for (const kosProject of allKosProjects) {
252
+ const projectType = kosProject.projectType;
253
+
254
+ // Handle both string and array formats for projectType
255
+ const hasAnyTargetType = targetTypes.some(targetType => {
256
+ return Array.isArray(projectType)
257
+ ? projectType.includes(targetType)
258
+ : projectType === targetType;
95
259
  });
96
- }, []);
260
+
261
+ if (hasAnyTargetType) {
262
+ filteredProjects.push(kosProject.name);
263
+ }
264
+ }
265
+
266
+ setCached(cacheKey, filteredProjects);
267
+ return filteredProjects;
268
+ }
269
+
270
+ export async function getComponentCompatibleProjects() {
271
+ return await getProjectsByMultipleTypes(["ui", "splash", "model-component"]);
272
+ }
273
+
274
+ export async function getComponentCompatibleProjectsWithFallback() {
275
+ const componentProjects = await getComponentCompatibleProjects();
276
+
277
+ if (componentProjects.length > 0) {
278
+ console.warn(`[kos-cli] Found ${componentProjects.length} component-compatible projects`);
279
+ return componentProjects;
280
+ } else {
281
+ console.warn(`[kos-cli] No component-compatible projects found, showing all projects`);
282
+ return await getAllProjects();
283
+ }
284
+ }
285
+
286
+ export async function getI18nProjectsWithFallback() {
287
+ return await getProjectsByTypeWithFallback("i18n", getAllProjects);
288
+ }
289
+
290
+ export async function getPluginProjectsWithFallback() {
291
+ // First try type-based filtering
292
+ const pluginProjectsByType = await getProjectsByType("plugin");
293
+ if (pluginProjectsByType.length > 0) {
294
+ console.warn(`[kos-cli] Found ${pluginProjectsByType.length} plugin projects by type`);
295
+ return pluginProjectsByType;
296
+ }
297
+
298
+ // Fall back to existing name-based filtering
299
+ const pluginProjectsByName = await getPluginProjects();
300
+ if (pluginProjectsByName.length > 0) {
301
+ console.warn(`[kos-cli] Found ${pluginProjectsByName.length} plugin projects by name`);
302
+ return pluginProjectsByName;
303
+ }
304
+
305
+ // Final fallback to all projects
306
+ console.warn(`[kos-cli] No plugin projects found, showing all projects`);
307
+ return await getAllProjects();
308
+ }
309
+
310
+ export async function getAllModels() {
311
+ const cached = getCached("allModels");
312
+ if (cached) return cached;
313
+
314
+ console.warn(`[kos-cli] Scanning for models in KOS projects...`);
315
+ const allKosProjects = await getAllKosProjects();
316
+ const models = [];
317
+
318
+ for (const kosProject of allKosProjects) {
319
+ if (kosProject.config.models) {
320
+ Object.keys(kosProject.config.models).forEach((model) => {
321
+ models.push({ model: model, project: kosProject.name });
322
+ });
323
+ }
324
+ }
325
+
326
+ // Sort models by project name first, then by model name
327
+ models.sort((a, b) => {
328
+ if (a.project === b.project) {
329
+ return a.model.localeCompare(b.model);
330
+ } else {
331
+ return a.project.localeCompare(b.project);
332
+ }
333
+ });
334
+
97
335
  setCached("allModels", models);
98
336
  return models;
99
337
  }
100
338
 
339
+ export async function getWorkspaceDefaults() {
340
+ const cached = getCached("workspaceDefaults");
341
+ if (cached) return cached;
342
+
343
+ const workspaceRoot = process.cwd();
344
+ const rootKosJsonPath = path.join(workspaceRoot, '.kos.json');
345
+
346
+ if (existsSync(rootKosJsonPath)) {
347
+ try {
348
+ const rootConfig = JSON.parse(readFileSync(rootKosJsonPath, "utf-8"));
349
+ if (rootConfig.type === 'root' && rootConfig.generator?.defaults) {
350
+ setCached("workspaceDefaults", rootConfig.generator.defaults);
351
+ return rootConfig.generator.defaults;
352
+ }
353
+ } catch (error) {
354
+ console.warn(`[kos-cli] Error reading workspace defaults: ${error.message}`);
355
+ }
356
+ }
357
+
358
+ setCached("workspaceDefaults", {});
359
+ return {};
360
+ }
361
+
362
+ export async function getDefaultProjectForType(projectType) {
363
+ const defaults = await getWorkspaceDefaults();
364
+ return defaults[projectType] || null;
365
+ }
366
+
101
367
  export async function getProjectDetails(projectName) {
102
368
  const cacheKey = `projectDetails:${projectName}`;
103
369
  const cached = getCached(cacheKey);