@kosdev-code/kos-ui-cli 2.0.31 → 2.0.32

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.31",
3
+ "version": "2.0.32",
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": "d7b8c63a9bfe61bb6177f4e7628bbb17e3696c2b"
23
+ "gitHash": "ea8751ddd6a1c43f56f22bab3bf99a0e3bd3f824"
24
24
  }
25
25
  },
26
26
  "publishConfig": {
package/src/lib/cli.mjs CHANGED
@@ -28,14 +28,13 @@ const showBanner =
28
28
  command !== "help" && process.env.DISABLE_CLI_BANNER !== "true";
29
29
 
30
30
  async function launch() {
31
- const plop = await nodePlop(configPath);
32
-
33
- plop.setActionType("clearCache", (answers, config, plop) => {
34
- clearCache();
35
- return "[ok] CLI cache cleared successfully.";
36
- });
37
-
31
+ // Check for help modes before initializing plop to avoid expensive model parsing
32
+ const shouldShowHelp = parsedArgs.help;
33
+
38
34
  if (!command || command === "help") {
35
+ // For general help, we still need plop to list generators
36
+ const plop = await nodePlop(configPath);
37
+
39
38
  console.warn("--- KOS CLI Help ---");
40
39
  console.log("\nAvailable Generators:");
41
40
  plop.getGeneratorList().forEach((g) => {
@@ -56,139 +55,297 @@ async function launch() {
56
55
  return;
57
56
  }
58
57
 
59
- const generator = plop.getGenerator(command);
60
-
61
- if (!generator) {
62
- console.error(`[kos-cli] Generator "${command}" not found.`);
63
- process.exit(1);
64
- }
65
-
66
- const meta = await getGeneratorMetadata(command);
67
-
68
- // Show generator-specific help if requested
69
- if (parsedArgs.help) {
70
- console.log(`--- ${meta?.name || command} Help ---`);
71
- console.log(generator.description || "No description available");
72
-
73
- if (meta?.namedArguments) {
74
- console.log("\nNamed Arguments:");
75
- Object.entries(meta.namedArguments).forEach(([cliArg, promptName]) => {
76
- console.log(` --${cliArg} Maps to prompt: ${promptName}`);
77
- });
78
- }
79
-
80
- console.log("\nExamples:");
58
+ // For generator-specific help, check if we can show help without full plop initialization
59
+ if (shouldShowHelp) {
60
+ const earlyMeta = await getGeneratorMetadata(command);
81
61
 
82
- // Generate examples dynamically from the generator's named arguments
83
- if (meta?.namedArguments) {
84
- const args = Object.keys(meta.namedArguments);
85
-
86
- // Generate example values based on argument names
87
- const generateExampleValue = (argName) => {
88
- switch (argName) {
89
- case 'name':
90
- case 'componentName':
91
- return command.includes('plugin') ? 'MyPlugin' : 'MyComponent';
92
- case 'modelName':
93
- return 'my-model';
94
- case 'workspaceName':
95
- return 'my-workspace';
96
- case 'project':
97
- case 'componentProject':
98
- case 'modelProject':
99
- return 'my-ui-lib';
100
- case 'registrationProject':
101
- return 'my-lib';
102
- case 'companionParent':
103
- return 'parent-model';
104
- case 'extensionPoint':
105
- return 'utility';
106
- case 'group':
107
- return 'appearance';
108
- case 'locale':
109
- return 'en';
110
- case 'container':
111
- case 'singleton':
112
- case 'parentAware':
113
- case 'dataServices':
114
- return true;
115
- case 'dryRun':
116
- return true;
117
- default:
118
- return `my-${argName}`;
62
+ if (earlyMeta && Object.keys(earlyMeta).length > 0) {
63
+ // Show help immediately for --help flag
64
+ console.log(`--- ${earlyMeta?.name || command} Help ---`);
65
+ console.log(earlyMeta.description || "No description available");
66
+
67
+ if (earlyMeta?.namedArguments) {
68
+ console.log("\nNamed Arguments:");
69
+ Object.entries(earlyMeta.namedArguments).forEach(([cliArg, promptName]) => {
70
+ console.log(` --${cliArg} Maps to prompt: ${promptName}`);
71
+ });
119
72
  }
120
- };
121
-
122
- // Create basic example with most important arguments
123
- const basicArgs = [];
124
- const booleanArgs = ['container', 'singleton', 'parentAware', 'dataServices', 'dryRun'];
125
-
126
- // Pick the most relevant arguments for each generator type
127
- const getRelevantArgs = (args, command) => {
128
- const relevantArgs = [];
73
+
74
+ console.log("\nExamples:");
129
75
 
130
- // Always prefer name-type arguments first
131
- const nameArgs = ['name', 'componentName', 'modelName', 'workspaceName'].filter(arg => args.includes(arg));
132
- if (nameArgs.length > 0) {
133
- relevantArgs.push(nameArgs[0]); // Take the first name argument
134
- }
76
+ // Generate examples dynamically from the generator's named arguments
77
+ if (earlyMeta?.namedArguments) {
78
+ const args = Object.keys(earlyMeta.namedArguments);
135
79
 
136
- // Then add project-type arguments
137
- const projectArgs = ['project', 'componentProject', 'modelProject', 'registrationProject'].filter(arg => args.includes(arg));
138
- if (projectArgs.length > 0) {
139
- relevantArgs.push(projectArgs[0]); // Take the first project argument
140
- }
80
+ // Generate example values based on argument names
81
+ const generateExampleValue = (argName) => {
82
+ 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':
108
+ return true;
109
+ case 'dryRun':
110
+ return true;
111
+ default:
112
+ return `my-${argName}`;
113
+ }
114
+ };
141
115
 
142
- // Add other specific arguments
143
- const otherArgs = args.filter(arg =>
144
- !nameArgs.includes(arg) &&
145
- !projectArgs.includes(arg) &&
146
- !booleanArgs.includes(arg) &&
147
- arg !== 'interactive'
148
- );
116
+ // Create basic example with most important arguments
117
+ const basicArgs = [];
118
+ const booleanArgs = ['container', 'singleton', 'parentAware', 'dataServices', 'dryRun'];
149
119
 
150
- // Add up to 2 more relevant arguments
151
- relevantArgs.push(...otherArgs.slice(0, 2));
120
+ // Pick the most relevant arguments for each generator type
121
+ const getRelevantArgs = (args, command) => {
122
+ const relevantArgs = [];
123
+
124
+ // Always prefer name-type arguments first
125
+ const nameArgs = ['name', 'componentName', 'modelName', 'workspaceName'].filter(arg => args.includes(arg));
126
+ if (nameArgs.length > 0) {
127
+ relevantArgs.push(nameArgs[0]); // Take the first name argument
128
+ }
129
+
130
+ // Then add project-type arguments
131
+ const projectArgs = ['project', 'componentProject', 'modelProject', 'registrationProject'].filter(arg => args.includes(arg));
132
+ if (projectArgs.length > 0) {
133
+ relevantArgs.push(projectArgs[0]); // Take the first project argument
134
+ }
135
+
136
+ // 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'
142
+ );
143
+
144
+ // Add up to 2 more relevant arguments
145
+ relevantArgs.push(...otherArgs.slice(0, 2));
146
+
147
+ return relevantArgs;
148
+ };
152
149
 
153
- return relevantArgs;
154
- };
155
-
156
- const relevantArgs = getRelevantArgs(args, command);
157
-
158
- relevantArgs.forEach(argName => {
159
- const value = generateExampleValue(argName);
160
- basicArgs.push(`--${argName} ${value}`);
161
- });
162
-
163
- // Create advanced example with boolean flags
164
- const advancedBooleanArgs = [];
165
- booleanArgs.forEach(argName => {
166
- if (args.includes(argName)) {
167
- advancedBooleanArgs.push(`--${argName}`);
150
+ const relevantArgs = getRelevantArgs(args, command);
151
+
152
+ relevantArgs.forEach(argName => {
153
+ const value = generateExampleValue(argName);
154
+ basicArgs.push(`--${argName} ${value}`);
155
+ });
156
+
157
+ // Create advanced example with boolean flags
158
+ const advancedBooleanArgs = [];
159
+ booleanArgs.forEach(argName => {
160
+ if (args.includes(argName)) {
161
+ advancedBooleanArgs.push(`--${argName}`);
162
+ }
163
+ });
164
+
165
+ // Show basic example
166
+ if (basicArgs.length > 0) {
167
+ console.log(` kosui ${command} ${basicArgs.join(' ')}`);
168
+ }
169
+
170
+ // Show advanced example with boolean flags if any exist
171
+ if (basicArgs.length > 0 && advancedBooleanArgs.length > 0) {
172
+ console.log(` kosui ${command} ${basicArgs.slice(0, 2).join(' ')} ${advancedBooleanArgs.join(' ')}`);
173
+ }
174
+
175
+ // If no basic args, show a minimal example
176
+ if (basicArgs.length === 0) {
177
+ console.log(` kosui ${command} [options]`);
168
178
  }
169
- });
170
-
171
- // Show basic example
172
- if (basicArgs.length > 0) {
173
- console.log(` kosui ${command} ${basicArgs.join(' ')}`);
174
179
  }
175
180
 
176
- // Show advanced example with boolean flags if any exist
177
- if (basicArgs.length > 0 && advancedBooleanArgs.length > 0) {
178
- console.log(` kosui ${command} ${basicArgs.slice(0, 2).join(' ')} ${advancedBooleanArgs.join(' ')}`);
181
+ console.log(` kosui ${command} --interactive # Force interactive mode`);
182
+ console.log(` kosui ${command} -i # Force interactive mode (short form)`);
183
+ return;
184
+ }
185
+ }
186
+
187
+ // Also check for no-arguments case (when only global flags like --no-cache are present)
188
+ const earlyMeta = await getGeneratorMetadata(command);
189
+ if (earlyMeta?.namedArguments) {
190
+ const namedArgumentsMap = earlyMeta.namedArguments;
191
+ const providedAnswers = {};
192
+ Object.entries(namedArgumentsMap).forEach(([cliArg, promptName]) => {
193
+ if (cliArg === "interactive") return;
194
+ if (parsedArgs[cliArg] !== undefined) {
195
+ let value = parsedArgs[cliArg];
196
+ if (value === "true" || value === "y" || value === "yes") {
197
+ value = true;
198
+ } else if (value === "false" || value === "n" || value === "no") {
199
+ value = false;
200
+ }
201
+ providedAnswers[promptName] = value;
179
202
  }
203
+ });
204
+ const hasAnyAnswers = Object.keys(providedAnswers).length > 0;
205
+ const forceInteractive = parsedArgs.interactive || parsedArgs.i;
206
+
207
+ // Show help for no-arguments case
208
+ if (!hasAnyAnswers && !forceInteractive) {
209
+ console.log(`--- ${earlyMeta?.name || command} Help ---`);
210
+ console.log(earlyMeta.description || "No description available");
211
+
212
+ if (earlyMeta?.namedArguments) {
213
+ console.log("\nNamed Arguments:");
214
+ Object.entries(earlyMeta.namedArguments).forEach(([cliArg, promptName]) => {
215
+ console.log(` --${cliArg} Maps to prompt: ${promptName}`);
216
+ });
217
+ }
218
+
219
+ console.log("\nExamples:");
180
220
 
181
- // If no basic args, show a minimal example
182
- if (basicArgs.length === 0) {
183
- console.log(` kosui ${command} [options]`);
221
+ // Generate examples dynamically from the generator's named arguments
222
+ if (earlyMeta?.namedArguments) {
223
+ const args = Object.keys(earlyMeta.namedArguments);
224
+
225
+ // Generate example values based on argument names
226
+ const generateExampleValue = (argName) => {
227
+ 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':
253
+ return true;
254
+ case 'dryRun':
255
+ return true;
256
+ default:
257
+ return `my-${argName}`;
258
+ }
259
+ };
260
+
261
+ // Create basic example with most important arguments
262
+ const basicArgs = [];
263
+ const booleanArgs = ['container', 'singleton', 'parentAware', 'dataServices', 'dryRun'];
264
+
265
+ // Pick the most relevant arguments for each generator type
266
+ const getRelevantArgs = (args, command) => {
267
+ const relevantArgs = [];
268
+
269
+ // Always prefer name-type arguments first
270
+ const nameArgs = ['name', 'componentName', 'modelName', 'workspaceName'].filter(arg => args.includes(arg));
271
+ if (nameArgs.length > 0) {
272
+ relevantArgs.push(nameArgs[0]); // Take the first name argument
273
+ }
274
+
275
+ // Then add project-type arguments
276
+ const projectArgs = ['project', 'componentProject', 'modelProject', 'registrationProject'].filter(arg => args.includes(arg));
277
+ if (projectArgs.length > 0) {
278
+ relevantArgs.push(projectArgs[0]); // Take the first project argument
279
+ }
280
+
281
+ // 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'
287
+ );
288
+
289
+ // Add up to 2 more relevant arguments
290
+ relevantArgs.push(...otherArgs.slice(0, 2));
291
+
292
+ return relevantArgs;
293
+ };
294
+
295
+ const relevantArgs = getRelevantArgs(args, command);
296
+
297
+ relevantArgs.forEach(argName => {
298
+ const value = generateExampleValue(argName);
299
+ basicArgs.push(`--${argName} ${value}`);
300
+ });
301
+
302
+ // Create advanced example with boolean flags
303
+ const advancedBooleanArgs = [];
304
+ booleanArgs.forEach(argName => {
305
+ if (args.includes(argName)) {
306
+ advancedBooleanArgs.push(`--${argName}`);
307
+ }
308
+ });
309
+
310
+ // Show basic example
311
+ if (basicArgs.length > 0) {
312
+ console.log(` kosui ${command} ${basicArgs.join(' ')}`);
313
+ }
314
+
315
+ // Show advanced example with boolean flags if any exist
316
+ if (basicArgs.length > 0 && advancedBooleanArgs.length > 0) {
317
+ console.log(` kosui ${command} ${basicArgs.slice(0, 2).join(' ')} ${advancedBooleanArgs.join(' ')}`);
318
+ }
319
+
320
+ // If no basic args, show a minimal example
321
+ if (basicArgs.length === 0) {
322
+ console.log(` kosui ${command} [options]`);
323
+ }
184
324
  }
325
+
326
+ console.log(` kosui ${command} --interactive # Force interactive mode`);
327
+ console.log(` kosui ${command} -i # Force interactive mode (short form)`);
328
+ return;
185
329
  }
186
-
187
- console.log(` kosui ${command} --interactive # Force interactive mode`);
188
- console.log(` kosui ${command} -i # Force interactive mode (short form)`);
189
- return;
190
330
  }
191
331
 
332
+ const plop = await nodePlop(configPath);
333
+
334
+ plop.setActionType("clearCache", (answers, config, plop) => {
335
+ clearCache();
336
+ return "[ok] CLI cache cleared successfully.";
337
+ });
338
+
339
+ const generator = plop.getGenerator(command);
340
+
341
+ if (!generator) {
342
+ console.error(`[kos-cli] Generator "${command}" not found.`);
343
+ process.exit(1);
344
+ }
345
+
346
+ const meta = await getGeneratorMetadata(command);
347
+
348
+
192
349
  if (showBanner) {
193
350
  console.log(`--- Running KOS Generator: ${meta?.name || command} ---`);
194
351
  }
@@ -251,6 +408,130 @@ async function launch() {
251
408
  // Check if user wants to force interactive mode
252
409
  const forceInteractive = parsedArgs.interactive || parsedArgs.i;
253
410
 
411
+ // If no arguments provided and no interactive flag, show help
412
+ if (!hasAnyAnswers && !forceInteractive) {
413
+ console.log(`--- ${meta?.name || command} Help ---`);
414
+ console.log(generator.description || "No description available");
415
+
416
+ if (meta?.namedArguments) {
417
+ console.log("\nNamed Arguments:");
418
+ Object.entries(meta.namedArguments).forEach(([cliArg, promptName]) => {
419
+ console.log(` --${cliArg} Maps to prompt: ${promptName}`);
420
+ });
421
+ }
422
+
423
+ console.log("\nExamples:");
424
+
425
+ // Generate examples dynamically from the generator's named arguments
426
+ if (meta?.namedArguments) {
427
+ const args = Object.keys(meta.namedArguments);
428
+
429
+ // Generate example values based on argument names
430
+ const generateExampleValue = (argName) => {
431
+ 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':
457
+ return true;
458
+ case 'dryRun':
459
+ return true;
460
+ default:
461
+ return `my-${argName}`;
462
+ }
463
+ };
464
+
465
+ // Create basic example with most important arguments
466
+ const basicArgs = [];
467
+ const booleanArgs = ['container', 'singleton', 'parentAware', 'dataServices', 'dryRun'];
468
+
469
+ // Pick the most relevant arguments for each generator type
470
+ const getRelevantArgs = (args, command) => {
471
+ const relevantArgs = [];
472
+
473
+ // Always prefer name-type arguments first
474
+ const nameArgs = ['name', 'componentName', 'modelName', 'workspaceName'].filter(arg => args.includes(arg));
475
+ if (nameArgs.length > 0) {
476
+ relevantArgs.push(nameArgs[0]); // Take the first name argument
477
+ }
478
+
479
+ // Then add project-type arguments
480
+ const projectArgs = ['project', 'componentProject', 'modelProject', 'registrationProject'].filter(arg => args.includes(arg));
481
+ if (projectArgs.length > 0) {
482
+ relevantArgs.push(projectArgs[0]); // Take the first project argument
483
+ }
484
+
485
+ // 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'
491
+ );
492
+
493
+ // Add up to 2 more relevant arguments
494
+ relevantArgs.push(...otherArgs.slice(0, 2));
495
+
496
+ return relevantArgs;
497
+ };
498
+
499
+ const relevantArgs = getRelevantArgs(args, command);
500
+
501
+ relevantArgs.forEach(argName => {
502
+ const value = generateExampleValue(argName);
503
+ basicArgs.push(`--${argName} ${value}`);
504
+ });
505
+
506
+ // Create advanced example with boolean flags
507
+ const advancedBooleanArgs = [];
508
+ booleanArgs.forEach(argName => {
509
+ if (args.includes(argName)) {
510
+ advancedBooleanArgs.push(`--${argName}`);
511
+ }
512
+ });
513
+
514
+ // Show basic example
515
+ if (basicArgs.length > 0) {
516
+ console.log(` kosui ${command} ${basicArgs.join(' ')}`);
517
+ }
518
+
519
+ // Show advanced example with boolean flags if any exist
520
+ if (basicArgs.length > 0 && advancedBooleanArgs.length > 0) {
521
+ console.log(` kosui ${command} ${basicArgs.slice(0, 2).join(' ')} ${advancedBooleanArgs.join(' ')}`);
522
+ }
523
+
524
+ // If no basic args, show a minimal example
525
+ if (basicArgs.length === 0) {
526
+ console.log(` kosui ${command} [options]`);
527
+ }
528
+ }
529
+
530
+ console.log(` kosui ${command} --interactive # Force interactive mode`);
531
+ console.log(` kosui ${command} -i # Force interactive mode (short form)`);
532
+ return;
533
+ }
534
+
254
535
  if (hasAnyAnswers && !forceInteractive) {
255
536
  // Create bypass array for all prompts
256
537
  const bypassArgs = [];
@@ -0,0 +1,80 @@
1
+ // generators/model/add-future.mjs
2
+ import { actionFactory } from "../../utils/action-factory.mjs";
3
+ import { execute } from "../../utils/exec.mjs";
4
+ import { getAllModels } from "../../utils/nx-context.mjs";
5
+ import { DEFAULT_PROMPTS } from "../../utils/prompts.mjs";
6
+
7
+ export const metadata = {
8
+ key: "add-future",
9
+ name: "Add Future Support to Model",
10
+ invalidateCache: true,
11
+ namedArguments: {
12
+ modelName: "modelName",
13
+ project: "modelProject",
14
+ modelProject: "modelProject",
15
+ futureType: "futureType",
16
+ updateServices: "updateServices",
17
+ dryRun: "dryRun",
18
+ interactive: "interactive",
19
+ },
20
+ };
21
+
22
+ export default async function (plop) {
23
+ const allModels = await getAllModels();
24
+ const modelChoices = allModels.map((m) => ({
25
+ name: `${m.model} (${m.project})`,
26
+ value: m.model,
27
+ }));
28
+
29
+ plop.setActionType("addFutureToModel", async function (answers) {
30
+ const modelProject = allModels.find(
31
+ (m) => m.model === answers.modelName
32
+ )?.project;
33
+
34
+ const command = `npx nx generate @kosdev-code/kos-nx-plugin:kos-add-future-to-model \\
35
+ --modelName=${answers.modelName} \\
36
+ --modelProject=${modelProject} \\
37
+ --futureType=${answers.futureType} \\
38
+ --updateServices=${!!answers.updateServices} \\
39
+ --no-interactive ${answers.dryRun ? "--dryRun" : ""} --verbose`;
40
+
41
+ try {
42
+ await execute(command);
43
+ } catch (error) {
44
+ throw new Error(error);
45
+ }
46
+
47
+ return `Future support (${answers.futureType}) added to model ${answers.modelName} in ${modelProject}`;
48
+ });
49
+
50
+ plop.setGenerator("add-future", {
51
+ description: "Add Future support to an existing KOS Model",
52
+ prompts: [
53
+ ...DEFAULT_PROMPTS,
54
+ {
55
+ type: "list",
56
+ name: "modelName",
57
+ message: "Which model do you want to add Future support to?",
58
+ choices: modelChoices,
59
+ },
60
+ {
61
+ type: "list",
62
+ name: "futureType",
63
+ message: "What type of Future support?",
64
+ choices: [
65
+ { name: "Minimal (external access only)", value: "minimal" },
66
+ { name: "Complete (internal + external access)", value: "complete" },
67
+ ],
68
+ default: "minimal",
69
+ },
70
+ {
71
+ type: "confirm",
72
+ name: "updateServices",
73
+ message: "Update/create services file with Future operations?",
74
+ default: true,
75
+ },
76
+ ],
77
+
78
+ actions: actionFactory("addFutureToModel", metadata),
79
+ });
80
+ }
@@ -6,7 +6,11 @@ import {
6
6
  getLibraryProjects,
7
7
  getProjectDetails,
8
8
  } from "../../utils/nx-context.mjs";
9
- import { DEFAULT_PROMPTS, MODEL_PROMPTS } from "../../utils/prompts.mjs";
9
+ import {
10
+ COMPANION_PROMPTS,
11
+ DEFAULT_PROMPTS,
12
+ MODEL_PROMPTS,
13
+ } from "../../utils/prompts.mjs";
10
14
  import { required } from "../../utils/validators.mjs";
11
15
 
12
16
  export const metadata = {
@@ -18,13 +22,14 @@ export const metadata = {
18
22
  project: "modelProject",
19
23
  modelProject: "modelProject",
20
24
  companionParent: "companionParent",
25
+ companionPattern: "companionPattern",
21
26
  container: "container",
22
27
  parentAware: "parentAware",
23
28
  singleton: "singleton",
24
29
  dataServices: "dataServices",
25
30
  dryRun: "dryRun",
26
- interactive: "interactive"
27
- }
31
+ interactive: "interactive",
32
+ },
28
33
  };
29
34
 
30
35
  export default async function (plop) {
@@ -52,6 +57,7 @@ export default async function (plop) {
52
57
  --companion=true \
53
58
  --companionModel=${answers.companionParent} \
54
59
  --companionModelProject=${companionProject} \
60
+ --companionPattern=${answers.companionPattern} \
55
61
  --no-interactive ${answers.dryRun ? "--dryRun" : ""} --verbose`;
56
62
 
57
63
  try {
@@ -84,6 +90,7 @@ export default async function (plop) {
84
90
  message: "Select the companion parent model",
85
91
  choices: modelChoices,
86
92
  },
93
+ ...COMPANION_PROMPTS,
87
94
  ...MODEL_PROMPTS,
88
95
  ],
89
96
  actions: actionFactory("createCompanionModel", metadata),
@@ -2,7 +2,6 @@
2
2
  import { actionFactory } from "../../utils/action-factory.mjs";
3
3
  import { execute } from "../../utils/exec.mjs";
4
4
  import {
5
- getAllProjects,
6
5
  getLibraryProjects,
7
6
  getProjectDetails,
8
7
  } from "../../utils/nx-context.mjs";
@@ -22,13 +21,13 @@ export const metadata = {
22
21
  parentAware: "parentAware",
23
22
  singleton: "singleton",
24
23
  dataServices: "dataServices",
24
+ futureAware: "futureAware",
25
25
  dryRun: "dryRun",
26
26
  interactive: "interactive"
27
27
  }
28
28
  };
29
29
 
30
30
  export default async function (plop) {
31
- const allProjects = await getAllProjects();
32
31
  const libraryProjects = await getLibraryProjects();
33
32
 
34
33
  plop.setActionType("createModel", async function (answers) {
@@ -41,6 +40,7 @@ export default async function (plop) {
41
40
  --dataServices=${!!answers.dataServices} \
42
41
  --singleton=${!!answers.singleton} \
43
42
  --parentAware=${!!answers.parentAware} \
43
+ --futureAware=${answers.futureAware || 'none'} \
44
44
  --no-interactive ${answers.dryRun ? "--dryRun" : ""} --verbose`;
45
45
 
46
46
  try {
@@ -2,6 +2,7 @@
2
2
  import { detectWorkspace } from "./utils/nx-context.mjs";
3
3
 
4
4
  // Generator registration
5
+ import registerAddFuture from "./generators/model/add-future.mjs";
5
6
  import registerCompanion from "./generators/model/companion.mjs";
6
7
  import registerContainer from "./generators/model/container.mjs";
7
8
  import registerContext from "./generators/model/context.mjs";
@@ -40,6 +41,7 @@ export default async function (plop) {
40
41
  }
41
42
 
42
43
  await registerKosModel(plop);
44
+ await registerAddFuture(plop);
43
45
  await registerHook(plop);
44
46
  await registerCompanion(plop);
45
47
  await registerContainer(plop);
@@ -42,4 +42,34 @@ export const MODEL_PROMPTS = [
42
42
  message: "Create data services?",
43
43
  default: true,
44
44
  },
45
+ {
46
+ type: "list",
47
+ name: "futureAware",
48
+ message: "Include Future-aware capabilities?",
49
+ choices: [
50
+ { name: "No Future support", value: "none" },
51
+ { name: "Minimal (external access only)", value: "minimal" },
52
+ { name: "Complete (internal + external access)", value: "complete" },
53
+ ],
54
+ default: "none",
55
+ },
56
+ ];
57
+
58
+ export const COMPANION_PROMPTS = [
59
+ {
60
+ type: "list",
61
+ name: "companionPattern",
62
+ message: "Which companion pattern to use?",
63
+ choices: [
64
+ {
65
+ name: "Composition (recommended) - explicit parent access via this.parent",
66
+ value: "composition",
67
+ },
68
+ {
69
+ name: "Decorator - transparent parent access, drop-in replacement",
70
+ value: "decorator",
71
+ },
72
+ ],
73
+ default: "composition",
74
+ },
45
75
  ];