@kosdev-code/kos-codegen-core 0.1.0-next.790 → 0.1.0-next.796

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