@kosdev-code/kos-ui-cli 2.1.39 → 3.0.1

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.
@@ -1,10 +1,1425 @@
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";
1
+ // src/lib/utils/action-factory.mjs
2
+ var actionFactory = (action, metadata2) => {
3
+ return () => {
4
+ const _actions = [{ type: action }];
5
+ if (metadata2.invalidateCache) {
6
+ _actions.push({ type: "clearCache" });
7
+ }
8
+ return _actions;
9
+ };
10
+ };
11
+
12
+ // src/lib/utils/nx-context.mjs
13
+ import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync } from "fs";
14
+ import path6 from "path";
15
+ import { fileURLToPath } from "url";
16
+
17
+ // src/lib/utils/cache.mjs
18
+ import fs from "fs";
19
+ import path from "path";
20
+ var CACHE_PATH = path.resolve(".nx/cli-cache.json");
21
+ var CACHE_TTL = 600 * 1e3;
22
+ var ARGS = process.argv;
23
+ var DISABLE_CACHE = process.env.DISABLE_CACHE === "true" || process.env.REFRESH === "true";
24
+ var _cache = {};
25
+ var _loaded = false;
26
+ function ensureCacheDir() {
27
+ const dir = path.dirname(CACHE_PATH);
28
+ if (!fs.existsSync(dir))
29
+ fs.mkdirSync(dir, { recursive: true });
30
+ }
31
+ function loadCacheFromDisk() {
32
+ if (_loaded)
33
+ return;
34
+ _loaded = true;
35
+ try {
36
+ if (fs.existsSync(CACHE_PATH)) {
37
+ const data = fs.readFileSync(CACHE_PATH, "utf-8");
38
+ _cache = JSON.parse(data);
39
+ }
40
+ } catch (err) {
41
+ console.warn("Failed to load CLI cache:", err);
42
+ _cache = {};
43
+ }
44
+ }
45
+ function saveCacheToDisk() {
46
+ try {
47
+ ensureCacheDir();
48
+ fs.writeFileSync(CACHE_PATH, JSON.stringify(_cache, null, 2));
49
+ } catch (err) {
50
+ console.warn("Failed to save CLI cache:", err);
51
+ }
52
+ }
53
+ function isFresh(entry, ttl = CACHE_TTL) {
54
+ if (!entry || !entry.timestamp)
55
+ return false;
56
+ return Date.now() - entry.timestamp < ttl;
57
+ }
58
+ function getCached(key, ttl = CACHE_TTL) {
59
+ if (DISABLE_CACHE)
60
+ return null;
61
+ loadCacheFromDisk();
62
+ const entry = _cache[key];
63
+ if (isFresh(entry, ttl))
64
+ return entry.data;
65
+ return null;
66
+ }
67
+ function setCached(key, data) {
68
+ loadCacheFromDisk();
69
+ _cache[key] = {
70
+ data,
71
+ timestamp: Date.now()
72
+ };
73
+ saveCacheToDisk();
74
+ }
75
+
76
+ // ../kos-codegen-core/src/lib/codegen-filesystem.ts
77
+ import * as fs2 from "fs";
78
+ import * as path2 from "path";
79
+ var DirectFileSystem = class {
80
+ root;
81
+ constructor(workspaceRoot) {
82
+ this.root = path2.resolve(workspaceRoot);
83
+ }
84
+ read(filePath) {
85
+ const abs = this.resolve(filePath);
86
+ try {
87
+ return fs2.readFileSync(abs, "utf-8");
88
+ } catch {
89
+ return null;
90
+ }
91
+ }
92
+ write(filePath, content) {
93
+ const abs = this.resolve(filePath);
94
+ fs2.mkdirSync(path2.dirname(abs), { recursive: true });
95
+ fs2.writeFileSync(abs, content, "utf-8");
96
+ }
97
+ exists(filePath) {
98
+ return fs2.existsSync(this.resolve(filePath));
99
+ }
100
+ delete(filePath) {
101
+ const abs = this.resolve(filePath);
102
+ try {
103
+ fs2.unlinkSync(abs);
104
+ } catch {
105
+ }
106
+ }
107
+ listFiles(dirPath) {
108
+ const abs = this.resolve(dirPath);
109
+ if (!fs2.existsSync(abs)) {
110
+ return [];
111
+ }
112
+ return this.walkDir(abs).map((file) => path2.relative(this.root, file));
113
+ }
114
+ resolve(filePath) {
115
+ if (path2.isAbsolute(filePath)) {
116
+ return filePath;
117
+ }
118
+ return path2.join(this.root, filePath);
119
+ }
120
+ walkDir(dir) {
121
+ const results = [];
122
+ const entries = fs2.readdirSync(dir, { withFileTypes: true });
123
+ for (const entry of entries) {
124
+ const full = path2.join(dir, entry.name);
125
+ if (entry.isDirectory()) {
126
+ results.push(...this.walkDir(full));
127
+ } else {
128
+ results.push(full);
129
+ }
130
+ }
131
+ return results;
132
+ }
133
+ };
134
+
135
+ // ../kos-codegen-core/src/lib/generate-files.ts
136
+ import * as ejs from "ejs";
137
+
138
+ // ../kos-codegen-core/src/lib/logger.ts
139
+ var noopLogger = {
140
+ debug: () => {
141
+ },
142
+ info: () => {
143
+ },
144
+ warn: () => {
145
+ },
146
+ error: () => {
147
+ }
148
+ };
149
+ var activeLogger = noopLogger;
150
+ function getCodegenLogger() {
151
+ return activeLogger;
152
+ }
153
+
154
+ // ../kos-codegen-core/src/lib/project-discovery.ts
155
+ import * as fs3 from "fs";
156
+ import * as path3 from "path";
157
+ import fg from "fast-glob";
158
+ function discoverProjects(workspaceRoot) {
159
+ const logger = getCodegenLogger();
160
+ const projects = /* @__PURE__ */ new Map();
161
+ const projectJsonPaths = fg.sync("**/project.json", {
162
+ cwd: workspaceRoot,
163
+ ignore: ["**/node_modules/**", "**/dist/**", "**/.git/**"],
164
+ absolute: false
165
+ });
166
+ for (const relPath of projectJsonPaths) {
167
+ const absPath = path3.join(workspaceRoot, relPath);
168
+ try {
169
+ const raw = fs3.readFileSync(absPath, "utf-8");
170
+ const json = JSON.parse(raw);
171
+ const projectRoot = path3.dirname(relPath);
172
+ const name = json.name ?? path3.basename(projectRoot);
173
+ const config = {
174
+ name,
175
+ root: projectRoot,
176
+ sourceRoot: json.sourceRoot ?? path3.join(projectRoot, "src"),
177
+ projectType: json.projectType,
178
+ targets: json.targets,
179
+ tags: json.tags
180
+ };
181
+ projects.set(name, config);
182
+ logger.debug(`Discovered project: ${name} at ${projectRoot}`);
183
+ } catch (err) {
184
+ logger.warn(`Failed to parse ${absPath}: ${err}`);
185
+ }
186
+ }
187
+ logger.info(`Discovered ${projects.size} projects`);
188
+ return projects;
189
+ }
190
+ function findProjectByName(workspaceRoot, projectName, projects) {
191
+ const map = projects ?? discoverProjects(workspaceRoot);
192
+ return map.get(projectName);
193
+ }
194
+
195
+ // ../kos-codegen-core/src/lib/format-files.ts
196
+ import prettier from "prettier";
197
+
198
+ // ../kos-codegen-core/src/lib/name-utils.ts
199
+ function dashCase(input) {
200
+ return input.replace(/\s+/g, "-").replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
201
+ }
202
+ function camelCase(input) {
203
+ if (input.length === 0)
204
+ return "";
205
+ const words = input.split(/-|\s+/);
206
+ if (words.length > 0 && words[0].length > 0) {
207
+ words[0] = words[0].charAt(0).toLowerCase() + words[0].slice(1);
208
+ }
209
+ for (let i = 1; i < words.length; i++) {
210
+ words[i] = words[i].charAt(0).toUpperCase() + words[i].slice(1);
211
+ }
212
+ return words.join("");
213
+ }
214
+ function pascalCase(input) {
215
+ if (input.length === 0)
216
+ return "";
217
+ const cc = camelCase(input);
218
+ return cc[0].toUpperCase() + cc.slice(1);
219
+ }
220
+ function properCase(input) {
221
+ const words = input.toLowerCase().replaceAll("-", " ").split(" ").filter(Boolean);
222
+ for (let i = 0; i < words.length; i++) {
223
+ words[i] = words[i][0].toUpperCase() + words[i].slice(1);
224
+ }
225
+ return words.join("");
226
+ }
227
+ function constantCase(input) {
228
+ if (input.length === 0)
229
+ return "";
230
+ return input.toUpperCase().split(/[\s-]+/).filter(Boolean).join("_");
231
+ }
232
+
233
+ // ../kos-codegen-core/src/lib/normalize-values.ts
234
+ var normalizeValue = (optionsName, value) => ({
235
+ [`${camelCase(optionsName)}CamelCase`]: camelCase(value),
236
+ [`${camelCase(optionsName)}ConstantCase`]: constantCase(value),
237
+ [`${camelCase(optionsName)}DashCase`]: dashCase(value),
238
+ [`${camelCase(optionsName)}PascalCase`]: pascalCase(value),
239
+ [`${camelCase(optionsName)}ProperCase`]: properCase(value),
240
+ [`${camelCase(optionsName)}LowerCase`]: value.toLowerCase(),
241
+ [`${optionsName}`]: value
242
+ });
243
+ var normalizeAllValues = (options) => {
244
+ let normalizedValues = {};
245
+ for (const key in options) {
246
+ if (Object.prototype.hasOwnProperty.call(options, key)) {
247
+ const element = options[key];
248
+ const newOptions = typeof element !== "string" || element === "" ? { [key]: element } : normalizeValue(key, element);
249
+ normalizedValues = {
250
+ ...normalizedValues,
251
+ ...newOptions
252
+ };
253
+ }
254
+ }
255
+ return normalizedValues;
256
+ };
257
+
258
+ // ../kos-codegen-core/src/lib/kos-config.ts
259
+ import * as path4 from "path";
260
+ function getKosProjectConfiguration(codegenFs, projectName, projects) {
261
+ const project = findProjectByName(codegenFs.root, projectName, projects);
262
+ if (!project)
263
+ return void 0;
264
+ const configPath = path4.join(project.root, ".kos.json");
265
+ if (!codegenFs.exists(configPath)) {
266
+ const defaultConfig = {
267
+ name: `${dashCase(projectName)}-model`,
268
+ type: "kos.model",
269
+ version: "0.1.0",
270
+ models: {},
271
+ generator: { defaults: { model: { folder: "" } } }
272
+ };
273
+ codegenFs.write(configPath, JSON.stringify(defaultConfig, null, 2));
274
+ }
275
+ const content = codegenFs.read(configPath);
276
+ return content ? JSON.parse(content) : void 0;
277
+ }
278
+
279
+ // ../kos-codegen-core/src/lib/generators/update-model-index.ts
280
+ import * as ts from "typescript";
281
+
282
+ // ../kos-codegen-core/src/lib/generators/add-future-to-model/normalize-options.ts
283
+ import * as path5 from "path";
284
+ function normalizeAddFutureOptions(codegenFs, options, projects) {
285
+ const projectConfiguration = findProjectByName(
286
+ codegenFs.root,
287
+ options.modelProject,
288
+ projects
289
+ );
290
+ if (!projectConfiguration) {
291
+ throw new Error(
292
+ `Project not found: ${options.modelProject}. Ensure a project.json exists for this project.`
293
+ );
294
+ }
295
+ const kosConfig = getKosProjectConfiguration(
296
+ codegenFs,
297
+ options.modelProject,
298
+ projects
299
+ );
300
+ const internal = !!kosConfig?.generator?.internal;
301
+ const normalizedValues = normalizeAllValues({
302
+ modelName: options.modelName
303
+ });
304
+ const nameDashCase = normalizedValues.modelNameDashCase;
305
+ const nameProperCase = normalizedValues.modelNameProperCase;
306
+ const nameCamelCase = normalizedValues.modelNameCamelCase;
307
+ const namePascalCase = normalizedValues.modelNamePascalCase;
308
+ const nameConstantCase = normalizedValues.modelNameConstantCase;
309
+ const nameLowerCase = normalizedValues.modelNameLowerCase;
310
+ const projectRoot = projectConfiguration.root;
311
+ const sourceRoot = projectConfiguration.sourceRoot || path5.join(projectRoot, "src");
312
+ const modelLocation = kosConfig?.generator?.defaults?.model?.folder || "";
313
+ const modelDirectory = path5.join(sourceRoot, modelLocation, nameDashCase);
314
+ const modelFilePath = path5.join(modelDirectory, `${nameDashCase}-model.ts`);
315
+ const servicesDirectory = path5.join(modelDirectory, "services");
316
+ const servicesFilePath = codegenFs.exists(servicesDirectory) ? path5.join(servicesDirectory, `${nameDashCase}-services.ts`) : void 0;
317
+ const registrationFilePath = path5.join(
318
+ modelDirectory,
319
+ `${nameDashCase}-registration.ts`
320
+ );
321
+ return {
322
+ ...options,
323
+ nameDashCase,
324
+ nameProperCase,
325
+ nameCamelCase,
326
+ namePascalCase,
327
+ nameConstantCase,
328
+ nameLowerCase,
329
+ projectRoot,
330
+ sourceRoot,
331
+ modelFilePath,
332
+ servicesFilePath,
333
+ registrationFilePath: codegenFs.exists(registrationFilePath) ? registrationFilePath : void 0,
334
+ internal
335
+ };
336
+ }
6
337
 
7
- export const metadata = {
338
+ // ../kos-codegen-core/src/lib/generators/add-future-to-model/model-transformer.ts
339
+ var ModelFileTransformer = class {
340
+ constructor(codegenFs, options) {
341
+ this.codegenFs = codegenFs;
342
+ this.options = options;
343
+ }
344
+ transform() {
345
+ const { modelFilePath } = this.options;
346
+ if (!this.codegenFs.exists(modelFilePath)) {
347
+ throw new Error(`Model file not found: ${modelFilePath}`);
348
+ }
349
+ let content = this.codegenFs.read(modelFilePath);
350
+ content = this.addESLintDisable(content);
351
+ content = this.addImports(content);
352
+ content = this.addServiceImport(content);
353
+ content = this.addInterfaceMerging(content);
354
+ content = this.addDecorator(content);
355
+ content = this.updatePublicType(content);
356
+ content = this.removeLegacySetup(content);
357
+ content = this.addFutureMethod(content);
358
+ if (this.options.futureType === "complete") {
359
+ content = this.addOnFutureUpdateMethod(content);
360
+ }
361
+ this.codegenFs.write(modelFilePath, content);
362
+ }
363
+ addESLintDisable(content) {
364
+ if (content.includes("@typescript-eslint/no-unsafe-declaration-merging")) {
365
+ return content;
366
+ }
367
+ const eslintDisable = "/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */\n";
368
+ return eslintDisable + content;
369
+ }
370
+ addImports(content) {
371
+ const { internal, futureType } = this.options;
372
+ const isComplete = futureType === "complete";
373
+ const kosModelImportRegex = internal ? /import\s*{\s*([^}]*kosModel[^}]*)\s*}\s*from\s*"\.\.\/\.\.\/\.\.\/core\/core\/decorators"/ : /import\s*{\s*([^}]*kosModel[^}]*)\s*}\s*from\s*"@kosdev-code\/kos-ui-sdk"/;
374
+ const kosModelMatch = content.match(kosModelImportRegex);
375
+ if (kosModelMatch) {
376
+ const existingImportsStr = kosModelMatch[1] || "";
377
+ const newImports = [
378
+ "kosFuture",
379
+ "kosFutureAware",
380
+ isComplete ? "KosFutureAwareFull" : "KosFutureAwareMinimal"
381
+ ];
382
+ const existingImports = existingImportsStr.split(",").map((s) => s.trim()).filter(Boolean);
383
+ const importsToRemove = [
384
+ "setupCompleteFutureSupport",
385
+ "setupMinimalFutureSupport"
386
+ ];
387
+ const cleanedImports = existingImports.filter(
388
+ (imp) => !importsToRemove.includes(imp)
389
+ );
390
+ const importsToAdd = newImports.filter(
391
+ (imp) => !cleanedImports.includes(imp)
392
+ );
393
+ const allImports = [...cleanedImports, ...importsToAdd];
394
+ const newImportLine = internal ? `import { ${allImports.join(
395
+ ", "
396
+ )} } from "../../../core/core/decorators"` : `import { ${allImports.join(", ")} } from "@kosdev-code/kos-ui-sdk"`;
397
+ content = content.replace(kosModelImportRegex, newImportLine);
398
+ }
399
+ const typeImportsBase = internal ? "../../../models/types/future-interfaces" : "@kosdev-code/kos-ui-sdk";
400
+ const futureTypeImports = ["ExternalFutureInterface", "IFutureModel"];
401
+ const typeImportRegex = internal ? /import type {([^}]*)} from "\.\.\/\.\.\/\.\.\/models\/types\/future-interfaces"/ : /import type {([^}]*)} from "@kosdev-code\/kos-ui-sdk"/;
402
+ const typeImportMatch = content.match(typeImportRegex);
403
+ if (typeImportMatch) {
404
+ const existingTypes = typeImportMatch[1] || "";
405
+ const existingTypesList = existingTypes.split(",").map((s) => s.trim()).filter(Boolean);
406
+ const typesToRemove = [
407
+ "FutureAwareContainer",
408
+ "FutureHandlerContainer",
409
+ "FutureStateAccessor",
410
+ "FutureUpdateHandler"
411
+ ];
412
+ const cleanedTypes = existingTypesList.filter(
413
+ (type) => !typesToRemove.includes(type)
414
+ );
415
+ const typesToAdd = futureTypeImports.filter(
416
+ (type) => !cleanedTypes.includes(type)
417
+ );
418
+ const allTypes = [...cleanedTypes, ...typesToAdd].join(", ");
419
+ const newTypeImport = `import type { ${allTypes} } from "${typeImportsBase}"`;
420
+ content = content.replace(typeImportRegex, newTypeImport);
421
+ } else {
422
+ const importLines = content.split("\n");
423
+ const lastImportIndex = importLines.findLastIndex(
424
+ (line) => line.trim().startsWith("import")
425
+ );
426
+ if (lastImportIndex >= 0) {
427
+ const newTypeImport = `import type { ${futureTypeImports.join(
428
+ ", "
429
+ )} } from "${typeImportsBase}";`;
430
+ importLines.splice(lastImportIndex + 1, 0, newTypeImport);
431
+ content = importLines.join("\n");
432
+ }
433
+ }
434
+ return content;
435
+ }
436
+ addServiceImport(content) {
437
+ const { nameProperCase, updateServices } = this.options;
438
+ if (!updateServices) {
439
+ return content;
440
+ }
441
+ if (content.includes(`${nameProperCase}OperationProgress`)) {
442
+ return content;
443
+ }
444
+ const servicesImportRegex = /import\s*{([^}]*)}\s*from\s*["']\.\/services["'];?/s;
445
+ const servicesMatch = content.match(servicesImportRegex);
446
+ if (servicesMatch) {
447
+ const existingImports = servicesMatch[1];
448
+ const cleanedImports = existingImports.split(",").map((s) => s.trim()).filter(Boolean);
449
+ cleanedImports.push(`${nameProperCase}OperationProgress`);
450
+ const newImport = `import { ${cleanedImports.join(
451
+ ", "
452
+ )} } from "./services";`;
453
+ content = content.replace(servicesImportRegex, newImport);
454
+ } else {
455
+ const typesImportRegex = /import\s+type\s+{[^}]*}\s+from\s+["']\.\/types["'];?/;
456
+ const typesMatch = content.match(typesImportRegex);
457
+ if (typesMatch) {
458
+ const newImport = `
459
+ import type { ${nameProperCase}OperationProgress } from "./services";`;
460
+ content = content.replace(typesMatch[0], typesMatch[0] + newImport);
461
+ }
462
+ }
463
+ return content;
464
+ }
465
+ addInterfaceMerging(content) {
466
+ const { nameProperCase, futureType } = this.options;
467
+ const isComplete = futureType === "complete";
468
+ const interfaceType = isComplete ? "KosFutureAwareFull" : "KosFutureAwareMinimal";
469
+ const progressType = this.options.updateServices ? `${nameProperCase}OperationProgress` : "Record<string, unknown>";
470
+ const classRegex = new RegExp(
471
+ `(@kosModel[^\\n]*\\n)([^\\n]*export\\s+class\\s+${nameProperCase}ModelImpl)`,
472
+ "m"
473
+ );
474
+ const classMatch = content.match(classRegex);
475
+ if (classMatch) {
476
+ const interfaceRegex = new RegExp(
477
+ `interface\\s+${nameProperCase}ModelImpl\\s+extends`
478
+ );
479
+ if (!content.match(interfaceRegex)) {
480
+ const interfaceMerging = `
481
+ // Interface merging for Future Container type safety
482
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
483
+ export interface ${nameProperCase}ModelImpl extends ${interfaceType}<${progressType}> {}
484
+
485
+ `;
486
+ content = content.replace(
487
+ classMatch[0],
488
+ interfaceMerging + classMatch[0]
489
+ );
490
+ }
491
+ }
492
+ return content;
493
+ }
494
+ addDecorator(content) {
495
+ const { nameProperCase, futureType } = this.options;
496
+ const isComplete = futureType === "complete";
497
+ const classRegex = new RegExp(
498
+ `(@kosModel[^\\n]*\\n)((?:@[^\\n]*\\n)*)([^\\n]*export\\s+class\\s+${nameProperCase}ModelImpl)`,
499
+ "m"
500
+ );
501
+ const classMatch = content.match(classRegex);
502
+ if (classMatch) {
503
+ if (!classMatch[2].includes("@kosFutureAware")) {
504
+ const decoratorOptions = isComplete ? "" : "{ mode: 'minimal' }";
505
+ const futureDecorator = `@kosFutureAware(${decoratorOptions})
506
+ `;
507
+ content = content.replace(
508
+ classMatch[0],
509
+ classMatch[1] + classMatch[2] + futureDecorator + classMatch[3]
510
+ );
511
+ }
512
+ }
513
+ return content;
514
+ }
515
+ updatePublicType(content) {
516
+ const { nameProperCase, updateServices } = this.options;
517
+ const progressType = updateServices ? `${nameProperCase}OperationProgress` : "Record<string, unknown>";
518
+ const typeRegex = new RegExp(
519
+ `export\\s+type\\s+${nameProperCase}Model\\s*=\\s*PublicModelInterface<${nameProperCase}ModelImpl>([^;]*);`,
520
+ "s"
521
+ );
522
+ const typeMatch = content.match(typeRegex);
523
+ if (typeMatch) {
524
+ if (!typeMatch[1].includes("ExternalFutureInterface")) {
525
+ const newType = `export type ${nameProperCase}Model = PublicModelInterface<${nameProperCase}ModelImpl> & ExternalFutureInterface<${progressType}>;`;
526
+ content = content.replace(typeMatch[0], newType);
527
+ }
528
+ }
529
+ return content;
530
+ }
531
+ removeLegacySetup(content) {
532
+ const setupRegex = /\s*setup(Complete|Minimal)FutureSupport\(this\);?\s*/g;
533
+ content = content.replace(setupRegex, "");
534
+ const propertyRegex = /\s*(public|private|protected)?\s*(declare\s+)?futureHandler[!?]?:\s*FutureAwareContainer[^;]*;\s*/g;
535
+ content = content.replace(propertyRegex, "");
536
+ const futurePropertyRegex = /\s*(public|private|protected)?\s*(declare\s+)?future\??:\s*IFutureModel[^;]*;\s*/g;
537
+ content = content.replace(futurePropertyRegex, "");
538
+ const implementsRegex = new RegExp(
539
+ `(implements\\s+[^{]*?)\\s*,?\\s*(FutureUpdateHandler|FutureHandlerContainer|FutureStateAccessor)`,
540
+ "g"
541
+ );
542
+ content = content.replace(implementsRegex, "$1");
543
+ content = content.replace(/,\s*,/g, ",");
544
+ content = content.replace(/implements\s*,/g, "implements");
545
+ return content;
546
+ }
547
+ addFutureMethod(content) {
548
+ const { nameProperCase } = this.options;
549
+ if (content.includes("@kosFuture()")) {
550
+ return content;
551
+ }
552
+ const classRegex = new RegExp(
553
+ `class\\s+${nameProperCase}ModelImpl[^{]*{([\\s\\S]*)}\\s*$`,
554
+ "m"
555
+ );
556
+ const classMatch = content.match(classRegex);
557
+ if (classMatch) {
558
+ const methodCode = `
559
+ /**
560
+ * Placeholder method for Future operations
561
+ * Replace this with your actual long-running operation
562
+ */
563
+ @kosFuture()
564
+ async performLongRunningOperation(): Promise<void> {
565
+ // TODO: Implement your long-running operation here
566
+ // This method should use a service that returns a Future for progress tracking
567
+
568
+ this.logger.debug(\`Starting long-running operation for \${this.id}\`);
569
+
570
+ // Example implementation pattern using services:
571
+ // import { perform${nameProperCase}Operation } from './services';
572
+ //
573
+ // const future = await perform${nameProperCase}Operation();
574
+ // return this.futureHandler.setFuture(future);
575
+
576
+ // Placeholder that doesn't actually do anything
577
+ await new Promise(resolve => setTimeout(resolve, 1000));
578
+
579
+ this.logger.debug(\`Completed long-running operation for \${this.id}\`);
580
+ }
581
+ `;
582
+ const classContent = classMatch[1];
583
+ const lastBraceIndex = classContent.lastIndexOf("}");
584
+ if (lastBraceIndex >= 0) {
585
+ const updatedContent = classContent.slice(0, lastBraceIndex) + methodCode + classContent.slice(lastBraceIndex);
586
+ content = content.replace(
587
+ classMatch[0],
588
+ `class ${nameProperCase}ModelImpl${classMatch[0].match(/[^{]*/)?.[0]}{${updatedContent}}`
589
+ );
590
+ }
591
+ }
592
+ return content;
593
+ }
594
+ addOnFutureUpdateMethod(content) {
595
+ const { nameProperCase, updateServices } = this.options;
596
+ const progressType = updateServices ? `${nameProperCase}OperationProgress` : "Record<string, unknown>";
597
+ if (content.includes("onFutureUpdate")) {
598
+ return content;
599
+ }
600
+ const futureMethodRegex = /@kosFuture\(\)[^}]*}/s;
601
+ const futureMethodMatch = content.match(futureMethodRegex);
602
+ if (futureMethodMatch) {
603
+ const methodCode = `
604
+
605
+ /**
606
+ * Optional: Custom Future update handling
607
+ * Called whenever the Future state changes (progress, status, completion, etc.)
608
+ */
609
+ onFutureUpdate?(update: IFutureModel<${progressType}>): void {
610
+ // Add custom Future update logic here
611
+ // Examples:
612
+ // - Log progress milestones
613
+ // - Update derived state based on progress
614
+ // - Handle specific error conditions
615
+ // - Trigger notifications at certain thresholds
616
+
617
+ this.logger.debug(\`Future update for \${this.id}:\`, {
618
+ progress: update.progress,
619
+ status: update.status,
620
+ endState: update.endState,
621
+ clientData: update.clientData
622
+ });
623
+ }`;
624
+ content = content.replace(
625
+ futureMethodMatch[0],
626
+ futureMethodMatch[0] + methodCode
627
+ );
628
+ }
629
+ return content;
630
+ }
631
+ };
632
+
633
+ // ../kos-codegen-core/src/lib/generators/add-future-to-model/service-transformer.ts
634
+ var ServiceFileTransformer = class {
635
+ constructor(codegenFs, options) {
636
+ this.codegenFs = codegenFs;
637
+ this.options = options;
638
+ }
639
+ transform() {
640
+ const { servicesFilePath } = this.options;
641
+ if (!servicesFilePath || !this.codegenFs.exists(servicesFilePath)) {
642
+ this.createServicesFile();
643
+ return;
644
+ }
645
+ let content = this.codegenFs.read(servicesFilePath);
646
+ content = this.addFutureImports(content);
647
+ content = this.addFutureService(content);
648
+ content = this.addProgressTypes(content);
649
+ this.codegenFs.write(servicesFilePath, content);
650
+ }
651
+ createServicesFile() {
652
+ const { servicesFilePath, nameProperCase, nameDashCase, nameLowerCase } = this.options;
653
+ if (!servicesFilePath) {
654
+ return;
655
+ }
656
+ const content = `import {
657
+ KosLog,
658
+ type ClientResponse,
659
+ type DeepRequired,
660
+ type ElementType,
661
+ type ServiceResponse,
662
+ type FutureResponse
663
+ } from '@kosdev-code/kos-ui-sdk';
664
+
665
+ import API, { type KosApi, type ApiPath } from '../../../utils/service';
666
+
667
+ const log = KosLog.createLogger({name: "${nameDashCase}-service", group: "Services"});
668
+
669
+ const SERVICE_PATH: ApiPath = "ENTER_SERVICE_PATH"
670
+ export type ${nameProperCase}ClientResponse = ClientResponse<
671
+ KosApi,
672
+ typeof SERVICE_PATH,
673
+ 'get'
674
+ >;
675
+ export type ${nameProperCase}Response = DeepRequired<${nameProperCase}ClientResponse>;
676
+
677
+ /**
678
+ * @category Service
679
+ * Retrieves the initial ${nameLowerCase} data.
680
+ */
681
+ export const get${nameProperCase} = async (): Promise<ServiceResponse<${nameProperCase}Response>> => {
682
+ log.debug('sending GET for ${nameLowerCase}');
683
+ return await API.get(SERVICE_PATH);
684
+ };
685
+
686
+ /**
687
+ * @category Service - Future Operation
688
+ * Placeholder for a long-running operation that returns a Future for progress tracking
689
+ *
690
+ * Replace this with your actual long-running service operation
691
+ */
692
+ export const perform${nameProperCase}Operation = async (): Promise<FutureResponse> => {
693
+ // TODO: Implement your long-running service operation here
694
+ // This should return a Future that can be tracked for progress
695
+
696
+ log.debug('starting long-running ${nameLowerCase} operation');
697
+
698
+ // Example pattern:
699
+ // return API.post(OPERATION_SERVICE_PATH, {
700
+ // // operation parameters
701
+ // });
702
+
703
+ // Placeholder - replace with actual implementation
704
+ throw new Error('perform${nameProperCase}Operation not yet implemented');
705
+ };
706
+
707
+ // Additional Future-aware service types (add as needed)
708
+ export type ${nameProperCase}OperationProgress = {
709
+ // Define your progress data structure here
710
+ stage: string;
711
+ percentComplete: number;
712
+ currentItem?: string;
713
+ totalItems?: number;
714
+ };
715
+
716
+ export type ${nameProperCase}OperationResult = {
717
+ // Define your operation result structure here
718
+ success: boolean;
719
+ message?: string;
720
+ data?: any;
721
+ };
722
+ `;
723
+ this.codegenFs.write(servicesFilePath, content);
724
+ }
725
+ addFutureImports(content) {
726
+ if (content.includes("FutureResponse")) {
727
+ return content;
728
+ }
729
+ const importRegex = /import {\s*([^}]*)\s*} from '@kosdev-code\/kos-ui-sdk';/;
730
+ const importMatch = content.match(importRegex);
731
+ if (importMatch) {
732
+ const existingImports = importMatch[1];
733
+ if (existingImports.includes("FutureResponse")) {
734
+ return content;
735
+ }
736
+ const cleanedImports = existingImports.trim().replace(/,\s*$/, "");
737
+ const newImports = cleanedImports ? `${cleanedImports},
738
+ type FutureResponse` : `type FutureResponse`;
739
+ const newImportStatement = `import {
740
+ ${newImports}
741
+ } from '@kosdev-code/kos-ui-sdk';`;
742
+ return content.replace(importMatch[0], newImportStatement);
743
+ }
744
+ return content;
745
+ }
746
+ addFutureService(content) {
747
+ const { nameProperCase, nameLowerCase } = this.options;
748
+ if (content.includes(`perform${nameProperCase}Operation`)) {
749
+ return content;
750
+ }
751
+ const futureService = `
752
+ /**
753
+ * @category Service - Future Operation
754
+ * Placeholder for a long-running operation that returns a Future for progress tracking
755
+ *
756
+ * Replace this with your actual long-running service operation
757
+ */
758
+ export const perform${nameProperCase}Operation = async (): Promise<FutureResponse> => {
759
+ // TODO: Implement your long-running service operation here
760
+ // This should return a Future that can be tracked for progress
761
+
762
+ log.debug('starting long-running ${nameLowerCase} operation');
763
+
764
+ // Example pattern:
765
+ // return API.post(OPERATION_SERVICE_PATH, {
766
+ // // operation parameters
767
+ // });
768
+
769
+ // Placeholder - replace with actual implementation
770
+ throw new Error('perform${nameProperCase}Operation not yet implemented');
771
+ };`;
772
+ return content + "\n" + futureService;
773
+ }
774
+ addProgressTypes(content) {
775
+ const { nameProperCase } = this.options;
776
+ if (content.includes(`${nameProperCase}OperationProgress`)) {
777
+ return content;
778
+ }
779
+ const progressTypes = `
780
+ // Additional Future-aware service types (add as needed)
781
+ export type ${nameProperCase}OperationProgress = {
782
+ // Define your progress data structure here
783
+ stage: string;
784
+ percentComplete: number;
785
+ currentItem?: string;
786
+ totalItems?: number;
787
+ };
788
+
789
+ export type ${nameProperCase}OperationResult = {
790
+ // Define your operation result structure here
791
+ success: boolean;
792
+ message?: string;
793
+ data?: any;
794
+ };`;
795
+ return content + "\n" + progressTypes;
796
+ }
797
+ };
798
+
799
+ // ../kos-codegen-core/src/lib/generators/add-future-to-model/registration-transformer.ts
800
+ var RegistrationFileTransformer = class {
801
+ constructor(codegenFs, options) {
802
+ this.codegenFs = codegenFs;
803
+ this.options = options;
804
+ }
805
+ transform() {
806
+ const logger = getCodegenLogger();
807
+ const { registrationFilePath } = this.options;
808
+ if (!registrationFilePath || !this.codegenFs.exists(registrationFilePath)) {
809
+ logger.warn(
810
+ "Registration file not found, skipping registration updates"
811
+ );
812
+ return;
813
+ }
814
+ let content = this.codegenFs.read(registrationFilePath);
815
+ content = this.addTypeCast(content);
816
+ content = this.updateDocumentation(content);
817
+ this.codegenFs.write(registrationFilePath, content);
818
+ }
819
+ addTypeCast(content) {
820
+ const { nameProperCase } = this.options;
821
+ const factoryRegex = new RegExp(`class: ${nameProperCase}ModelImpl,`);
822
+ const replacement = `class: ${nameProperCase}ModelImpl as any, // Type cast needed for Future intersection`;
823
+ return content.replace(factoryRegex, replacement);
824
+ }
825
+ updateDocumentation(content) {
826
+ const { nameProperCase, futureType } = this.options;
827
+ const descriptionRegex = new RegExp(
828
+ `(\\* The registration bean includes convenience methods for creating and working with ${nameProperCase}Model instances\\.)`
829
+ );
830
+ const futureDocumentation = `$1
831
+ *
832
+ * ## Future Support
833
+ * This model includes ${futureType} Future support for tracking long-running operations with:
834
+ * - Progress tracking (0-1) with reactive updates
835
+ * - Status messages during operation
836
+ * - Cancellation support with bi-directional AbortController integration
837
+ * - Reactive integration for UI updates${futureType === "complete" ? "\n * - Internal access to Future state for custom logic and computed properties" : ""}`;
838
+ if (content.match(descriptionRegex)) {
839
+ content = content.replace(descriptionRegex, futureDocumentation);
840
+ }
841
+ const factoryExampleRegex = /(\*\s+\}\);?\s*\*\s+```)/;
842
+ const futureExample = `$1
843
+ *
844
+ * // Example: Accessing Future state (when a Future is active)
845
+ * const isRunning = model.futureIsRunning;
846
+ * const progress = model.futureProgress; // 0-1
847
+ * const status = model.futureStatus; // Current status message
848
+ * \`\`\``;
849
+ if (content.match(factoryExampleRegex)) {
850
+ content = content.replace(factoryExampleRegex, futureExample);
851
+ }
852
+ const predicateExampleRegex = /(\*\s+model\.updateAvailability\(false\);?\s*\*\s+\})/;
853
+ const predicateFutureExample = `$1
854
+ *
855
+ * // Future capabilities are also available
856
+ * const isRunning = model.futureIsRunning;
857
+ * const progress = model.futureProgress;
858
+ * }`;
859
+ if (content.match(predicateExampleRegex)) {
860
+ content = content.replace(predicateExampleRegex, predicateFutureExample);
861
+ }
862
+ return content;
863
+ }
864
+ };
865
+
866
+ // ../kos-codegen-core/src/lib/generators/add-future-to-model/generate-add-future-to-model.ts
867
+ function addFutureToModel(codegenFs, options, projects) {
868
+ const logger = getCodegenLogger();
869
+ const normalized = normalizeAddFutureOptions(codegenFs, options, projects);
870
+ logger.info(
871
+ `Adding ${normalized.futureType} Future support to model: ${normalized.modelName}`
872
+ );
873
+ if (!codegenFs.exists(normalized.modelFilePath)) {
874
+ throw new Error(`Model file not found: ${normalized.modelFilePath}`);
875
+ }
876
+ if (options.dryRun) {
877
+ logger.info("DRY RUN - No files will be modified");
878
+ logger.info(`Would modify model file: ${normalized.modelFilePath}`);
879
+ if (normalized.servicesFilePath) {
880
+ logger.info(
881
+ `Would modify/create services file: ${normalized.servicesFilePath}`
882
+ );
883
+ }
884
+ if (normalized.registrationFilePath) {
885
+ logger.info(
886
+ `Would modify registration file: ${normalized.registrationFilePath}`
887
+ );
888
+ }
889
+ return;
890
+ }
891
+ try {
892
+ logger.info(`Transforming model file: ${normalized.modelFilePath}`);
893
+ const modelTransformer = new ModelFileTransformer(codegenFs, normalized);
894
+ modelTransformer.transform();
895
+ if (normalized.updateServices) {
896
+ logger.info(
897
+ `Transforming services file: ${normalized.servicesFilePath || "creating new"}`
898
+ );
899
+ const serviceTransformer = new ServiceFileTransformer(
900
+ codegenFs,
901
+ normalized
902
+ );
903
+ serviceTransformer.transform();
904
+ }
905
+ if (normalized.registrationFilePath) {
906
+ logger.info(
907
+ `Transforming registration file: ${normalized.registrationFilePath}`
908
+ );
909
+ const registrationTransformer = new RegistrationFileTransformer(
910
+ codegenFs,
911
+ normalized
912
+ );
913
+ registrationTransformer.transform();
914
+ }
915
+ logger.info(
916
+ `Successfully added ${normalized.futureType} Future support to ${normalized.modelName}`
917
+ );
918
+ logger.info("");
919
+ logger.info("Next steps:");
920
+ logger.info(
921
+ "1. Review the generated @kosFuture method and implement your actual operation"
922
+ );
923
+ logger.info(
924
+ "2. Update the service method to return a proper FutureResponse"
925
+ );
926
+ logger.info("3. Define your specific progress and result types");
927
+ if (normalized.futureType === "complete") {
928
+ logger.info(
929
+ "4. Customize the onFutureUpdate method for your specific needs"
930
+ );
931
+ }
932
+ } catch (error) {
933
+ logger.error(`Failed to add Future support: ${error}`);
934
+ throw error;
935
+ }
936
+ }
937
+
938
+ // ../kos-codegen-core/src/lib/generators/component/types.ts
939
+ var PLUGIN_TYPES = {
940
+ CUI: "cui",
941
+ UTILITY: "utility",
942
+ TROUBLE_ACTION: "troubleAction",
943
+ SETUP: "setup",
944
+ SETTING: "setting",
945
+ NAV: "nav",
946
+ CONTROL_POUR: "controlPour",
947
+ CUSTOM: "custom"
948
+ };
949
+ var CONTRIBUTION_TYPE_MAP = {
950
+ [PLUGIN_TYPES.SETUP]: "setup",
951
+ [PLUGIN_TYPES.CUI]: "cui",
952
+ [PLUGIN_TYPES.UTILITY]: "utility",
953
+ [PLUGIN_TYPES.SETTING]: "setting",
954
+ [PLUGIN_TYPES.NAV]: "nav",
955
+ [PLUGIN_TYPES.TROUBLE_ACTION]: "trouble-action",
956
+ [PLUGIN_TYPES.CONTROL_POUR]: "control-pour",
957
+ [PLUGIN_TYPES.CUSTOM]: "custom"
958
+ };
959
+ var LOCALIZED_PLUGIN_TYPES = /* @__PURE__ */ new Set([
960
+ PLUGIN_TYPES.CUI,
961
+ PLUGIN_TYPES.UTILITY,
962
+ PLUGIN_TYPES.SETUP,
963
+ PLUGIN_TYPES.SETTING,
964
+ PLUGIN_TYPES.NAV,
965
+ PLUGIN_TYPES.CONTROL_POUR,
966
+ PLUGIN_TYPES.TROUBLE_ACTION,
967
+ PLUGIN_TYPES.CUSTOM
968
+ ]);
969
+
970
+ // ../kos-codegen-core/src/lib/generators/component/plugin-handlers/base.ts
971
+ var BasePluginHandler = class {
972
+ getContributionKey() {
973
+ return this.contributionKey;
974
+ }
975
+ requiresLocalization() {
976
+ return this.requiresI18n;
977
+ }
978
+ getTemplatePath() {
979
+ return this.contributionKey;
980
+ }
981
+ /**
982
+ * Helper to create experience configuration
983
+ */
984
+ createExperience(options, experienceId) {
985
+ const compPath = this.getComponentPath(options);
986
+ return {
987
+ id: experienceId,
988
+ component: options.namePascalCase,
989
+ location: `./src/${compPath}`
990
+ };
991
+ }
992
+ /**
993
+ * Helper to get component path
994
+ */
995
+ getComponentPath(options) {
996
+ return `${options.appDirectory}/${this.contributionKey}/${options.nameDashCase}/${options.nameDashCase}.tsx`;
997
+ }
998
+ /**
999
+ * Helper to create config prefix
1000
+ */
1001
+ getConfigPrefix(options) {
1002
+ return `${options.appProject}.${options.nameCamelCase}`;
1003
+ }
1004
+ };
1005
+
1006
+ // ../kos-codegen-core/src/lib/generators/component/plugin-handlers/control-pour-handler.ts
1007
+ var ControlPourPluginHandler = class extends BasePluginHandler {
1008
+ pluginType = PLUGIN_TYPES.CONTROL_POUR;
1009
+ contributionKey = "control-pour";
1010
+ requiresI18n = true;
1011
+ createConfiguration(options) {
1012
+ const configPrefix = this.getConfigPrefix(options);
1013
+ const experienceId = `${configPrefix}.controlPour.experience`;
1014
+ const contribution = {
1015
+ id: `${configPrefix}.controlPour`,
1016
+ title: `${configPrefix}.controlPour.title`,
1017
+ namespace: options.appProject,
1018
+ experienceId
1019
+ };
1020
+ const experience = this.createExperience(options, experienceId);
1021
+ return {
1022
+ contributions: {
1023
+ controlPour: [contribution]
1024
+ },
1025
+ experiences: {
1026
+ [experienceId]: experience
1027
+ }
1028
+ };
1029
+ }
1030
+ };
1031
+
1032
+ // ../kos-codegen-core/src/lib/generators/component/plugin-handlers/cui-handler.ts
1033
+ var CuiPluginHandler = class extends BasePluginHandler {
1034
+ pluginType = PLUGIN_TYPES.CUI;
1035
+ contributionKey = "cui";
1036
+ requiresI18n = true;
1037
+ createConfiguration(options) {
1038
+ const configPrefix = this.getConfigPrefix(options);
1039
+ const experienceId = `${configPrefix}.cui.experience`;
1040
+ const contribution = {
1041
+ id: configPrefix,
1042
+ title: `${configPrefix}.cui.title`,
1043
+ namespace: options.appProject,
1044
+ experienceId
1045
+ };
1046
+ const experience = this.createExperience(options, experienceId);
1047
+ return {
1048
+ contributions: {
1049
+ cui: [contribution]
1050
+ },
1051
+ experiences: {
1052
+ [experienceId]: experience
1053
+ }
1054
+ };
1055
+ }
1056
+ };
1057
+
1058
+ // ../kos-codegen-core/src/lib/generators/component/plugin-handlers/custom-handler.ts
1059
+ var CustomPluginHandler = class extends BasePluginHandler {
1060
+ pluginType = PLUGIN_TYPES.CUSTOM;
1061
+ contributionKey = "custom";
1062
+ requiresI18n = true;
1063
+ createConfiguration(options) {
1064
+ const configPrefix = this.getConfigPrefix(options);
1065
+ const experienceId = `${configPrefix}.${options.contributionKey || "custom"}.experience`;
1066
+ const userContributionKey = options.contributionKey || "custom";
1067
+ const contribution = {
1068
+ id: configPrefix,
1069
+ title: `${configPrefix}.${userContributionKey}.title`,
1070
+ namespace: options.appProject,
1071
+ experienceId
1072
+ // TODO: Add additional fields as required by the plugin-explorer specification
1073
+ // Refer to the plugin-explorer documentation for your specific contribution type
1074
+ };
1075
+ const experience = this.createExperience(options, experienceId);
1076
+ return {
1077
+ contributions: {
1078
+ [userContributionKey]: [contribution]
1079
+ },
1080
+ experiences: {
1081
+ [experienceId]: experience
1082
+ }
1083
+ };
1084
+ }
1085
+ getTemplatePath() {
1086
+ return this.contributionKey || "custom";
1087
+ }
1088
+ getComponentPath(options) {
1089
+ const pathKey = options.contributionKey || "custom";
1090
+ return `${options.appDirectory}/${pathKey}/${options.nameDashCase}/${options.nameDashCase}.tsx`;
1091
+ }
1092
+ };
1093
+
1094
+ // ../kos-codegen-core/src/lib/generators/component/plugin-handlers/default-handler.ts
1095
+ var DefaultComponentHandler = class extends BasePluginHandler {
1096
+ pluginType = "component";
1097
+ contributionKey = "components";
1098
+ requiresI18n = false;
1099
+ createConfiguration(options) {
1100
+ const compPath = this.getComponentPath(options);
1101
+ const viewConfig = {
1102
+ id: `${options.appProject}.${options.nameCamelCase}`,
1103
+ title: "ddk.ncui.config.title",
1104
+ namespace: options.appProject,
1105
+ component: options.namePascalCase,
1106
+ location: `./src/${compPath}`
1107
+ };
1108
+ return {
1109
+ contributions: {},
1110
+ experiences: {},
1111
+ views: {
1112
+ [this.getTabViewKey()]: [viewConfig]
1113
+ }
1114
+ };
1115
+ }
1116
+ getTabViewKey() {
1117
+ return "ddk.ncui.settings.tabView";
1118
+ }
1119
+ getTemplatePath() {
1120
+ return "files";
1121
+ }
1122
+ };
1123
+
1124
+ // ../kos-codegen-core/src/lib/generators/component/plugin-handlers/nav-handler.ts
1125
+ var NavPluginHandler = class extends BasePluginHandler {
1126
+ pluginType = PLUGIN_TYPES.NAV;
1127
+ contributionKey = "nav";
1128
+ requiresI18n = true;
1129
+ createConfiguration(options) {
1130
+ const configPrefix = this.getConfigPrefix(options);
1131
+ const experienceId = `${configPrefix}.nav.experience`;
1132
+ const contribution = {
1133
+ id: `${configPrefix}.nav`,
1134
+ title: `${configPrefix}.nav.title`,
1135
+ namespace: options.appProject,
1136
+ navDescriptor: options.nameLowerCase,
1137
+ experienceId
1138
+ };
1139
+ const experience = this.createExperience(options, experienceId);
1140
+ return {
1141
+ contributions: {
1142
+ navViews: [contribution]
1143
+ },
1144
+ experiences: {
1145
+ [experienceId]: experience
1146
+ }
1147
+ };
1148
+ }
1149
+ };
1150
+
1151
+ // ../kos-codegen-core/src/lib/generators/component/plugin-handlers/setting-handler.ts
1152
+ var SettingPluginHandler = class extends BasePluginHandler {
1153
+ pluginType = PLUGIN_TYPES.SETTING;
1154
+ contributionKey = "setting";
1155
+ requiresI18n = true;
1156
+ createConfiguration(options) {
1157
+ const configPrefix = this.getConfigPrefix(options);
1158
+ const experienceId = `${configPrefix}.settings.experience`;
1159
+ const contribution = {
1160
+ id: `${configPrefix}.setting`,
1161
+ title: `${configPrefix}.setting.title`,
1162
+ namespace: options.appProject,
1163
+ settingsGroup: options.group || "general",
1164
+ experienceId
1165
+ };
1166
+ const experience = this.createExperience(options, experienceId);
1167
+ return {
1168
+ contributions: {
1169
+ settings: [contribution]
1170
+ },
1171
+ experiences: {
1172
+ [experienceId]: experience
1173
+ }
1174
+ };
1175
+ }
1176
+ };
1177
+
1178
+ // ../kos-codegen-core/src/lib/generators/component/plugin-handlers/setup-handler.ts
1179
+ var SetupPluginHandler = class extends BasePluginHandler {
1180
+ pluginType = PLUGIN_TYPES.SETUP;
1181
+ contributionKey = "setup";
1182
+ requiresI18n = true;
1183
+ createConfiguration(options) {
1184
+ const configPrefix = this.getConfigPrefix(options);
1185
+ const experienceId = `${configPrefix}.setup.experience`;
1186
+ const contribution = {
1187
+ id: `${configPrefix}.setup`,
1188
+ title: `${configPrefix}.setup.title`,
1189
+ namespace: options.appProject,
1190
+ setupDescriptor: options.nameCamelCase,
1191
+ experienceId
1192
+ };
1193
+ const experience = this.createExperience(options, experienceId);
1194
+ return {
1195
+ contributions: {
1196
+ setupStep: [contribution]
1197
+ },
1198
+ experiences: {
1199
+ [experienceId]: experience
1200
+ }
1201
+ };
1202
+ }
1203
+ };
1204
+
1205
+ // ../kos-codegen-core/src/lib/generators/component/plugin-handlers/trouble-action-handler.ts
1206
+ var TroubleActionPluginHandler = class extends BasePluginHandler {
1207
+ pluginType = PLUGIN_TYPES.TROUBLE_ACTION;
1208
+ contributionKey = "trouble-action";
1209
+ requiresI18n = true;
1210
+ createConfiguration(options) {
1211
+ const configPrefix = this.getConfigPrefix(options);
1212
+ const experienceId = `${configPrefix}.troubleAction.experience`;
1213
+ const contribution = {
1214
+ id: `${configPrefix}.troubleAction`,
1215
+ title: `${configPrefix}.troubleAction.title`,
1216
+ namespace: options.appProject,
1217
+ troubleType: options.nameCamelCase,
1218
+ experienceId
1219
+ };
1220
+ const experience = this.createExperience(options, experienceId);
1221
+ return {
1222
+ contributions: {
1223
+ troubleActions: [contribution]
1224
+ },
1225
+ experiences: {
1226
+ [experienceId]: experience
1227
+ }
1228
+ };
1229
+ }
1230
+ };
1231
+
1232
+ // ../kos-codegen-core/src/lib/generators/component/plugin-handlers/utility-handler.ts
1233
+ var UtilityPluginHandler = class extends BasePluginHandler {
1234
+ pluginType = PLUGIN_TYPES.UTILITY;
1235
+ contributionKey = "utility";
1236
+ requiresI18n = true;
1237
+ createConfiguration(options) {
1238
+ const configPrefix = this.getConfigPrefix(options);
1239
+ const experienceId = `${configPrefix}.util.experience`;
1240
+ const contribution = {
1241
+ id: `${configPrefix}.util`,
1242
+ title: `${configPrefix}.utility.title`,
1243
+ namespace: options.appProject,
1244
+ utilDescriptor: options.nameCamelCase,
1245
+ experienceId
1246
+ };
1247
+ const experience = this.createExperience(options, experienceId);
1248
+ return {
1249
+ contributions: {
1250
+ utilities: [contribution]
1251
+ },
1252
+ experiences: {
1253
+ [experienceId]: experience
1254
+ }
1255
+ };
1256
+ }
1257
+ };
1258
+
1259
+ // ../kos-codegen-core/src/lib/generators/component/plugin-handlers/factory.ts
1260
+ var PluginHandlerFactory = class {
1261
+ static handlers = /* @__PURE__ */ new Map([
1262
+ [PLUGIN_TYPES.CUI, CuiPluginHandler],
1263
+ [PLUGIN_TYPES.UTILITY, UtilityPluginHandler],
1264
+ [PLUGIN_TYPES.SETTING, SettingPluginHandler],
1265
+ [PLUGIN_TYPES.SETUP, SetupPluginHandler],
1266
+ [PLUGIN_TYPES.NAV, NavPluginHandler],
1267
+ [PLUGIN_TYPES.CONTROL_POUR, ControlPourPluginHandler],
1268
+ [PLUGIN_TYPES.TROUBLE_ACTION, TroubleActionPluginHandler],
1269
+ [PLUGIN_TYPES.CUSTOM, CustomPluginHandler]
1270
+ ]);
1271
+ static createHandler(pluginType) {
1272
+ if (!pluginType) {
1273
+ return new DefaultComponentHandler();
1274
+ }
1275
+ const HandlerClass = this.handlers.get(pluginType);
1276
+ if (!HandlerClass) {
1277
+ console.warn(
1278
+ `No handler found for plugin type: ${pluginType}. Using default handler.`
1279
+ );
1280
+ return new DefaultComponentHandler();
1281
+ }
1282
+ return new HandlerClass();
1283
+ }
1284
+ static isValidPluginType(type) {
1285
+ return this.handlers.has(type);
1286
+ }
1287
+ };
1288
+
1289
+ // src/lib/utils/nx-context.mjs
1290
+ var __dirname2 = path6.dirname(fileURLToPath(import.meta.url));
1291
+ function findKosJsonFiles(dir = process.cwd(), files = []) {
1292
+ try {
1293
+ const entries = readdirSync2(dir);
1294
+ for (const entry of entries) {
1295
+ if (entry === "node_modules" || entry === ".git" || entry === ".nx" || entry === "dist" || entry === "coverage" || entry === ".vscode" || entry === ".idea" || entry.startsWith(".") && entry !== ".kos.json") {
1296
+ continue;
1297
+ }
1298
+ const fullPath = path6.join(dir, entry);
1299
+ try {
1300
+ const stat = statSync(fullPath);
1301
+ if (stat.isFile() && entry === ".kos.json") {
1302
+ files.push(fullPath);
1303
+ } else if (stat.isDirectory()) {
1304
+ if (entry !== "tmp" && entry !== "temp" && entry !== "build") {
1305
+ findKosJsonFiles(fullPath, files);
1306
+ }
1307
+ }
1308
+ } catch (error) {
1309
+ continue;
1310
+ }
1311
+ }
1312
+ } catch (error) {
1313
+ }
1314
+ return files;
1315
+ }
1316
+ async function getAllKosProjects() {
1317
+ const cached = getCached("allKosProjects");
1318
+ if (cached)
1319
+ return cached;
1320
+ if (process.env.KOS_CLI_QUIET !== "true") {
1321
+ console.warn(`[kos-cli] Discovering KOS projects by scanning .kos.json files...`);
1322
+ }
1323
+ const projectDirs = ["apps", "libs", "packages"];
1324
+ const kosJsonFiles = [];
1325
+ const workspaceRoot = process.cwd();
1326
+ for (const dir of projectDirs) {
1327
+ const dirPath = path6.join(workspaceRoot, dir);
1328
+ if (existsSync3(dirPath)) {
1329
+ findKosJsonFiles(dirPath, kosJsonFiles);
1330
+ }
1331
+ }
1332
+ const rootKosJson = path6.join(workspaceRoot, ".kos.json");
1333
+ if (existsSync3(rootKosJson)) {
1334
+ kosJsonFiles.push(rootKosJson);
1335
+ }
1336
+ if (kosJsonFiles.length === 0) {
1337
+ if (process.env.KOS_CLI_QUIET !== "true") {
1338
+ console.warn(`[kos-cli] No .kos.json files found in common directories, performing full workspace scan...`);
1339
+ }
1340
+ findKosJsonFiles(workspaceRoot, kosJsonFiles);
1341
+ }
1342
+ const kosProjects = [];
1343
+ for (const kosJsonPath of kosJsonFiles) {
1344
+ try {
1345
+ const kosConfig = JSON.parse(readFileSync3(kosJsonPath, "utf-8"));
1346
+ const projectDir = path6.dirname(kosJsonPath);
1347
+ let projectName = null;
1348
+ const projectJsonPath = path6.join(projectDir, "project.json");
1349
+ if (existsSync3(projectJsonPath)) {
1350
+ try {
1351
+ const projectJson = JSON.parse(readFileSync3(projectJsonPath, "utf-8"));
1352
+ projectName = projectJson.name;
1353
+ } catch (error) {
1354
+ projectName = path6.basename(projectDir);
1355
+ }
1356
+ } else {
1357
+ projectName = path6.basename(projectDir);
1358
+ }
1359
+ if (kosConfig.type === "root") {
1360
+ continue;
1361
+ }
1362
+ kosProjects.push({
1363
+ name: projectName,
1364
+ path: projectDir,
1365
+ kosJsonPath,
1366
+ config: kosConfig,
1367
+ projectType: kosConfig.generator?.projectType
1368
+ });
1369
+ } catch (error) {
1370
+ console.warn(`[kos-cli] Error reading ${kosJsonPath}: ${error.message}`);
1371
+ }
1372
+ }
1373
+ setCached("allKosProjects", kosProjects);
1374
+ return kosProjects;
1375
+ }
1376
+ async function getAllModels() {
1377
+ const cached = getCached("allModels");
1378
+ if (cached)
1379
+ return cached;
1380
+ if (process.env.KOS_CLI_QUIET !== "true") {
1381
+ console.warn(`[kos-cli] Scanning for models in KOS projects...`);
1382
+ }
1383
+ const allKosProjects = await getAllKosProjects();
1384
+ const models = [];
1385
+ for (const kosProject of allKosProjects) {
1386
+ if (kosProject.config.models) {
1387
+ Object.keys(kosProject.config.models).forEach((model) => {
1388
+ models.push({ model, project: kosProject.name });
1389
+ });
1390
+ }
1391
+ }
1392
+ models.sort((a, b) => {
1393
+ if (a.project === b.project) {
1394
+ return a.model.localeCompare(b.model);
1395
+ } else {
1396
+ return a.project.localeCompare(b.project);
1397
+ }
1398
+ });
1399
+ setCached("allModels", models);
1400
+ return models;
1401
+ }
1402
+
1403
+ // src/lib/utils/prompts.mjs
1404
+ var DEFAULT_PROMPTS = [
1405
+ {
1406
+ type: "confirm",
1407
+ name: "interactive",
1408
+ message: "Use interactive mode?",
1409
+ default: false,
1410
+ when: false
1411
+ },
1412
+ {
1413
+ type: "confirm",
1414
+ name: "dryRun",
1415
+ message: "Dry run the command?",
1416
+ default: false,
1417
+ when: false
1418
+ }
1419
+ ];
1420
+
1421
+ // src/lib/generators/model/add-future.mjs
1422
+ var metadata = {
8
1423
  key: "add-future",
9
1424
  name: "Add Future Support to Model",
10
1425
  invalidateCache: true,
@@ -15,54 +1430,41 @@ export const metadata = {
15
1430
  futureType: "futureType",
16
1431
  updateServices: "updateServices",
17
1432
  dryRun: "dryRun",
18
- interactive: "interactive",
19
- },
1433
+ interactive: "interactive"
1434
+ }
20
1435
  };
21
-
22
- export default async function (plop) {
23
- // Check if we're in interactive mode by looking at process args
24
- const isInteractive = process.argv.includes('-i') || process.argv.includes('--interactive');
25
-
26
- // For interactive mode, use lazy loading. For non-interactive, load immediately.
1436
+ async function add_future_default(plop) {
1437
+ const isInteractive = process.argv.includes("-i") || process.argv.includes("--interactive");
27
1438
  let modelChoices;
28
1439
  if (isInteractive) {
29
1440
  modelChoices = async () => {
30
1441
  const allModels = await getAllModels();
31
1442
  return allModels.map((m) => ({
32
1443
  name: `${m.model} (${m.project})`,
33
- value: m.model,
1444
+ value: m.model
34
1445
  }));
35
1446
  };
36
1447
  } else {
37
1448
  const allModels = await getAllModels();
38
1449
  modelChoices = allModels.map((m) => ({
39
1450
  name: `${m.model} (${m.project})`,
40
- value: m.model,
1451
+ value: m.model
41
1452
  }));
42
1453
  }
43
-
44
- plop.setActionType("addFutureToModel", async function (answers) {
1454
+ plop.setActionType("addFutureToModel", async function(answers) {
1455
+ const cwd = process.cwd();
1456
+ const codegenFs = new DirectFileSystem(cwd);
45
1457
  const allModels = await getAllModels();
46
- const modelProject = allModels.find(
47
- (m) => m.model === answers.modelName
48
- )?.project;
49
-
50
- const command = `npx nx generate @kosdev-code/kos-nx-plugin:kos-add-future-to-model \\
51
- --modelName=${answers.modelName} \\
52
- --modelProject=${modelProject} \\
53
- --futureType=${answers.futureType} \\
54
- --updateServices=${!!answers.updateServices} \\
55
- --no-interactive ${answers.dryRun ? "--dryRun" : ""} --verbose`;
56
-
57
- try {
58
- await execute(command);
59
- } catch (error) {
60
- throw new Error(error);
61
- }
62
-
1458
+ const modelProject = allModels.find((m) => m.model === answers.modelName)?.project;
1459
+ addFutureToModel(codegenFs, {
1460
+ modelName: answers.modelName,
1461
+ modelProject,
1462
+ futureType: answers.futureType,
1463
+ updateServices: !!answers.updateServices,
1464
+ dryRun: !!answers.dryRun
1465
+ });
63
1466
  return `Future support (${answers.futureType}) added to model ${answers.modelName} in ${modelProject}`;
64
1467
  });
65
-
66
1468
  plop.setGenerator("add-future", {
67
1469
  description: "Add Future support to an existing KOS Model",
68
1470
  prompts: [
@@ -71,7 +1473,7 @@ export default async function (plop) {
71
1473
  type: "list",
72
1474
  name: "modelName",
73
1475
  message: "Which model do you want to add Future support to?",
74
- choices: modelChoices,
1476
+ choices: modelChoices
75
1477
  },
76
1478
  {
77
1479
  type: "list",
@@ -79,18 +1481,22 @@ export default async function (plop) {
79
1481
  message: "What type of Future support?",
80
1482
  choices: [
81
1483
  { name: "Minimal (external access only)", value: "minimal" },
82
- { name: "Complete (internal + external access)", value: "complete" },
1484
+ { name: "Complete (internal + external access)", value: "complete" }
83
1485
  ],
84
- default: "minimal",
1486
+ default: "minimal"
85
1487
  },
86
1488
  {
87
1489
  type: "confirm",
88
1490
  name: "updateServices",
89
1491
  message: "Update/create services file with Future operations?",
90
- default: true,
91
- },
1492
+ default: true
1493
+ }
92
1494
  ],
93
-
94
- actions: actionFactory("addFutureToModel", metadata),
1495
+ actions: actionFactory("addFutureToModel", metadata)
95
1496
  });
96
1497
  }
1498
+ export {
1499
+ add_future_default as default,
1500
+ metadata
1501
+ };
1502
+ //# sourceMappingURL=add-future.mjs.map