@kosdev-code/kos-codegen-core 3.0.8 → 3.0.9

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.
Files changed (40) hide show
  1. package/index.d.ts +2 -2
  2. package/index.d.ts.map +1 -1
  3. package/index.js +816 -488
  4. package/index.js.map +1 -1
  5. package/index.mjs +815 -487
  6. package/index.mjs.map +1 -1
  7. package/lib/generators/add-future-to-model/model-transformer.d.ts +3 -4
  8. package/lib/generators/add-future-to-model/model-transformer.d.ts.map +1 -1
  9. package/lib/generators/add-future-to-model/normalize-options.d.ts.map +1 -1
  10. package/lib/generators/add-future-to-model/registration-transformer.d.ts.map +1 -1
  11. package/lib/generators/augment/resolve-model-file.d.ts.map +1 -1
  12. package/lib/generators/ci-sync.d.ts +26 -0
  13. package/lib/generators/ci-sync.d.ts.map +1 -0
  14. package/lib/generators/generate-polyglot-workspace.d.ts +38 -0
  15. package/lib/generators/generate-polyglot-workspace.d.ts.map +1 -0
  16. package/lib/generators/index.d.ts +6 -0
  17. package/lib/generators/index.d.ts.map +1 -1
  18. package/lib/generators/java-workspace.d.ts +33 -0
  19. package/lib/generators/java-workspace.d.ts.map +1 -0
  20. package/lib/generators/kab-targets.d.ts +40 -0
  21. package/lib/generators/kab-targets.d.ts.map +1 -0
  22. package/lib/generators/release-version-script.d.ts +10 -0
  23. package/lib/generators/release-version-script.d.ts.map +1 -0
  24. package/package.json +2 -2
  25. package/templates/polyglot-workspace/build/build-java.sh.template +18 -0
  26. package/templates/polyglot-workspace/build/build-release.sh.template +11 -0
  27. package/templates/polyglot-workspace/build/build-ui.sh.template +23 -0
  28. package/templates/polyglot-workspace/build/docker-build.sh.template +55 -0
  29. package/templates/polyglot-workspace/build/jdkw.sh.template +93 -0
  30. package/templates/polyglot-workspace/build/nodew.sh.template +73 -0
  31. package/templates/polyglot-workspace/build/release_version_prebuild.sh.template +34 -0
  32. package/templates/polyglot-workspace/github/build-java.json.template +6 -0
  33. package/templates/polyglot-workspace/github/build-release.json.template +14 -0
  34. package/templates/polyglot-workspace/github/build-ui.json.template +13 -0
  35. package/templates/polyglot-workspace/github/workflows/develop-java.yml.template +35 -0
  36. package/templates/polyglot-workspace/github/workflows/develop-ui.yml.template +38 -0
  37. package/templates/polyglot-workspace/github/workflows/release.yml.template +37 -0
  38. package/templates/polyglot-workspace/java/README.md.template +36 -0
  39. package/templates/polyglot-workspace/root/.gitignore.template +5 -0
  40. package/templates/polyglot-workspace/root/README.md.template +71 -0
package/index.mjs CHANGED
@@ -515,6 +515,296 @@ function generateInit(codegenFs, options) {
515
515
  };
516
516
  writeJson(codegenFs, "nx.json", nxConfig);
517
517
  }
518
+ const BUILD_SCRIPTS = [
519
+ "build/build-ui.sh",
520
+ "build/build-java.sh",
521
+ "build/build-release.sh",
522
+ "build/release_version_prebuild.sh",
523
+ "build/docker-build.sh"
524
+ ];
525
+ function generatePolyglotWorkspace(codegenFs, templateDir, options) {
526
+ const hasUi = (options.type ?? "polyglot") !== "java";
527
+ const normalized = normalizeAllValues({ name: options.name });
528
+ const vars = {
529
+ ...normalized,
530
+ hasUi,
531
+ keyset: options.keyset || "prod.kos",
532
+ appUiName: `${normalized.nameDashCase}-ui`,
533
+ nodeVersion: options.nodeVersion || process.version.replace(/^v/, ""),
534
+ jdkMajor: options.jdkMajor || "17",
535
+ mavenVersion: options.mavenVersion || "3.9.9"
536
+ };
537
+ generateFilesFromTemplates(
538
+ codegenFs,
539
+ path.join(templateDir, "root"),
540
+ ".",
541
+ vars
542
+ );
543
+ generateFilesFromTemplates(
544
+ codegenFs,
545
+ path.join(templateDir, "build"),
546
+ "build",
547
+ vars
548
+ );
549
+ generateFilesFromTemplates(
550
+ codegenFs,
551
+ path.join(templateDir, "github"),
552
+ ".github",
553
+ vars
554
+ );
555
+ generateFilesFromTemplates(
556
+ codegenFs,
557
+ path.join(templateDir, "java"),
558
+ "java",
559
+ vars
560
+ );
561
+ return { executablePaths: [...BUILD_SCRIPTS] };
562
+ }
563
+ const UI_PROJECT_DIRS = [
564
+ "apps",
565
+ "libs",
566
+ "plugins",
567
+ "splash",
568
+ "themes",
569
+ "content",
570
+ "translations"
571
+ ];
572
+ function joinArtifactPath(outputPath, fileName) {
573
+ return `${outputPath.replace(/\/+$/, "")}/${fileName}`;
574
+ }
575
+ function discoverUiArtifacts(codegenFs) {
576
+ const artifacts = [];
577
+ for (const dir of UI_PROJECT_DIRS) {
578
+ for (const file of codegenFs.listFiles(`ui/${dir}`)) {
579
+ if (!file.endsWith("project.json")) continue;
580
+ const raw = codegenFs.read(file);
581
+ if (raw === null) continue;
582
+ let project;
583
+ try {
584
+ project = JSON.parse(raw);
585
+ } catch {
586
+ continue;
587
+ }
588
+ const name = project.name;
589
+ if (!name) continue;
590
+ const kabOptions = project.targets?.kab?.options;
591
+ if (kabOptions?.outputPath && kabOptions?.kabName) {
592
+ artifacts.push({
593
+ id: name,
594
+ filename: `ui/${joinArtifactPath(kabOptions.outputPath, kabOptions.kabName)}`
595
+ });
596
+ } else if (project.targets?.splash) {
597
+ artifacts.push({
598
+ id: name,
599
+ filename: `ui/dist/archives/packages/${name}/${name}.kab`,
600
+ layer: 1
601
+ });
602
+ }
603
+ }
604
+ }
605
+ for (const file of codegenFs.listFiles("ui/external")) {
606
+ if (!file.endsWith(".kab")) continue;
607
+ const base = file.slice(file.lastIndexOf("/") + 1, -".kab".length);
608
+ artifacts.push({ id: base, filename: file });
609
+ }
610
+ return artifacts;
611
+ }
612
+ function discoverJavaArtifacts(codegenFs) {
613
+ const aggregator = codegenFs.read("java/pom.xml");
614
+ if (aggregator === null) return [];
615
+ const artifacts = [];
616
+ for (const match of aggregator.matchAll(/<module>([^<]+)<\/module>/g)) {
617
+ const moduleDir = match[1];
618
+ const pom = codegenFs.read(`java/${moduleDir}/pom.xml`);
619
+ if (pom === null || !pom.includes("kos-kab-maven-plugin")) continue;
620
+ const withoutParent = pom.replace(/<parent>[\s\S]*?<\/parent>/, "");
621
+ const artifactId = withoutParent.match(
622
+ /<artifactId>([^<]+)<\/artifactId>/
623
+ )?.[1];
624
+ if (!artifactId) continue;
625
+ artifacts.push({
626
+ id: moduleDir,
627
+ // eslint-disable-next-line no-template-curly-in-string
628
+ filename: `java/${moduleDir}/target/${artifactId}-\${KOS_STD_VERSION_REGEX}.kab`
629
+ });
630
+ }
631
+ return artifacts;
632
+ }
633
+ function syncManifest(codegenFs, manifestPath, discovered, prune) {
634
+ const raw = codegenFs.read(manifestPath);
635
+ if (raw === null) return null;
636
+ const manifest = JSON.parse(raw);
637
+ const existing = manifest.artifacts ?? [];
638
+ const byFilename = new Map(discovered.map((d) => [d.filename, d]));
639
+ const kept = [];
640
+ const stale = [];
641
+ const pruned = [];
642
+ for (const entry of existing) {
643
+ if (byFilename.has(entry.filename)) {
644
+ kept.push(entry);
645
+ byFilename.delete(entry.filename);
646
+ } else if (prune) {
647
+ pruned.push(entry.id ?? entry.filename);
648
+ } else {
649
+ kept.push(entry);
650
+ stale.push(entry.id ?? entry.filename);
651
+ }
652
+ }
653
+ const added = [];
654
+ for (const artifact of byFilename.values()) {
655
+ kept.push({
656
+ id: artifact.id,
657
+ filename: artifact.filename,
658
+ artifactstore: "kos-cdn",
659
+ marketplace: 1,
660
+ ...artifact.layer !== void 0 ? { layer: artifact.layer } : {}
661
+ });
662
+ added.push(artifact.id);
663
+ }
664
+ if (added.length > 0 || pruned.length > 0) {
665
+ manifest.artifacts = kept;
666
+ codegenFs.write(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
667
+ }
668
+ return { added, stale, pruned };
669
+ }
670
+ function syncCiManifests(codegenFs, options = {}) {
671
+ const prune = options.prune ?? false;
672
+ const ui = discoverUiArtifacts(codegenFs);
673
+ const java = discoverJavaArtifacts(codegenFs);
674
+ const manifests = {};
675
+ const plan = [
676
+ [".github/build-ui.json", ui],
677
+ [".github/build-java.json", java],
678
+ [".github/build-release.json", [...ui, ...java]]
679
+ ];
680
+ for (const [manifestPath, discovered] of plan) {
681
+ const result = syncManifest(codegenFs, manifestPath, discovered, prune);
682
+ if (result) {
683
+ manifests[manifestPath] = result;
684
+ }
685
+ }
686
+ return { manifests, discovered: { ui, java } };
687
+ }
688
+ function ensureJavaAggregatorPom(codegenFs, options) {
689
+ const pomPath = "java/pom.xml";
690
+ const existing = codegenFs.read(pomPath);
691
+ if (existing === null) {
692
+ codegenFs.write(
693
+ pomPath,
694
+ `<?xml version="1.0" encoding="UTF-8"?>
695
+ <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
696
+ <modelVersion>4.0.0</modelVersion>
697
+ <groupId>${options.groupId}</groupId>
698
+ <artifactId>${options.artifactId}</artifactId>
699
+ <version>0.0.0-SNAPSHOT</version>
700
+ <packaging>pom</packaging>
701
+ <modules>
702
+ <module>${options.moduleName}</module>
703
+ </modules>
704
+ </project>
705
+ `
706
+ );
707
+ return;
708
+ }
709
+ if (existing.includes(`<module>${options.moduleName}</module>`)) {
710
+ return;
711
+ }
712
+ codegenFs.write(
713
+ pomPath,
714
+ existing.replace(
715
+ "</modules>",
716
+ ` <module>${options.moduleName}</module>
717
+ </modules>`
718
+ )
719
+ );
720
+ }
721
+ function finalizeArchetypeModule(codegenFs, options) {
722
+ const { moduleName, kosVersion, kabPluginVersion, apiInfoVersion } = options;
723
+ const notes = [];
724
+ const pomPath = `java/${moduleName}/pom.xml`;
725
+ let pom = codegenFs.read(pomPath);
726
+ if (pom === null) {
727
+ throw new Error(`Generated module pom not found: ${pomPath}`);
728
+ }
729
+ if (pom.includes("<kos.version>0.0.0-SNAPSHOT</kos.version>")) {
730
+ if (kosVersion) {
731
+ pom = pom.replace(
732
+ "<kos.version>0.0.0-SNAPSHOT</kos.version>",
733
+ `<kos.version>${kosVersion}</kos.version>`
734
+ );
735
+ } else {
736
+ notes.push(
737
+ "kos.version is 0.0.0-SNAPSHOT — set it to a released kos-bom version before building"
738
+ );
739
+ }
740
+ }
741
+ if (pom.includes("${kos-kab-maven-plugin.version}") && !pom.includes("<kos-kab-maven-plugin.version>")) {
742
+ if (kabPluginVersion) {
743
+ pom = pom.replace(
744
+ /(<kos\.version>[^<]*<\/kos\.version>)/,
745
+ `$1
746
+ <kos-kab-maven-plugin.version>${kabPluginVersion}</kos-kab-maven-plugin.version>`
747
+ );
748
+ } else {
749
+ notes.push(
750
+ "the ${kos-kab-maven-plugin.version} property is referenced but undefined — add it before building"
751
+ );
752
+ }
753
+ }
754
+ const unversionedApiInfo = /<artifactId>api-info<\/artifactId>(\s*)<\/dependency>/;
755
+ if (unversionedApiInfo.test(pom)) {
756
+ if (apiInfoVersion) {
757
+ pom = pom.replace(
758
+ unversionedApiInfo,
759
+ `<artifactId>api-info</artifactId>$1 <version>${apiInfoVersion}</version>$1</dependency>`
760
+ );
761
+ } else {
762
+ notes.push(
763
+ "api-info has no version and is not managed by the kos-bom — add one before building"
764
+ );
765
+ }
766
+ }
767
+ codegenFs.write(pomPath, pom);
768
+ const githubDir = `java/${moduleName}/github`;
769
+ if (codegenFs.exists(githubDir)) {
770
+ for (const file of codegenFs.listFiles(githubDir)) {
771
+ codegenFs.delete(file);
772
+ }
773
+ }
774
+ const gitignore = codegenFs.read(`java/${moduleName}/gitignore`);
775
+ if (gitignore !== null) {
776
+ codegenFs.write(`java/${moduleName}/.gitignore`, gitignore);
777
+ codegenFs.delete(`java/${moduleName}/gitignore`);
778
+ }
779
+ return notes;
780
+ }
781
+ function addJavaArtifactToManifests(codegenFs, options) {
782
+ const { moduleName } = options;
783
+ for (const manifestPath of [
784
+ ".github/build-java.json",
785
+ ".github/build-release.json"
786
+ ]) {
787
+ const raw = codegenFs.read(manifestPath);
788
+ if (raw === null) {
789
+ continue;
790
+ }
791
+ const manifest = JSON.parse(raw);
792
+ manifest.artifacts = manifest.artifacts ?? [];
793
+ if (manifest.artifacts.some(
794
+ (artifact) => artifact.id === moduleName
795
+ )) {
796
+ continue;
797
+ }
798
+ manifest.artifacts.push({
799
+ id: moduleName,
800
+ // eslint-disable-next-line no-template-curly-in-string
801
+ filename: `java/${moduleName}/target/${moduleName}-\${KOS_STD_VERSION_REGEX}.kab`,
802
+ artifactstore: "kos-cdn",
803
+ marketplace: 1
804
+ });
805
+ codegenFs.write(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
806
+ }
807
+ }
518
808
  function normalizeOptions(codegenFs, options, projects) {
519
809
  const toNormalize = {
520
810
  name: options.name
@@ -560,6 +850,103 @@ function normalizeOptions(codegenFs, options, projects) {
560
850
  template: ""
561
851
  };
562
852
  }
853
+ const UPDATE_RELEASE_VERSION_SCRIPT_PATH = "tools/scripts/update-release-version.mjs";
854
+ function buildKabTargets(options) {
855
+ const {
856
+ name,
857
+ archiveDir = "packages",
858
+ descriptorDir,
859
+ buildTarget = "build"
860
+ } = options;
861
+ const outputPath = `dist/archives/${archiveDir}/${name}/`;
862
+ const targets = {
863
+ kab: {
864
+ command: `node tools/scripts/kabtool.mjs build ${name} && node tools/scripts/kabtool.mjs list ${name} `,
865
+ options: {
866
+ outputPath,
867
+ zipName: "ui.zip",
868
+ kabName: `${name}.kab`
869
+ },
870
+ dependsOn: ["zip"]
871
+ },
872
+ zip: {
873
+ command: `node tools/scripts/archiver.js ${name}`,
874
+ options: {
875
+ outputPath,
876
+ zipName: "ui.zip"
877
+ },
878
+ dependsOn: descriptorDir ? [buildTarget, "descriptor"] : [buildTarget]
879
+ }
880
+ };
881
+ if (descriptorDir) {
882
+ targets.descriptor = {
883
+ command: `node tools/scripts/descriptor.mjs ${name}`,
884
+ options: {
885
+ outputPath: `dist/${descriptorDir}`,
886
+ fileName: "descriptor.json"
887
+ },
888
+ dependsOn: ["build"]
889
+ };
890
+ }
891
+ targets.version = {
892
+ command: `node ${UPDATE_RELEASE_VERSION_SCRIPT_PATH} ${name} {args.ver}`,
893
+ options: {},
894
+ dependsOn: []
895
+ };
896
+ return targets;
897
+ }
898
+ const UPDATE_RELEASE_VERSION_SCRIPT = `import devkit from "@nx/devkit";
899
+ import { resolve } from "path";
900
+ import { readFileSync, writeFileSync } from "fs";
901
+ import prettier from "prettier";
902
+
903
+ // KOS artifact versioning: stamps the project's .kos.json "version" field
904
+ // (which kabtool bakes into the KAB). Never touches package.json.
905
+ // Driven by tag-based releases:
906
+ // nx run-many --target=version --args=--ver=$KOSBUILD_VERSION
907
+
908
+ const { readCachedProjectGraph } = devkit;
909
+ const [, , name, versionArg] = process.argv;
910
+
911
+ // "{args.ver}" arrives literally when the target runs without --args=--ver=<v>;
912
+ // treat that (or a missing arg) as "report current version, change nothing".
913
+ const version =
914
+ versionArg && !versionArg.startsWith("{args") ? versionArg : undefined;
915
+
916
+ if (!name) {
917
+ console.error("usage: update-release-version.mjs <project> <version>");
918
+ process.exit(1);
919
+ }
920
+
921
+ const graph = readCachedProjectGraph();
922
+ const project = graph.nodes[name];
923
+ if (!project) {
924
+ console.error("Unknown project: " + name);
925
+ process.exit(1);
926
+ }
927
+
928
+ const kosJsonPath = resolve(process.cwd(), project.data.root, ".kos.json");
929
+ let kosJson;
930
+ try {
931
+ kosJson = JSON.parse(readFileSync(kosJsonPath, "utf8"));
932
+ } catch {
933
+ console.error("Missing or invalid .kos.json: " + kosJsonPath);
934
+ process.exit(1);
935
+ }
936
+
937
+ if (!version) {
938
+ console.log(name + ": " + kosJson.version + " (no --ver given; unchanged)");
939
+ process.exit(0);
940
+ }
941
+
942
+ const prettierOptions = await prettier.resolveConfig(kosJsonPath);
943
+ const output = await prettier.format(
944
+ JSON.stringify({ ...kosJson, version }, null, 2),
945
+ { ...prettierOptions, parser: "json" }
946
+ );
947
+ writeFileSync(kosJsonPath, output);
948
+ console.log(name + ": version -> " + version);
949
+ `;
563
950
  function appendBarrelExport(codegenFs, indexPath, exportPath) {
564
951
  const exportLine = `export * from '${exportPath}'`;
565
952
  const content = codegenFs.read(indexPath) ?? "";
@@ -955,6 +1342,63 @@ function generateModel(params) {
955
1342
  );
956
1343
  }
957
1344
  }
1345
+ function resolveModelFilePath(codegenFs, query, projects) {
1346
+ const kosConfig = getKosProjectConfiguration(
1347
+ codegenFs,
1348
+ query.modelProject,
1349
+ projects
1350
+ );
1351
+ const internal = !!kosConfig?.generator?.internal;
1352
+ const project = findProjectByName(
1353
+ codegenFs.root,
1354
+ query.modelProject,
1355
+ projects
1356
+ );
1357
+ const sourceRoot = project ? project.sourceRoot || path.join(project.root, "src") : void 0;
1358
+ if (query.modelPath) {
1359
+ return { modelFilePath: query.modelPath, internal, sourceRoot };
1360
+ }
1361
+ if (!project) {
1362
+ throw new Error(
1363
+ `Project not found: ${query.modelProject}. Ensure a project.json exists.`
1364
+ );
1365
+ }
1366
+ const { modelNameDashCase } = normalizeAllValues({
1367
+ modelName: query.modelName
1368
+ });
1369
+ const modelLocation = kosConfig?.generator?.defaults?.model?.folder || "";
1370
+ const modelFilePath = path.join(
1371
+ sourceRoot,
1372
+ modelLocation,
1373
+ modelNameDashCase,
1374
+ `${modelNameDashCase}-model.ts`
1375
+ );
1376
+ if (codegenFs.exists(modelFilePath)) {
1377
+ return { modelFilePath, internal, sourceRoot };
1378
+ }
1379
+ const discovered = findModelFileByName(
1380
+ codegenFs,
1381
+ path.join(sourceRoot, modelLocation),
1382
+ modelNameDashCase
1383
+ );
1384
+ if (discovered) {
1385
+ return { modelFilePath: discovered, internal, sourceRoot };
1386
+ }
1387
+ return { modelFilePath, internal, sourceRoot };
1388
+ }
1389
+ function findModelFileByName(codegenFs, searchRoot, modelNameDashCase) {
1390
+ const fileName = `${modelNameDashCase}-model.ts`;
1391
+ const candidates = codegenFs.listFiles(searchRoot).filter((f) => path.basename(f) === fileName).sort();
1392
+ if (candidates.length === 0) return null;
1393
+ if (candidates.length > 1) {
1394
+ throw new Error(
1395
+ `Model name '${modelNameDashCase}' is ambiguous — multiple files match ${fileName}:
1396
+ ` + candidates.map((c) => ` - ${c}`).join("\n") + `
1397
+ Pass modelPath to pick one.`
1398
+ );
1399
+ }
1400
+ return candidates[0];
1401
+ }
958
1402
  function normalizeAddFutureOptions(codegenFs, options, projects) {
959
1403
  const projectConfiguration = findProjectByName(
960
1404
  codegenFs.root,
@@ -983,9 +1427,12 @@ function normalizeAddFutureOptions(codegenFs, options, projects) {
983
1427
  const nameLowerCase = normalizedValues.modelNameLowerCase;
984
1428
  const projectRoot = projectConfiguration.root;
985
1429
  const sourceRoot = projectConfiguration.sourceRoot || path.join(projectRoot, "src");
986
- const modelLocation = kosConfig?.generator?.defaults?.model?.folder || "";
987
- const modelDirectory = path.join(sourceRoot, modelLocation, nameDashCase);
988
- const modelFilePath = path.join(modelDirectory, `${nameDashCase}-model.ts`);
1430
+ const { modelFilePath } = resolveModelFilePath(
1431
+ codegenFs,
1432
+ { modelName: options.modelName, modelProject: options.modelProject },
1433
+ projects
1434
+ );
1435
+ const modelDirectory = path.dirname(modelFilePath);
989
1436
  const servicesDirectory = path.join(modelDirectory, "services");
990
1437
  const servicesFilePath = codegenFs.exists(servicesDirectory) ? path.join(servicesDirectory, `${nameDashCase}-services.ts`) : void 0;
991
1438
  const registrationFilePath = path.join(
@@ -1008,309 +1455,357 @@ function normalizeAddFutureOptions(codegenFs, options, projects) {
1008
1455
  internal
1009
1456
  };
1010
1457
  }
1011
- class ModelFileTransformer {
1012
- constructor(codegenFs, options) {
1013
- this.codegenFs = codegenFs;
1014
- this.options = options;
1015
- }
1016
- codegenFs;
1017
- options;
1018
- transform() {
1019
- const { modelFilePath } = this.options;
1020
- if (!this.codegenFs.exists(modelFilePath)) {
1021
- throw new Error(`Model file not found: ${modelFilePath}`);
1022
- }
1023
- let content = this.codegenFs.read(modelFilePath);
1024
- content = this.addESLintDisable(content);
1025
- content = this.addImports(content);
1026
- content = this.addServiceImport(content);
1027
- content = this.addInterfaceMerging(content);
1028
- content = this.addDecorator(content);
1029
- content = this.updatePublicType(content);
1030
- content = this.removeLegacySetup(content);
1031
- content = this.addFutureMethod(content);
1032
- if (this.options.futureType === "complete") {
1033
- content = this.addOnFutureUpdateMethod(content);
1034
- }
1035
- this.codegenFs.write(modelFilePath, content);
1036
- }
1037
- addESLintDisable(content) {
1038
- if (content.includes("@typescript-eslint/no-unsafe-declaration-merging")) {
1039
- return content;
1040
- }
1041
- const eslintDisable = "/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */\n";
1042
- return eslintDisable + content;
1043
- }
1044
- addImports(content) {
1045
- const { internal, futureType } = this.options;
1046
- const isComplete = futureType === "complete";
1047
- 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"/;
1048
- const kosModelMatch = content.match(kosModelImportRegex);
1049
- if (kosModelMatch) {
1050
- const existingImportsStr = kosModelMatch[1] || "";
1051
- const newImports = [
1052
- "kosFuture",
1053
- "kosFutureAware",
1054
- isComplete ? "KosFutureAwareFull" : "KosFutureAwareMinimal"
1055
- ];
1056
- const existingImports = existingImportsStr.split(",").map((s) => s.trim()).filter(Boolean);
1057
- const importsToRemove = [
1058
- "setupCompleteFutureSupport",
1059
- "setupMinimalFutureSupport"
1060
- ];
1061
- const cleanedImports = existingImports.filter(
1062
- (imp) => !importsToRemove.includes(imp)
1063
- );
1064
- const importsToAdd = newImports.filter(
1065
- (imp) => !cleanedImports.includes(imp)
1066
- );
1067
- const allImports = [...cleanedImports, ...importsToAdd];
1068
- const newImportLine = internal ? `import { ${allImports.join(
1069
- ", "
1070
- )} } from "../../../core/core/decorators"` : `import { ${allImports.join(", ")} } from "@kosdev-code/kos-ui-sdk"`;
1071
- content = content.replace(kosModelImportRegex, newImportLine);
1072
- }
1073
- const typeImportsBase = internal ? "../../../models/types/future-interfaces" : "@kosdev-code/kos-ui-sdk";
1074
- const futureTypeImports = ["ExternalFutureInterface", "IFutureModel"];
1075
- const typeImportRegex = internal ? /import type {([^}]*)} from "\.\.\/\.\.\/\.\.\/models\/types\/future-interfaces"/ : /import type {([^}]*)} from "@kosdev-code\/kos-ui-sdk"/;
1076
- const typeImportMatch = content.match(typeImportRegex);
1077
- if (typeImportMatch) {
1078
- const existingTypes = typeImportMatch[1] || "";
1079
- const existingTypesList = existingTypes.split(",").map((s) => s.trim()).filter(Boolean);
1080
- const typesToRemove = [
1081
- "FutureAwareContainer",
1082
- "FutureHandlerContainer",
1083
- "FutureStateAccessor",
1084
- "FutureUpdateHandler"
1085
- ];
1086
- const cleanedTypes = existingTypesList.filter(
1087
- (type) => !typesToRemove.includes(type)
1088
- );
1089
- const typesToAdd = futureTypeImports.filter(
1090
- (type) => !cleanedTypes.includes(type)
1091
- );
1092
- const allTypes = [...cleanedTypes, ...typesToAdd].join(", ");
1093
- const newTypeImport = `import type { ${allTypes} } from "${typeImportsBase}"`;
1094
- content = content.replace(typeImportRegex, newTypeImport);
1095
- } else {
1096
- const importLines = content.split("\n");
1097
- const lastImportIndex = importLines.findLastIndex(
1098
- (line) => line.trim().startsWith("import")
1099
- );
1100
- if (lastImportIndex >= 0) {
1101
- const newTypeImport = `import type { ${futureTypeImports.join(
1102
- ", "
1103
- )} } from "${typeImportsBase}";`;
1104
- importLines.splice(lastImportIndex + 1, 0, newTypeImport);
1105
- content = importLines.join("\n");
1106
- }
1107
- }
1108
- return content;
1109
- }
1110
- addServiceImport(content) {
1111
- const { nameProperCase, updateServices } = this.options;
1112
- if (!updateServices) {
1113
- return content;
1114
- }
1115
- if (content.includes(`${nameProperCase}OperationProgress`)) {
1116
- return content;
1117
- }
1118
- const servicesImportRegex = /import\s*{([^}]*)}\s*from\s*["']\.\/services["'];?/s;
1119
- const servicesMatch = content.match(servicesImportRegex);
1120
- if (servicesMatch) {
1121
- const existingImports = servicesMatch[1];
1122
- const cleanedImports = existingImports.split(",").map((s) => s.trim()).filter(Boolean);
1123
- cleanedImports.push(`${nameProperCase}OperationProgress`);
1124
- const newImport = `import { ${cleanedImports.join(
1125
- ", "
1126
- )} } from "./services";`;
1127
- content = content.replace(servicesImportRegex, newImport);
1128
- } else {
1129
- const typesImportRegex = /import\s+type\s+{[^}]*}\s+from\s+["']\.\/types["'];?/;
1130
- const typesMatch = content.match(typesImportRegex);
1131
- if (typesMatch) {
1132
- const newImport = `
1133
- import type { ${nameProperCase}OperationProgress } from "./services";`;
1134
- content = content.replace(typesMatch[0], typesMatch[0] + newImport);
1135
- }
1136
- }
1137
- return content;
1138
- }
1139
- addInterfaceMerging(content) {
1140
- const { nameProperCase, futureType } = this.options;
1141
- const isComplete = futureType === "complete";
1142
- const interfaceType = isComplete ? "KosFutureAwareFull" : "KosFutureAwareMinimal";
1143
- const progressType = this.options.updateServices ? `${nameProperCase}OperationProgress` : "Record<string, unknown>";
1144
- const classRegex = new RegExp(
1145
- `(@kosModel[^\\n]*\\n)([^\\n]*export\\s+class\\s+${nameProperCase}ModelImpl)`,
1146
- "m"
1147
- );
1148
- const classMatch = content.match(classRegex);
1149
- if (classMatch) {
1150
- const interfaceRegex = new RegExp(
1151
- `interface\\s+${nameProperCase}ModelImpl\\s+extends`
1152
- );
1153
- if (!content.match(interfaceRegex)) {
1154
- const interfaceMerging = `
1155
- // Interface merging for Future Container type safety
1156
- // eslint-disable-next-line @typescript-eslint/no-empty-interface
1157
- export interface ${nameProperCase}ModelImpl extends ${interfaceType}<${progressType}> {}
1158
-
1159
- `;
1160
- content = content.replace(
1161
- classMatch[0],
1162
- interfaceMerging + classMatch[0]
1163
- );
1164
- }
1165
- }
1166
- return content;
1167
- }
1168
- addDecorator(content) {
1169
- const { nameProperCase, futureType } = this.options;
1170
- const isComplete = futureType === "complete";
1171
- const classRegex = new RegExp(
1172
- `(@kosModel[^\\n]*\\n)((?:@[^\\n]*\\n)*)([^\\n]*export\\s+class\\s+${nameProperCase}ModelImpl)`,
1173
- "m"
1174
- );
1175
- const classMatch = content.match(classRegex);
1176
- if (classMatch) {
1177
- if (!classMatch[2].includes("@kosFutureAware")) {
1178
- const decoratorOptions = isComplete ? "" : "{ mode: 'minimal' }";
1179
- const futureDecorator = `@kosFutureAware(${decoratorOptions})
1180
- `;
1181
- content = content.replace(
1182
- classMatch[0],
1183
- classMatch[1] + classMatch[2] + futureDecorator + classMatch[3]
1184
- );
1185
- }
1186
- }
1187
- return content;
1188
- }
1189
- updatePublicType(content) {
1190
- const { nameProperCase, updateServices } = this.options;
1191
- const progressType = updateServices ? `${nameProperCase}OperationProgress` : "Record<string, unknown>";
1192
- const typeRegex = new RegExp(
1193
- `export\\s+type\\s+${nameProperCase}Model\\s*=\\s*PublicModelInterface<${nameProperCase}ModelImpl>([^;]*);`,
1194
- "s"
1195
- );
1196
- const typeMatch = content.match(typeRegex);
1197
- if (typeMatch) {
1198
- if (!typeMatch[1].includes("ExternalFutureInterface")) {
1199
- const newType = `export type ${nameProperCase}Model = PublicModelInterface<${nameProperCase}ModelImpl> & ExternalFutureInterface<${progressType}>;`;
1200
- content = content.replace(typeMatch[0], newType);
1201
- }
1202
- }
1203
- return content;
1204
- }
1205
- removeLegacySetup(content) {
1206
- const setupRegex = /\s*setup(Complete|Minimal)FutureSupport\(this\);?\s*/g;
1207
- content = content.replace(setupRegex, "");
1208
- const propertyRegex = /\s*(public|private|protected)?\s*(declare\s+)?futureHandler[!?]?:\s*FutureAwareContainer[^;]*;\s*/g;
1209
- content = content.replace(propertyRegex, "");
1210
- const futurePropertyRegex = /\s*(public|private|protected)?\s*(declare\s+)?future\??:\s*IFutureModel[^;]*;\s*/g;
1211
- content = content.replace(futurePropertyRegex, "");
1212
- const implementsRegex = new RegExp(
1213
- `(implements\\s+[^{]*?)\\s*,?\\s*(FutureUpdateHandler|FutureHandlerContainer|FutureStateAccessor)`,
1214
- "g"
1215
- );
1216
- content = content.replace(implementsRegex, "$1");
1217
- content = content.replace(/,\s*,/g, ",");
1218
- content = content.replace(/implements\s*,/g, "implements");
1219
- return content;
1458
+ function transformSourceFile(codegenFs, filePath, mutate) {
1459
+ const content = codegenFs.read(filePath);
1460
+ if (content === null) {
1461
+ throw new Error(`File not found: ${filePath}`);
1220
1462
  }
1221
- addFutureMethod(content) {
1222
- const { nameProperCase } = this.options;
1223
- if (content.includes("@kosFuture()")) {
1224
- return content;
1463
+ const project = new Project({
1464
+ useInMemoryFileSystem: true,
1465
+ manipulationSettings: {
1466
+ indentationText: IndentationText.TwoSpaces,
1467
+ quoteKind: QuoteKind.Double
1225
1468
  }
1226
- const classRegex = new RegExp(
1227
- `class\\s+${nameProperCase}ModelImpl[^{]*{([\\s\\S]*)}\\s*$`,
1228
- "m"
1229
- );
1230
- const classMatch = content.match(classRegex);
1231
- if (classMatch) {
1232
- const methodCode = `
1233
- /**
1234
- * Placeholder method for Future operations
1235
- * Replace this with your actual long-running operation
1236
- */
1237
- @kosFuture()
1238
- async performLongRunningOperation(): Promise<void> {
1239
- // TODO: Implement your long-running operation here
1240
- // This method should use a service that returns a Future for progress tracking
1241
-
1242
- this.logger.debug(\`Starting long-running operation for \${this.id}\`);
1243
-
1244
- // Example implementation pattern using services:
1245
- // import { perform${nameProperCase}Operation } from './services';
1246
- //
1247
- // const future = await perform${nameProperCase}Operation();
1248
- // return this.futureHandler.setFuture(future);
1249
-
1250
- // Placeholder that doesn't actually do anything
1251
- await new Promise(resolve => setTimeout(resolve, 1000));
1252
-
1253
- this.logger.debug(\`Completed long-running operation for \${this.id}\`);
1469
+ });
1470
+ const sourceFile = project.createSourceFile(filePath, content, {
1471
+ overwrite: true
1472
+ });
1473
+ mutate(sourceFile);
1474
+ codegenFs.write(filePath, sourceFile.getFullText());
1475
+ }
1476
+ function ensureNamedImport(sourceFile, moduleSpecifier, names) {
1477
+ const decls = sourceFile.getImportDeclarations().filter((d) => d.getModuleSpecifierValue() === moduleSpecifier);
1478
+ const existing = /* @__PURE__ */ new Set();
1479
+ for (const d of decls) {
1480
+ for (const n of d.getNamedImports()) existing.add(n.getName());
1254
1481
  }
1255
- `;
1256
- const classContent = classMatch[1];
1257
- const lastBraceIndex = classContent.lastIndexOf("}");
1258
- if (lastBraceIndex >= 0) {
1259
- const updatedContent = classContent.slice(0, lastBraceIndex) + methodCode + classContent.slice(lastBraceIndex);
1260
- content = content.replace(
1261
- classMatch[0],
1262
- `class ${nameProperCase}ModelImpl${classMatch[0].match(/[^{]*/)?.[0]}{${updatedContent}}`
1263
- );
1264
- }
1265
- }
1266
- return content;
1482
+ let target = decls.find((d) => !d.isTypeOnly());
1483
+ if (!target) {
1484
+ target = sourceFile.addImportDeclaration({ moduleSpecifier });
1267
1485
  }
1268
- addOnFutureUpdateMethod(content) {
1269
- const { nameProperCase, updateServices } = this.options;
1270
- const progressType = updateServices ? `${nameProperCase}OperationProgress` : "Record<string, unknown>";
1271
- if (content.includes("onFutureUpdate")) {
1272
- return content;
1273
- }
1274
- const futureMethodRegex = /@kosFuture\(\)[^}]*}/s;
1275
- const futureMethodMatch = content.match(futureMethodRegex);
1276
- if (futureMethodMatch) {
1277
- const methodCode = `
1278
-
1279
- /**
1280
- * Optional: Custom Future update handling
1281
- * Called whenever the Future state changes (progress, status, completion, etc.)
1282
- */
1283
- onFutureUpdate?(update: IFutureModel<${progressType}>): void {
1284
- // Add custom Future update logic here
1285
- // Examples:
1286
- // - Log progress milestones
1287
- // - Update derived state based on progress
1288
- // - Handle specific error conditions
1289
- // - Trigger notifications at certain thresholds
1290
-
1291
- this.logger.debug(\`Future update for \${this.id}:\`, {
1292
- progress: update.progress,
1293
- status: update.status,
1294
- endState: update.endState,
1295
- clientData: update.clientData
1296
- });
1297
- }`;
1298
- content = content.replace(
1299
- futureMethodMatch[0],
1300
- futureMethodMatch[0] + methodCode
1301
- );
1302
- }
1303
- return content;
1486
+ for (const { name, isTypeOnly } of names) {
1487
+ if (existing.has(name)) continue;
1488
+ target.addNamedImport({ name, isTypeOnly: !!isTypeOnly });
1489
+ existing.add(name);
1304
1490
  }
1305
1491
  }
1306
- class ServiceFileTransformer {
1307
- constructor(codegenFs, options) {
1308
- this.codegenFs = codegenFs;
1309
- this.options = options;
1310
- }
1311
- codegenFs;
1312
- options;
1313
- transform() {
1492
+ function resolveSdkModuleSpecifier(sourceFile) {
1493
+ const decl = sourceFile.getImportDeclarations().find((d) => d.getNamedImports().some((n) => n.getName() === "kosModel"));
1494
+ return decl?.getModuleSpecifierValue() ?? "@kosdev-code/kos-ui-sdk";
1495
+ }
1496
+ function getModelClass(sourceFile, preferName) {
1497
+ const classes = sourceFile.getClasses();
1498
+ const byName = preferName ? classes.find((c) => c.getName() === preferName) : void 0;
1499
+ if (byName) return byName;
1500
+ const impl = classes.find((c) => /ModelImpl$/.test(c.getName() ?? ""));
1501
+ if (impl) return impl;
1502
+ const exported = classes.find((c) => c.isExported());
1503
+ if (exported) return exported;
1504
+ if (classes.length > 0) return classes[0];
1505
+ throw new Error("No class declaration found in model file.");
1506
+ }
1507
+ function addClassDecorator(cls, name, opts) {
1508
+ if (cls.getDecorator(name)) return;
1509
+ const typeArgs = opts?.typeArgs?.length ? `<${opts.typeArgs.join(", ")}>` : "";
1510
+ const args = opts?.argsText ?? "";
1511
+ const decorators = cls.getDecorators();
1512
+ const kosModelIdx = decorators.findIndex((d) => d.getName() === "kosModel");
1513
+ const insertIdx = kosModelIdx >= 0 ? kosModelIdx + 1 : decorators.length;
1514
+ cls.insertDecorator(insertIdx, {
1515
+ name: `${name}${typeArgs}`,
1516
+ arguments: args ? [args] : []
1517
+ });
1518
+ }
1519
+ function ensureDeclarationMerge(sourceFile, interfaceName, extendsExpr, typeParameters = []) {
1520
+ const baseType = extendsExpr.split("<")[0].trim();
1521
+ let iface = sourceFile.getInterface(interfaceName);
1522
+ if (iface) {
1523
+ const already = iface.getExtends().some((e) => e.getText().split("<")[0].trim() === baseType);
1524
+ if (!already) iface.addExtends(extendsExpr);
1525
+ return;
1526
+ }
1527
+ iface = sourceFile.addInterface({
1528
+ name: interfaceName,
1529
+ isExported: true,
1530
+ typeParameters,
1531
+ extends: [extendsExpr]
1532
+ });
1533
+ sourceFile.insertText(
1534
+ iface.getStart(),
1535
+ "// eslint-disable-next-line @typescript-eslint/no-empty-interface\n"
1536
+ );
1537
+ }
1538
+ function ensureFileEslintDisable(sourceFile, rule) {
1539
+ if (sourceFile.getFullText().includes(rule)) return;
1540
+ sourceFile.insertText(0, `/* eslint-disable ${rule} */
1541
+ `);
1542
+ }
1543
+ function addDecoratedMethod(cls, spec) {
1544
+ if (cls.getMethod(spec.name)) return false;
1545
+ cls.addMethod({
1546
+ name: spec.name,
1547
+ isAsync: spec.isAsync,
1548
+ returnType: spec.returnType,
1549
+ parameters: spec.parameters?.map((p) => {
1550
+ return { name: p.name, type: p.type };
1551
+ }),
1552
+ statements: spec.statements,
1553
+ decorators: [
1554
+ {
1555
+ name: spec.decoratorName,
1556
+ arguments: spec.decoratorArgsText ? [spec.decoratorArgsText] : []
1557
+ }
1558
+ ]
1559
+ });
1560
+ return true;
1561
+ }
1562
+ function propertyInsertIndex(cls) {
1563
+ const props = cls.getProperties();
1564
+ if (props.length === 0) return 0;
1565
+ return props[props.length - 1].getChildIndex() + 1;
1566
+ }
1567
+ function addPlainProperty(cls, spec) {
1568
+ if (cls.getProperty(spec.name)) return false;
1569
+ cls.insertProperty(propertyInsertIndex(cls), {
1570
+ name: spec.name,
1571
+ type: spec.type,
1572
+ initializer: spec.initializer,
1573
+ isReadonly: !!spec.readonly,
1574
+ scope: spec.scope ? SCOPE_MAP[spec.scope] : void 0,
1575
+ hasExclamationToken: spec.initializer ? false : !!spec.hasExclamation
1576
+ });
1577
+ return true;
1578
+ }
1579
+ const SCOPE_MAP = {
1580
+ private: Scope.Private,
1581
+ protected: Scope.Protected,
1582
+ public: Scope.Public
1583
+ };
1584
+ function addDecoratedProperty(cls, spec) {
1585
+ if (cls.getProperty(spec.name)) return false;
1586
+ cls.insertProperty(propertyInsertIndex(cls), {
1587
+ name: spec.name,
1588
+ type: spec.type,
1589
+ initializer: spec.initializer,
1590
+ scope: spec.scope ? SCOPE_MAP[spec.scope] : void 0,
1591
+ hasExclamationToken: spec.initializer ? false : !!spec.hasExclamation,
1592
+ decorators: [
1593
+ spec.bare && !spec.decoratorArgsText ? { name: spec.decoratorName } : {
1594
+ name: spec.decoratorName,
1595
+ arguments: spec.decoratorArgsText ? [spec.decoratorArgsText] : []
1596
+ }
1597
+ ]
1598
+ });
1599
+ return true;
1600
+ }
1601
+ function addGetter(cls, spec) {
1602
+ if (cls.getGetAccessor(spec.name)) return false;
1603
+ const ctor = cls.getConstructors()[0];
1604
+ const index = ctor ? ctor.getChildIndex() + 1 : propertyInsertIndex(cls);
1605
+ cls.insertGetAccessor(index, {
1606
+ name: spec.name,
1607
+ returnType: spec.returnType,
1608
+ statements: spec.statements ?? "// TODO: derive and return the computed value"
1609
+ });
1610
+ return true;
1611
+ }
1612
+ const LEGACY_IMPORTS = /* @__PURE__ */ new Set([
1613
+ "setupCompleteFutureSupport",
1614
+ "setupMinimalFutureSupport",
1615
+ "FutureAwareContainer",
1616
+ "FutureHandlerContainer",
1617
+ "FutureStateAccessor",
1618
+ "FutureUpdateHandler"
1619
+ ]);
1620
+ const LEGACY_IMPLEMENTS = /* @__PURE__ */ new Set([
1621
+ "FutureUpdateHandler",
1622
+ "FutureHandlerContainer",
1623
+ "FutureStateAccessor"
1624
+ ]);
1625
+ class ModelFileTransformer {
1626
+ constructor(codegenFs, options) {
1627
+ this.codegenFs = codegenFs;
1628
+ this.options = options;
1629
+ }
1630
+ codegenFs;
1631
+ options;
1632
+ get progressType() {
1633
+ const { nameProperCase, updateServices } = this.options;
1634
+ return updateServices ? `${nameProperCase}OperationProgress` : "Record<string, unknown>";
1635
+ }
1636
+ transform() {
1637
+ const { modelFilePath } = this.options;
1638
+ if (!this.codegenFs.exists(modelFilePath)) {
1639
+ throw new Error(`Model file not found: ${modelFilePath}`);
1640
+ }
1641
+ transformSourceFile(this.codegenFs, modelFilePath, (sf) => {
1642
+ this.removeLegacyImports(sf);
1643
+ this.addImports(sf);
1644
+ const cls = getModelClass(sf, `${this.options.nameProperCase}ModelImpl`);
1645
+ this.removeLegacyClassMembers(cls);
1646
+ this.addDecorator(cls);
1647
+ this.addFutureMethod(cls);
1648
+ if (this.options.futureType === "complete") {
1649
+ this.addOnFutureUpdateMethod(cls);
1650
+ }
1651
+ this.updatePublicType(sf);
1652
+ ensureDeclarationMerge(
1653
+ sf,
1654
+ `${this.options.nameProperCase}ModelImpl`,
1655
+ `${this.options.futureType === "complete" ? "KosFutureAwareFull" : "KosFutureAwareMinimal"}<${this.progressType}>`
1656
+ );
1657
+ ensureFileEslintDisable(
1658
+ sf,
1659
+ "@typescript-eslint/no-unsafe-declaration-merging"
1660
+ );
1661
+ });
1662
+ }
1663
+ removeLegacyImports(sf) {
1664
+ for (const decl of sf.getImportDeclarations()) {
1665
+ for (const named of decl.getNamedImports()) {
1666
+ if (LEGACY_IMPORTS.has(named.getName())) named.remove();
1667
+ }
1668
+ if (decl.getNamedImports().length === 0 && !decl.getDefaultImport() && !decl.getNamespaceImport()) {
1669
+ decl.remove();
1670
+ }
1671
+ }
1672
+ }
1673
+ addImports(sf) {
1674
+ const { internal, futureType, updateServices, nameProperCase } = this.options;
1675
+ const isComplete = futureType === "complete";
1676
+ const sdkSpec = resolveSdkModuleSpecifier(sf);
1677
+ ensureNamedImport(sf, sdkSpec, [
1678
+ { name: "kosFuture" },
1679
+ { name: "kosFutureAware" },
1680
+ {
1681
+ name: isComplete ? "KosFutureAwareFull" : "KosFutureAwareMinimal",
1682
+ isTypeOnly: true
1683
+ }
1684
+ ]);
1685
+ const typeSpec = internal ? "../../../models/types/future-interfaces" : sdkSpec;
1686
+ ensureNamedImport(sf, typeSpec, [
1687
+ { name: "ExternalFutureInterface", isTypeOnly: true },
1688
+ ...isComplete ? [{ name: "IFutureModel", isTypeOnly: true }] : []
1689
+ ]);
1690
+ if (updateServices) {
1691
+ ensureNamedImport(sf, "./services", [
1692
+ { name: `${nameProperCase}OperationProgress`, isTypeOnly: true }
1693
+ ]);
1694
+ }
1695
+ }
1696
+ removeLegacyClassMembers(cls) {
1697
+ for (const ctor of cls.getConstructors()) {
1698
+ for (const stmt of ctor.getStatements()) {
1699
+ if (/setup(Complete|Minimal)FutureSupport\s*\(\s*this\s*\)/.test(
1700
+ stmt.getText()
1701
+ )) {
1702
+ stmt.remove();
1703
+ }
1704
+ }
1705
+ }
1706
+ const futureHandler = cls.getProperty("futureHandler");
1707
+ if (futureHandler?.getTypeNode()?.getText().includes("FutureAwareContainer")) {
1708
+ futureHandler.remove();
1709
+ }
1710
+ const future = cls.getProperty("future");
1711
+ if (future?.getTypeNode()?.getText().includes("IFutureModel")) {
1712
+ future.remove();
1713
+ }
1714
+ const impls = cls.getImplements();
1715
+ for (let i = impls.length - 1; i >= 0; i--) {
1716
+ const base = impls[i].getText().split("<")[0].trim();
1717
+ if (LEGACY_IMPLEMENTS.has(base)) cls.removeImplements(i);
1718
+ }
1719
+ }
1720
+ addDecorator(cls) {
1721
+ const argsText = this.options.futureType === "complete" ? "" : `{ mode: "minimal" }`;
1722
+ addClassDecorator(cls, "kosFutureAware", { argsText });
1723
+ }
1724
+ updatePublicType(sf) {
1725
+ const { nameProperCase } = this.options;
1726
+ const alias = sf.getTypeAlias(`${nameProperCase}Model`);
1727
+ if (!alias) return;
1728
+ const text = alias.getTypeNode()?.getText() ?? "";
1729
+ if (!text.includes(`PublicModelInterface<${nameProperCase}ModelImpl>`) || text.includes("ExternalFutureInterface")) {
1730
+ return;
1731
+ }
1732
+ alias.setType(
1733
+ `PublicModelInterface<${nameProperCase}ModelImpl> & ExternalFutureInterface<${this.progressType}>`
1734
+ );
1735
+ }
1736
+ addFutureMethod(cls) {
1737
+ const { nameProperCase } = this.options;
1738
+ const hasFutureMethod = cls.getMethods().some((m) => m.getDecorator("kosFuture"));
1739
+ if (hasFutureMethod || cls.getMethod("performLongRunningOperation")) return;
1740
+ cls.addMethod({
1741
+ name: "performLongRunningOperation",
1742
+ isAsync: true,
1743
+ returnType: "Promise<void>",
1744
+ decorators: [{ name: "kosFuture", arguments: [] }],
1745
+ docs: [
1746
+ {
1747
+ description: "Placeholder method for Future operations\nReplace this with your actual long-running operation"
1748
+ }
1749
+ ],
1750
+ statements: [
1751
+ "// TODO: Implement your long-running operation here",
1752
+ "// This method should use a service that returns a Future for progress tracking",
1753
+ "",
1754
+ "this.logger.debug(`Starting long-running operation for ${this.id}`);",
1755
+ "",
1756
+ "// Example implementation pattern using services:",
1757
+ `// import { perform${nameProperCase}Operation } from './services';`,
1758
+ "//",
1759
+ `// const future = await perform${nameProperCase}Operation();`,
1760
+ "// return this.futureHandler.setFuture(future);",
1761
+ "",
1762
+ "// Placeholder that doesn't actually do anything",
1763
+ "await new Promise((resolve) => setTimeout(resolve, 1000));",
1764
+ "",
1765
+ "this.logger.debug(`Completed long-running operation for ${this.id}`);"
1766
+ ]
1767
+ });
1768
+ }
1769
+ addOnFutureUpdateMethod(cls) {
1770
+ if (cls.getMethod("onFutureUpdate")) return;
1771
+ cls.addMethod({
1772
+ name: "onFutureUpdate",
1773
+ hasQuestionToken: true,
1774
+ returnType: "void",
1775
+ parameters: [
1776
+ { name: "update", type: `IFutureModel<${this.progressType}>` }
1777
+ ],
1778
+ docs: [
1779
+ {
1780
+ description: "Optional: Custom Future update handling\nCalled whenever the Future state changes (progress, status, completion, etc.)"
1781
+ }
1782
+ ],
1783
+ statements: [
1784
+ "// Add custom Future update logic here",
1785
+ "// Examples:",
1786
+ "// - Log progress milestones",
1787
+ "// - Update derived state based on progress",
1788
+ "// - Handle specific error conditions",
1789
+ "// - Trigger notifications at certain thresholds",
1790
+ "",
1791
+ "this.logger.debug(`Future update for ${this.id}:`, {",
1792
+ " progress: update.progress,",
1793
+ " status: update.status,",
1794
+ " endState: update.endState,",
1795
+ " clientData: update.clientData,",
1796
+ "});"
1797
+ ]
1798
+ });
1799
+ }
1800
+ }
1801
+ class ServiceFileTransformer {
1802
+ constructor(codegenFs, options) {
1803
+ this.codegenFs = codegenFs;
1804
+ this.options = options;
1805
+ }
1806
+ codegenFs;
1807
+ options;
1808
+ transform() {
1314
1809
  const { servicesFilePath } = this.options;
1315
1810
  if (!servicesFilePath || !this.codegenFs.exists(servicesFilePath)) {
1316
1811
  this.createServicesFile();
@@ -1483,9 +1978,16 @@ class RegistrationFileTransformer {
1483
1978
  logger.warn("Registration file not found, skipping registration updates");
1484
1979
  return;
1485
1980
  }
1486
- let content = this.codegenFs.read(registrationFilePath);
1981
+ const original = this.codegenFs.read(registrationFilePath);
1982
+ let content = original;
1487
1983
  content = this.addTypeCast(content);
1488
1984
  content = this.updateDocumentation(content);
1985
+ if (content === original) {
1986
+ logger.info(
1987
+ "Registration file has no legacy future patterns — leaving it untouched"
1988
+ );
1989
+ return;
1990
+ }
1489
1991
  this.codegenFs.write(registrationFilePath, content);
1490
1992
  }
1491
1993
  addTypeCast(content) {
@@ -1553,7 +2055,7 @@ function addFutureToModel(codegenFs, options, projects) {
1553
2055
  }
1554
2056
  if (normalized.registrationFilePath) {
1555
2057
  logger.info(
1556
- `Would modify registration file: ${normalized.registrationFilePath}`
2058
+ `Would update registration file (only if legacy patterns are present): ${normalized.registrationFilePath}`
1557
2059
  );
1558
2060
  }
1559
2061
  return;
@@ -1604,191 +2106,6 @@ function addFutureToModel(codegenFs, options, projects) {
1604
2106
  throw error;
1605
2107
  }
1606
2108
  }
1607
- function resolveModelFilePath(codegenFs, query, projects) {
1608
- const kosConfig = getKosProjectConfiguration(
1609
- codegenFs,
1610
- query.modelProject,
1611
- projects
1612
- );
1613
- const internal = !!kosConfig?.generator?.internal;
1614
- const project = findProjectByName(
1615
- codegenFs.root,
1616
- query.modelProject,
1617
- projects
1618
- );
1619
- const sourceRoot = project ? project.sourceRoot || path.join(project.root, "src") : void 0;
1620
- if (query.modelPath) {
1621
- return { modelFilePath: query.modelPath, internal, sourceRoot };
1622
- }
1623
- if (!project) {
1624
- throw new Error(
1625
- `Project not found: ${query.modelProject}. Ensure a project.json exists.`
1626
- );
1627
- }
1628
- const { modelNameDashCase } = normalizeAllValues({
1629
- modelName: query.modelName
1630
- });
1631
- const modelLocation = kosConfig?.generator?.defaults?.model?.folder || "";
1632
- const modelFilePath = path.join(
1633
- sourceRoot,
1634
- modelLocation,
1635
- modelNameDashCase,
1636
- `${modelNameDashCase}-model.ts`
1637
- );
1638
- return { modelFilePath, internal, sourceRoot };
1639
- }
1640
- function transformSourceFile(codegenFs, filePath, mutate) {
1641
- const content = codegenFs.read(filePath);
1642
- if (content === null) {
1643
- throw new Error(`File not found: ${filePath}`);
1644
- }
1645
- const project = new Project({
1646
- useInMemoryFileSystem: true,
1647
- manipulationSettings: {
1648
- indentationText: IndentationText.TwoSpaces,
1649
- quoteKind: QuoteKind.Double
1650
- }
1651
- });
1652
- const sourceFile = project.createSourceFile(filePath, content, {
1653
- overwrite: true
1654
- });
1655
- mutate(sourceFile);
1656
- codegenFs.write(filePath, sourceFile.getFullText());
1657
- }
1658
- function ensureNamedImport(sourceFile, moduleSpecifier, names) {
1659
- const decls = sourceFile.getImportDeclarations().filter((d) => d.getModuleSpecifierValue() === moduleSpecifier);
1660
- const existing = /* @__PURE__ */ new Set();
1661
- for (const d of decls) {
1662
- for (const n of d.getNamedImports()) existing.add(n.getName());
1663
- }
1664
- let target = decls.find((d) => !d.isTypeOnly());
1665
- if (!target) {
1666
- target = sourceFile.addImportDeclaration({ moduleSpecifier });
1667
- }
1668
- for (const { name, isTypeOnly } of names) {
1669
- if (existing.has(name)) continue;
1670
- target.addNamedImport({ name, isTypeOnly: !!isTypeOnly });
1671
- existing.add(name);
1672
- }
1673
- }
1674
- function resolveSdkModuleSpecifier(sourceFile) {
1675
- const decl = sourceFile.getImportDeclarations().find((d) => d.getNamedImports().some((n) => n.getName() === "kosModel"));
1676
- return decl?.getModuleSpecifierValue() ?? "@kosdev-code/kos-ui-sdk";
1677
- }
1678
- function getModelClass(sourceFile, preferName) {
1679
- const classes = sourceFile.getClasses();
1680
- const impl = classes.find((c) => /ModelImpl$/.test(c.getName() ?? ""));
1681
- if (impl) return impl;
1682
- const exported = classes.find((c) => c.isExported());
1683
- if (exported) return exported;
1684
- if (classes.length > 0) return classes[0];
1685
- throw new Error("No class declaration found in model file.");
1686
- }
1687
- function addClassDecorator(cls, name, opts) {
1688
- if (cls.getDecorator(name)) return;
1689
- const typeArgs = opts?.typeArgs?.length ? `<${opts.typeArgs.join(", ")}>` : "";
1690
- const args = opts?.argsText ?? "";
1691
- const decorators = cls.getDecorators();
1692
- const kosModelIdx = decorators.findIndex((d) => d.getName() === "kosModel");
1693
- const insertIdx = kosModelIdx >= 0 ? kosModelIdx + 1 : decorators.length;
1694
- cls.insertDecorator(insertIdx, {
1695
- name: `${name}${typeArgs}`,
1696
- arguments: args ? [args] : []
1697
- });
1698
- }
1699
- function ensureDeclarationMerge(sourceFile, interfaceName, extendsExpr, typeParameters = []) {
1700
- const baseType = extendsExpr.split("<")[0].trim();
1701
- let iface = sourceFile.getInterface(interfaceName);
1702
- if (iface) {
1703
- const already = iface.getExtends().some((e) => e.getText().split("<")[0].trim() === baseType);
1704
- if (!already) iface.addExtends(extendsExpr);
1705
- return;
1706
- }
1707
- iface = sourceFile.addInterface({
1708
- name: interfaceName,
1709
- isExported: true,
1710
- typeParameters,
1711
- extends: [extendsExpr]
1712
- });
1713
- sourceFile.insertText(
1714
- iface.getStart(),
1715
- "// eslint-disable-next-line @typescript-eslint/no-empty-interface\n"
1716
- );
1717
- }
1718
- function ensureFileEslintDisable(sourceFile, rule) {
1719
- if (sourceFile.getFullText().includes(rule)) return;
1720
- sourceFile.insertText(0, `/* eslint-disable ${rule} */
1721
- `);
1722
- }
1723
- function addDecoratedMethod(cls, spec) {
1724
- if (cls.getMethod(spec.name)) return false;
1725
- cls.addMethod({
1726
- name: spec.name,
1727
- isAsync: spec.isAsync,
1728
- returnType: spec.returnType,
1729
- parameters: spec.parameters?.map((p) => {
1730
- return { name: p.name, type: p.type };
1731
- }),
1732
- statements: spec.statements,
1733
- decorators: [
1734
- {
1735
- name: spec.decoratorName,
1736
- arguments: spec.decoratorArgsText ? [spec.decoratorArgsText] : []
1737
- }
1738
- ]
1739
- });
1740
- return true;
1741
- }
1742
- function propertyInsertIndex(cls) {
1743
- const props = cls.getProperties();
1744
- if (props.length === 0) return 0;
1745
- return props[props.length - 1].getChildIndex() + 1;
1746
- }
1747
- function addPlainProperty(cls, spec) {
1748
- if (cls.getProperty(spec.name)) return false;
1749
- cls.insertProperty(propertyInsertIndex(cls), {
1750
- name: spec.name,
1751
- type: spec.type,
1752
- initializer: spec.initializer,
1753
- isReadonly: !!spec.readonly,
1754
- scope: spec.scope ? SCOPE_MAP[spec.scope] : void 0,
1755
- hasExclamationToken: spec.initializer ? false : !!spec.hasExclamation
1756
- });
1757
- return true;
1758
- }
1759
- const SCOPE_MAP = {
1760
- private: Scope.Private,
1761
- protected: Scope.Protected,
1762
- public: Scope.Public
1763
- };
1764
- function addDecoratedProperty(cls, spec) {
1765
- if (cls.getProperty(spec.name)) return false;
1766
- cls.insertProperty(propertyInsertIndex(cls), {
1767
- name: spec.name,
1768
- type: spec.type,
1769
- initializer: spec.initializer,
1770
- scope: spec.scope ? SCOPE_MAP[spec.scope] : void 0,
1771
- hasExclamationToken: spec.initializer ? false : !!spec.hasExclamation,
1772
- decorators: [
1773
- spec.bare && !spec.decoratorArgsText ? { name: spec.decoratorName } : {
1774
- name: spec.decoratorName,
1775
- arguments: spec.decoratorArgsText ? [spec.decoratorArgsText] : []
1776
- }
1777
- ]
1778
- });
1779
- return true;
1780
- }
1781
- function addGetter(cls, spec) {
1782
- if (cls.getGetAccessor(spec.name)) return false;
1783
- const ctor = cls.getConstructors()[0];
1784
- const index = ctor ? ctor.getChildIndex() + 1 : propertyInsertIndex(cls);
1785
- cls.insertGetAccessor(index, {
1786
- name: spec.name,
1787
- returnType: spec.returnType,
1788
- statements: spec.statements ?? "// TODO: derive and return the computed value"
1789
- });
1790
- return true;
1791
- }
1792
2109
  function buildDecoratorArgs(options) {
1793
2110
  const top = [];
1794
2111
  if (options.containerProperty) {
@@ -3055,6 +3372,8 @@ export {
3055
3372
  PLUGIN_TYPES,
3056
3373
  PluginHandlerFactory,
3057
3374
  TrackingFileSystem,
3375
+ UPDATE_RELEASE_VERSION_SCRIPT,
3376
+ UPDATE_RELEASE_VERSION_SCRIPT_PATH,
3058
3377
  ValidationError,
3059
3378
  addChildToModel,
3060
3379
  addComputedToModel,
@@ -3062,17 +3381,23 @@ export {
3062
3381
  addContainerSupportToModel,
3063
3382
  addDependencyToModel,
3064
3383
  addFutureToModel,
3384
+ addJavaArtifactToManifests,
3065
3385
  addKosModelConfiguration,
3066
3386
  addModelEffectToModel,
3067
3387
  addPropertyToModel,
3068
3388
  addServiceRequestToModel,
3069
3389
  addTopicHandlerToModel,
3070
3390
  appendBarrelExport,
3391
+ buildKabTargets,
3071
3392
  camelCase,
3072
3393
  constantCase,
3073
3394
  dashCase,
3074
3395
  describeModel,
3396
+ discoverJavaArtifacts,
3075
3397
  discoverProjects,
3398
+ discoverUiArtifacts,
3399
+ ensureJavaAggregatorPom,
3400
+ finalizeArchetypeModule,
3076
3401
  findProjectByName,
3077
3402
  findProjectForPath,
3078
3403
  formatFiles,
@@ -3084,6 +3409,7 @@ export {
3084
3409
  generateHook,
3085
3410
  generateInit,
3086
3411
  generateModel,
3412
+ generatePolyglotWorkspace,
3087
3413
  generateSplashProject,
3088
3414
  getCodegenLogger,
3089
3415
  getCurrentDirectoryName,
@@ -3099,7 +3425,9 @@ export {
3099
3425
  properCase,
3100
3426
  readJson,
3101
3427
  readNxJson,
3428
+ resolveModelFilePath,
3102
3429
  setCodegenLogger,
3430
+ syncCiManifests,
3103
3431
  updateJson,
3104
3432
  updateModelIndex,
3105
3433
  validateModel,