@kosdev-code/kos-codegen-core 3.0.16 → 3.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -5,8 +5,8 @@ const path = require("path");
5
5
  const ejs = require("ejs");
6
6
  const fg = require("fast-glob");
7
7
  const prettier = require("prettier");
8
- const ts = require("typescript");
9
8
  const tsMorph = require("ts-morph");
9
+ const ts = require("typescript");
10
10
  function _interopNamespaceDefault(e) {
11
11
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
12
12
  if (e) {
@@ -582,6 +582,72 @@ function generatePolyglotWorkspace(codegenFs, templateDir, options) {
582
582
  );
583
583
  return { executablePaths: [...BUILD_SCRIPTS] };
584
584
  }
585
+ const KAB_OUTPUT_DIR = "target";
586
+ const KAB_OUTPUT_PATH = `{projectRoot}/${KAB_OUTPUT_DIR}/`;
587
+ function resolveKabPath(value, projectRoot) {
588
+ const root = projectRoot.replace(/\\/g, "/").replace(/\/+$/, "");
589
+ return value.replace(/\{projectRoot\}/g, root).replace(/\{workspaceRoot\}\/?/g, "");
590
+ }
591
+ const UPDATE_RELEASE_VERSION_SCRIPT_PATH = "tools/scripts/update-release-version.mjs";
592
+ function buildKabTargets(options) {
593
+ const { name, descriptor = false, buildTarget = "build" } = options;
594
+ const outputPath = KAB_OUTPUT_PATH;
595
+ const zipPayloadDir = "dist/{projectRoot}";
596
+ const targets = {
597
+ kab: {
598
+ command: `node tools/scripts/kabtool.mjs build ${name} && node tools/scripts/kabtool.mjs list ${name} `,
599
+ options: {
600
+ outputPath,
601
+ zipName: "ui.zip",
602
+ kabName: `${name}.kab`
603
+ },
604
+ dependsOn: ["zip"]
605
+ },
606
+ zip: {
607
+ command: `node tools/scripts/archiver.js ${name}`,
608
+ options: {
609
+ outputPath,
610
+ zipName: "ui.zip"
611
+ },
612
+ // `sbom` is a zip dependency, not a kab one: kabtool packages an
613
+ // already-sealed ui.zip, so anything ordered after `zip` misses it.
614
+ dependsOn: descriptor ? [buildTarget, "descriptor", "sbom"] : [buildTarget, "sbom"]
615
+ }
616
+ };
617
+ if (descriptor) {
618
+ targets.descriptor = {
619
+ command: `node tools/scripts/descriptor.mjs ${name}`,
620
+ options: {
621
+ outputPath: zipPayloadDir,
622
+ fileName: "descriptor.json"
623
+ },
624
+ dependsOn: ["build"]
625
+ };
626
+ }
627
+ targets.version = {
628
+ command: `node ${UPDATE_RELEASE_VERSION_SCRIPT_PATH} ${name} {args.ver}`,
629
+ options: {},
630
+ dependsOn: []
631
+ };
632
+ targets.sbom = buildSbomTarget({ outputPath: zipPayloadDir });
633
+ return targets;
634
+ }
635
+ function buildSbomTarget({
636
+ outputPath
637
+ }) {
638
+ const dir = outputPath.replace(/\/+$/, "");
639
+ const outputDir = dir.startsWith("{") ? dir : `{workspaceRoot}/${dir}`;
640
+ return {
641
+ executor: "@kosdev-code/kos-nx-plugin:sbom",
642
+ outputs: [
643
+ `${outputDir}/sbom.spdx.json`,
644
+ `${outputDir}/sbom.cyclonedx.json`
645
+ ],
646
+ cache: true,
647
+ inputs: ["production", "^production", "{workspaceRoot}/package-lock.json"],
648
+ options: { outputPath: dir, softFail: true }
649
+ };
650
+ }
585
651
  const UI_PROJECT_DIRS = [
586
652
  "apps",
587
653
  "libs",
@@ -609,16 +675,25 @@ function discoverUiArtifacts(codegenFs) {
609
675
  }
610
676
  const name = project.name;
611
677
  if (!name) continue;
678
+ const projectRoot = file.slice("ui/".length, file.lastIndexOf("/"));
679
+ const resolve = (value) => resolveKabPath(value, projectRoot);
612
680
  const kabOptions = project.targets?.kab?.options;
613
681
  if (kabOptions?.outputPath && kabOptions?.kabName) {
614
682
  artifacts.push({
615
683
  id: name,
616
- filename: `ui/${joinArtifactPath(kabOptions.outputPath, kabOptions.kabName)}`
684
+ filename: `ui/${joinArtifactPath(
685
+ resolve(kabOptions.outputPath),
686
+ kabOptions.kabName
687
+ )}`
617
688
  });
618
689
  } else if (project.targets?.splash) {
690
+ const splashOutput = project.targets.splash.options?.outputPath;
619
691
  artifacts.push({
620
692
  id: name,
621
- filename: `ui/dist/archives/packages/${name}/${name}.kab`,
693
+ filename: `ui/${joinArtifactPath(
694
+ resolve(splashOutput ?? `{projectRoot}/${KAB_OUTPUT_DIR}`),
695
+ `${name}.kab`
696
+ )}`,
622
697
  layer: 1
623
698
  });
624
699
  }
@@ -873,67 +948,249 @@ function normalizeOptions(codegenFs, options, projects) {
873
948
  template: ""
874
949
  };
875
950
  }
876
- const UPDATE_RELEASE_VERSION_SCRIPT_PATH = "tools/scripts/update-release-version.mjs";
877
- function buildKabTargets(options) {
878
- const {
879
- name,
880
- archiveDir = "packages",
881
- descriptorDir,
882
- buildTarget = "build"
883
- } = options;
884
- const outputPath = `dist/archives/${archiveDir}/${name}/`;
885
- const targets = {
886
- kab: {
887
- command: `node tools/scripts/kabtool.mjs build ${name} && node tools/scripts/kabtool.mjs list ${name} `,
888
- options: {
889
- outputPath,
890
- zipName: "ui.zip",
891
- kabName: `${name}.kab`
892
- },
893
- dependsOn: ["zip"]
894
- },
895
- zip: {
896
- command: `node tools/scripts/archiver.js ${name}`,
897
- options: {
898
- outputPath,
899
- zipName: "ui.zip"
900
- },
901
- dependsOn: descriptorDir ? [buildTarget, "descriptor"] : [buildTarget]
951
+ function transformSourceFile(codegenFs, filePath, mutate) {
952
+ const content = codegenFs.read(filePath);
953
+ if (content === null) {
954
+ throw new Error(`File not found: ${filePath}`);
955
+ }
956
+ const project = new tsMorph.Project({
957
+ useInMemoryFileSystem: true,
958
+ manipulationSettings: {
959
+ indentationText: tsMorph.IndentationText.TwoSpaces,
960
+ quoteKind: tsMorph.QuoteKind.Double
902
961
  }
903
- };
904
- if (descriptorDir) {
905
- targets.descriptor = {
906
- command: `node tools/scripts/descriptor.mjs ${name}`,
907
- options: {
908
- outputPath: `dist/${descriptorDir}`,
909
- fileName: "descriptor.json"
910
- },
911
- dependsOn: ["build"]
912
- };
962
+ });
963
+ const sourceFile = project.createSourceFile(filePath, content, {
964
+ overwrite: true
965
+ });
966
+ mutate(sourceFile);
967
+ codegenFs.write(filePath, sourceFile.getFullText());
968
+ }
969
+ function readSourceFile(codegenFs, filePath, inspect) {
970
+ const content = codegenFs.read(filePath);
971
+ if (content === null) {
972
+ throw new Error(`File not found: ${filePath}`);
913
973
  }
914
- targets.version = {
915
- command: `node ${UPDATE_RELEASE_VERSION_SCRIPT_PATH} ${name} {args.ver}`,
916
- options: {},
917
- dependsOn: []
918
- };
919
- targets.sbom = buildSbomTarget({ outputPath });
920
- targets.kab.dependsOn = [...targets.kab.dependsOn ?? [], "sbom"];
921
- return targets;
974
+ const project = new tsMorph.Project({ useInMemoryFileSystem: true });
975
+ return inspect(
976
+ project.createSourceFile(filePath, content, { overwrite: true })
977
+ );
922
978
  }
923
- function buildSbomTarget({
924
- outputPath
925
- }) {
926
- const dir = outputPath.replace(/\/+$/, "");
927
- return {
928
- executor: "@kosdev-code/kos-nx-plugin:sbom",
929
- outputs: [
930
- `{workspaceRoot}/${dir}/sbom.spdx.json`,
931
- `{workspaceRoot}/${dir}/sbom.cyclonedx.json`
932
- ],
933
- cache: true,
934
- inputs: ["production", "^production", "{workspaceRoot}/package-lock.json"],
935
- options: { outputPath: dir, softFail: true }
936
- };
979
+ function ensureNamedImport(sourceFile, moduleSpecifier, names) {
980
+ const decls = sourceFile.getImportDeclarations().filter((d) => d.getModuleSpecifierValue() === moduleSpecifier);
981
+ const existing = /* @__PURE__ */ new Set();
982
+ for (const d of decls) {
983
+ for (const n of d.getNamedImports()) existing.add(n.getName());
984
+ }
985
+ let target = decls.find((d) => !d.isTypeOnly());
986
+ if (!target) {
987
+ target = sourceFile.addImportDeclaration({ moduleSpecifier });
988
+ }
989
+ for (const { name, isTypeOnly } of names) {
990
+ if (existing.has(name)) continue;
991
+ target.addNamedImport({ name, isTypeOnly: !!isTypeOnly });
992
+ existing.add(name);
993
+ }
994
+ }
995
+ function resolveSdkModuleSpecifier(sourceFile) {
996
+ const decl = sourceFile.getImportDeclarations().find((d) => d.getNamedImports().some((n) => n.getName() === "kosModel"));
997
+ return decl?.getModuleSpecifierValue() ?? "@kosdev-code/kos-ui-sdk";
998
+ }
999
+ function getModelClass(sourceFile, preferName) {
1000
+ const classes = sourceFile.getClasses();
1001
+ const byName = preferName ? classes.find((c) => c.getName() === preferName) : void 0;
1002
+ if (byName) return byName;
1003
+ const impl = classes.find((c) => /ModelImpl$/.test(c.getName() ?? ""));
1004
+ if (impl) return impl;
1005
+ const exported = classes.find((c) => c.isExported());
1006
+ if (exported) return exported;
1007
+ if (classes.length > 0) return classes[0];
1008
+ throw new Error("No class declaration found in model file.");
1009
+ }
1010
+ function addClassDecorator(cls, name, opts) {
1011
+ if (cls.getDecorator(name)) return;
1012
+ const typeArgs = opts?.typeArgs?.length ? `<${opts.typeArgs.join(", ")}>` : "";
1013
+ const args = opts?.argsText ?? "";
1014
+ const decorators = cls.getDecorators();
1015
+ const kosModelIdx = decorators.findIndex((d) => d.getName() === "kosModel");
1016
+ const insertIdx = kosModelIdx >= 0 ? kosModelIdx + 1 : decorators.length;
1017
+ cls.insertDecorator(insertIdx, {
1018
+ name: `${name}${typeArgs}`,
1019
+ arguments: args ? [args] : []
1020
+ });
1021
+ }
1022
+ function ensureDeclarationMerge(sourceFile, interfaceName, extendsExpr, typeParameters = []) {
1023
+ const baseType = extendsExpr.split("<")[0].trim();
1024
+ let iface = sourceFile.getInterface(interfaceName);
1025
+ if (iface) {
1026
+ const already = iface.getExtends().some((e) => e.getText().split("<")[0].trim() === baseType);
1027
+ if (!already) iface.addExtends(extendsExpr);
1028
+ return;
1029
+ }
1030
+ iface = sourceFile.addInterface({
1031
+ name: interfaceName,
1032
+ isExported: true,
1033
+ typeParameters,
1034
+ extends: [extendsExpr]
1035
+ });
1036
+ sourceFile.insertText(
1037
+ iface.getStart(),
1038
+ "// eslint-disable-next-line @typescript-eslint/no-empty-interface\n"
1039
+ );
1040
+ }
1041
+ function ensureFileEslintDisable(sourceFile, rule) {
1042
+ if (sourceFile.getFullText().includes(rule)) return;
1043
+ sourceFile.insertText(0, `/* eslint-disable ${rule} */
1044
+ `);
1045
+ }
1046
+ function ensureModuleConst(sourceFile, spec) {
1047
+ if (sourceFile.getVariableDeclaration(spec.name)) return false;
1048
+ const imports = sourceFile.getImportDeclarations();
1049
+ const index = imports.length > 0 ? imports[imports.length - 1].getChildIndex() + 1 : 0;
1050
+ sourceFile.insertVariableStatement(index, {
1051
+ declarationKind: tsMorph.VariableDeclarationKind.Const,
1052
+ declarations: [{ name: spec.name, initializer: spec.initializer }]
1053
+ });
1054
+ return true;
1055
+ }
1056
+ function addDecoratedMethod(cls, spec) {
1057
+ if (cls.getMethod(spec.name)) return false;
1058
+ cls.addMethod({
1059
+ name: spec.name,
1060
+ isAsync: spec.isAsync,
1061
+ returnType: spec.returnType,
1062
+ parameters: spec.parameters?.map((p) => {
1063
+ return { name: p.name, type: p.type, hasQuestionToken: p.optional };
1064
+ }),
1065
+ statements: spec.statements,
1066
+ decorators: [
1067
+ {
1068
+ name: spec.decoratorName,
1069
+ arguments: spec.decoratorArgsText ? [spec.decoratorArgsText] : []
1070
+ }
1071
+ ]
1072
+ });
1073
+ return true;
1074
+ }
1075
+ function propertyInsertIndex(cls) {
1076
+ const props = cls.getProperties();
1077
+ if (props.length === 0) return 0;
1078
+ return props[props.length - 1].getChildIndex() + 1;
1079
+ }
1080
+ function addPlainProperty(cls, spec) {
1081
+ if (cls.getProperty(spec.name)) return false;
1082
+ cls.insertProperty(propertyInsertIndex(cls), {
1083
+ name: spec.name,
1084
+ type: spec.type,
1085
+ initializer: spec.initializer,
1086
+ isReadonly: !!spec.readonly,
1087
+ scope: spec.scope ? SCOPE_MAP[spec.scope] : void 0,
1088
+ hasExclamationToken: spec.initializer ? false : !!spec.hasExclamation
1089
+ });
1090
+ return true;
1091
+ }
1092
+ const SCOPE_MAP = {
1093
+ private: tsMorph.Scope.Private,
1094
+ protected: tsMorph.Scope.Protected,
1095
+ public: tsMorph.Scope.Public
1096
+ };
1097
+ function addDecoratedProperty(cls, spec) {
1098
+ if (cls.getProperty(spec.name)) return false;
1099
+ cls.insertProperty(propertyInsertIndex(cls), {
1100
+ name: spec.name,
1101
+ type: spec.type,
1102
+ initializer: spec.initializer,
1103
+ scope: spec.scope ? SCOPE_MAP[spec.scope] : void 0,
1104
+ hasExclamationToken: spec.initializer ? false : !!spec.hasExclamation,
1105
+ decorators: [
1106
+ spec.bare && !spec.decoratorArgsText ? { name: spec.decoratorName } : {
1107
+ name: spec.decoratorName,
1108
+ arguments: spec.decoratorArgsText ? [spec.decoratorArgsText] : []
1109
+ }
1110
+ ]
1111
+ });
1112
+ return true;
1113
+ }
1114
+ function addGetter(cls, spec) {
1115
+ if (cls.getGetAccessor(spec.name)) return false;
1116
+ const ctor = cls.getConstructors()[0];
1117
+ const index = ctor ? ctor.getChildIndex() + 1 : propertyInsertIndex(cls);
1118
+ cls.insertGetAccessor(index, {
1119
+ name: spec.name,
1120
+ returnType: spec.returnType,
1121
+ statements: spec.statements ?? "// TODO: derive and return the computed value"
1122
+ });
1123
+ return true;
1124
+ }
1125
+ const CATALOG_METHODS = ["get", "put", "post", "delete", "patch"];
1126
+ const SERVICE_MODULE_RE$1 = /\/utils\/services\/([^/]+)\/([^/]+)\/service\.ts$/;
1127
+ function listServiceCatalog(codegenFs, query, projects) {
1128
+ const project = findProjectByName(
1129
+ codegenFs.root,
1130
+ query.modelProject,
1131
+ projects
1132
+ );
1133
+ if (!project) {
1134
+ throw new Error(`Project not found: ${query.modelProject}`);
1135
+ }
1136
+ const sourceRoot = project.sourceRoot || path__namespace.join(project.root, "src");
1137
+ const entries = [];
1138
+ for (const file of codegenFs.listFiles(sourceRoot)) {
1139
+ const posix = file.split(path__namespace.sep).join("/");
1140
+ const match = SERVICE_MODULE_RE$1.exec(posix);
1141
+ if (!match) continue;
1142
+ const [, app, version] = match;
1143
+ if (query.app && app !== query.app) continue;
1144
+ if (query.version && version !== query.version) continue;
1145
+ const openapiPath = posix.replace(/service\.ts$/, "openapi.d.ts");
1146
+ if (!codegenFs.exists(openapiPath)) continue;
1147
+ entries.push({
1148
+ app,
1149
+ version,
1150
+ serviceModulePath: posix,
1151
+ operations: readOperations(codegenFs, openapiPath)
1152
+ });
1153
+ }
1154
+ return entries.sort(
1155
+ (a, b) => a.app.localeCompare(b.app) || a.version.localeCompare(b.version)
1156
+ );
1157
+ }
1158
+ function readOperations(codegenFs, openapiPath) {
1159
+ return readSourceFile(codegenFs, openapiPath, (sf) => {
1160
+ const paths = sf.getInterface("paths");
1161
+ if (!paths) return [];
1162
+ const operations = [];
1163
+ for (const pathProp of paths.getProperties()) {
1164
+ const servicePath = unquote(pathProp.getName());
1165
+ const literal = pathProp.getTypeNode();
1166
+ if (!literal || !("getProperties" in literal)) continue;
1167
+ for (const methodProp of literal.getProperties()) {
1168
+ const method = methodProp.getName();
1169
+ if (!isCatalogMethod(method)) continue;
1170
+ const typeText = methodProp.getTypeNode()?.getText() ?? "";
1171
+ if (methodProp.hasQuestionToken() && typeText === "never") continue;
1172
+ operations.push({
1173
+ path: servicePath,
1174
+ method,
1175
+ summary: readSummary(methodProp),
1176
+ pathParams: [...servicePath.matchAll(/\{([^}]+)\}/g)].map(
1177
+ (m) => m[1]
1178
+ )
1179
+ });
1180
+ }
1181
+ }
1182
+ return operations;
1183
+ });
1184
+ }
1185
+ function isCatalogMethod(value) {
1186
+ return CATALOG_METHODS.includes(value);
1187
+ }
1188
+ function readSummary(methodProp) {
1189
+ const text = methodProp.getLeadingCommentRanges().map((c) => c.getText()).join(" ").replace(/^\/\*+/, "").replace(/\*+\/$/, "").replace(/^\s*\*\s?/gm, "").replace(/\s+/g, " ").trim();
1190
+ return text || void 0;
1191
+ }
1192
+ function unquote(name) {
1193
+ return name.replace(/^["']|["']$/g, "");
937
1194
  }
938
1195
  const UPDATE_RELEASE_VERSION_SCRIPT = `import devkit from "@nx/devkit";
939
1196
  import { resolve } from "path";
@@ -1495,160 +1752,6 @@ function normalizeAddFutureOptions(codegenFs, options, projects) {
1495
1752
  internal
1496
1753
  };
1497
1754
  }
1498
- function transformSourceFile(codegenFs, filePath, mutate) {
1499
- const content = codegenFs.read(filePath);
1500
- if (content === null) {
1501
- throw new Error(`File not found: ${filePath}`);
1502
- }
1503
- const project = new tsMorph.Project({
1504
- useInMemoryFileSystem: true,
1505
- manipulationSettings: {
1506
- indentationText: tsMorph.IndentationText.TwoSpaces,
1507
- quoteKind: tsMorph.QuoteKind.Double
1508
- }
1509
- });
1510
- const sourceFile = project.createSourceFile(filePath, content, {
1511
- overwrite: true
1512
- });
1513
- mutate(sourceFile);
1514
- codegenFs.write(filePath, sourceFile.getFullText());
1515
- }
1516
- function ensureNamedImport(sourceFile, moduleSpecifier, names) {
1517
- const decls = sourceFile.getImportDeclarations().filter((d) => d.getModuleSpecifierValue() === moduleSpecifier);
1518
- const existing = /* @__PURE__ */ new Set();
1519
- for (const d of decls) {
1520
- for (const n of d.getNamedImports()) existing.add(n.getName());
1521
- }
1522
- let target = decls.find((d) => !d.isTypeOnly());
1523
- if (!target) {
1524
- target = sourceFile.addImportDeclaration({ moduleSpecifier });
1525
- }
1526
- for (const { name, isTypeOnly } of names) {
1527
- if (existing.has(name)) continue;
1528
- target.addNamedImport({ name, isTypeOnly: !!isTypeOnly });
1529
- existing.add(name);
1530
- }
1531
- }
1532
- function resolveSdkModuleSpecifier(sourceFile) {
1533
- const decl = sourceFile.getImportDeclarations().find((d) => d.getNamedImports().some((n) => n.getName() === "kosModel"));
1534
- return decl?.getModuleSpecifierValue() ?? "@kosdev-code/kos-ui-sdk";
1535
- }
1536
- function getModelClass(sourceFile, preferName) {
1537
- const classes = sourceFile.getClasses();
1538
- const byName = preferName ? classes.find((c) => c.getName() === preferName) : void 0;
1539
- if (byName) return byName;
1540
- const impl = classes.find((c) => /ModelImpl$/.test(c.getName() ?? ""));
1541
- if (impl) return impl;
1542
- const exported = classes.find((c) => c.isExported());
1543
- if (exported) return exported;
1544
- if (classes.length > 0) return classes[0];
1545
- throw new Error("No class declaration found in model file.");
1546
- }
1547
- function addClassDecorator(cls, name, opts) {
1548
- if (cls.getDecorator(name)) return;
1549
- const typeArgs = opts?.typeArgs?.length ? `<${opts.typeArgs.join(", ")}>` : "";
1550
- const args = opts?.argsText ?? "";
1551
- const decorators = cls.getDecorators();
1552
- const kosModelIdx = decorators.findIndex((d) => d.getName() === "kosModel");
1553
- const insertIdx = kosModelIdx >= 0 ? kosModelIdx + 1 : decorators.length;
1554
- cls.insertDecorator(insertIdx, {
1555
- name: `${name}${typeArgs}`,
1556
- arguments: args ? [args] : []
1557
- });
1558
- }
1559
- function ensureDeclarationMerge(sourceFile, interfaceName, extendsExpr, typeParameters = []) {
1560
- const baseType = extendsExpr.split("<")[0].trim();
1561
- let iface = sourceFile.getInterface(interfaceName);
1562
- if (iface) {
1563
- const already = iface.getExtends().some((e) => e.getText().split("<")[0].trim() === baseType);
1564
- if (!already) iface.addExtends(extendsExpr);
1565
- return;
1566
- }
1567
- iface = sourceFile.addInterface({
1568
- name: interfaceName,
1569
- isExported: true,
1570
- typeParameters,
1571
- extends: [extendsExpr]
1572
- });
1573
- sourceFile.insertText(
1574
- iface.getStart(),
1575
- "// eslint-disable-next-line @typescript-eslint/no-empty-interface\n"
1576
- );
1577
- }
1578
- function ensureFileEslintDisable(sourceFile, rule) {
1579
- if (sourceFile.getFullText().includes(rule)) return;
1580
- sourceFile.insertText(0, `/* eslint-disable ${rule} */
1581
- `);
1582
- }
1583
- function addDecoratedMethod(cls, spec) {
1584
- if (cls.getMethod(spec.name)) return false;
1585
- cls.addMethod({
1586
- name: spec.name,
1587
- isAsync: spec.isAsync,
1588
- returnType: spec.returnType,
1589
- parameters: spec.parameters?.map((p) => {
1590
- return { name: p.name, type: p.type };
1591
- }),
1592
- statements: spec.statements,
1593
- decorators: [
1594
- {
1595
- name: spec.decoratorName,
1596
- arguments: spec.decoratorArgsText ? [spec.decoratorArgsText] : []
1597
- }
1598
- ]
1599
- });
1600
- return true;
1601
- }
1602
- function propertyInsertIndex(cls) {
1603
- const props = cls.getProperties();
1604
- if (props.length === 0) return 0;
1605
- return props[props.length - 1].getChildIndex() + 1;
1606
- }
1607
- function addPlainProperty(cls, spec) {
1608
- if (cls.getProperty(spec.name)) return false;
1609
- cls.insertProperty(propertyInsertIndex(cls), {
1610
- name: spec.name,
1611
- type: spec.type,
1612
- initializer: spec.initializer,
1613
- isReadonly: !!spec.readonly,
1614
- scope: spec.scope ? SCOPE_MAP[spec.scope] : void 0,
1615
- hasExclamationToken: spec.initializer ? false : !!spec.hasExclamation
1616
- });
1617
- return true;
1618
- }
1619
- const SCOPE_MAP = {
1620
- private: tsMorph.Scope.Private,
1621
- protected: tsMorph.Scope.Protected,
1622
- public: tsMorph.Scope.Public
1623
- };
1624
- function addDecoratedProperty(cls, spec) {
1625
- if (cls.getProperty(spec.name)) return false;
1626
- cls.insertProperty(propertyInsertIndex(cls), {
1627
- name: spec.name,
1628
- type: spec.type,
1629
- initializer: spec.initializer,
1630
- scope: spec.scope ? SCOPE_MAP[spec.scope] : void 0,
1631
- hasExclamationToken: spec.initializer ? false : !!spec.hasExclamation,
1632
- decorators: [
1633
- spec.bare && !spec.decoratorArgsText ? { name: spec.decoratorName } : {
1634
- name: spec.decoratorName,
1635
- arguments: spec.decoratorArgsText ? [spec.decoratorArgsText] : []
1636
- }
1637
- ]
1638
- });
1639
- return true;
1640
- }
1641
- function addGetter(cls, spec) {
1642
- if (cls.getGetAccessor(spec.name)) return false;
1643
- const ctor = cls.getConstructors()[0];
1644
- const index = ctor ? ctor.getChildIndex() + 1 : propertyInsertIndex(cls);
1645
- cls.insertGetAccessor(index, {
1646
- name: spec.name,
1647
- returnType: spec.returnType,
1648
- statements: spec.statements ?? "// TODO: derive and return the computed value"
1649
- });
1650
- return true;
1651
- }
1652
1755
  const LEGACY_IMPORTS = /* @__PURE__ */ new Set([
1653
1756
  "setupCompleteFutureSupport",
1654
1757
  "setupMinimalFutureSupport",
@@ -2455,6 +2558,12 @@ function addServiceRequestToModel(codegenFs, options, projects) {
2455
2558
  throw new Error(`Model file not found: ${modelFilePath}`);
2456
2559
  }
2457
2560
  let serviceImport = options.serviceModule;
2561
+ if (!serviceImport && options.serviceModulePath) {
2562
+ serviceImport = toImportSpecifier(
2563
+ modelFilePath,
2564
+ options.serviceModulePath.replace(/\.ts$/, "")
2565
+ );
2566
+ }
2458
2567
  if (!serviceImport) {
2459
2568
  if (!sourceRoot) {
2460
2569
  throw new Error(
@@ -2480,31 +2589,93 @@ function addServiceRequestToModel(codegenFs, options, projects) {
2480
2589
  serviceImport = specifiers[0];
2481
2590
  }
2482
2591
  const method = options.method || "get";
2592
+ const mode = options.mode ?? (options.lifecycle ? "lifecycle" : "method");
2483
2593
  const lifecycle = options.lifecycle || "LOAD";
2484
2594
  logger.info(
2485
- `Adding @kosServiceRequest "${options.methodName}" (${method} ${options.servicePath}) to ${options.modelName}`
2595
+ `Adding ${mode}-driven @kosServiceRequest "${options.methodName}" (${method} ${options.servicePath}) to ${options.modelName}`
2486
2596
  );
2487
2597
  transformSourceFile(codegenFs, modelFilePath, (sf) => {
2598
+ const endpointConst = endpointConstName(options.methodName);
2599
+ const className = getModelClass(sf).getName();
2600
+ if (!className) throw new Error("Model class has no name.");
2601
+ const typeParams = getModelClass(sf).getTypeParameters().map((tp) => tp.getText());
2602
+ ensureNamedImport(sf, serviceImport, [
2603
+ { name: "endpoint" },
2604
+ { name: "serviceRequest" }
2605
+ ]);
2606
+ ensureModuleConst(sf, {
2607
+ name: endpointConst,
2608
+ initializer: `endpoint(${JSON.stringify(
2609
+ options.servicePath
2610
+ )}, ${JSON.stringify(method)})`
2611
+ });
2612
+ if (mode === "lifecycle") {
2613
+ ensureNamedImport(sf, serviceImport, [
2614
+ { name: "EndpointResponse", isTypeOnly: true }
2615
+ ]);
2616
+ ensureNamedImport(sf, resolveSdkModuleSpecifier(sf), [
2617
+ { name: "DependencyLifecycle" }
2618
+ ]);
2619
+ addDecoratedMethod(getModelClass(sf), {
2620
+ name: options.methodName,
2621
+ decoratorName: "serviceRequest",
2622
+ decoratorArgsText: `${endpointConst}, { lifecycle: DependencyLifecycle.${lifecycle} }`,
2623
+ // The manager invokes phase handlers error-first, passing (null, data)
2624
+ // on success. The `error` half is only ever reached because
2625
+ // `serviceRequest` supplies an errorHandler — the bare decorator
2626
+ // defaults to `throw`, which fails the phase instead of calling back.
2627
+ parameters: [
2628
+ { name: "error", type: "string | null" },
2629
+ {
2630
+ name: "data",
2631
+ type: `EndpointResponse<typeof ${endpointConst}>`
2632
+ }
2633
+ ],
2634
+ returnType: "void",
2635
+ statements: "// TODO: apply `data` to the model"
2636
+ });
2637
+ return;
2638
+ }
2488
2639
  ensureNamedImport(sf, serviceImport, [
2489
- { name: "kosServiceRequest" }
2640
+ { name: "EndpointCtx", isTypeOnly: true }
2490
2641
  ]);
2491
2642
  ensureNamedImport(sf, resolveSdkModuleSpecifier(sf), [
2492
- { name: "DependencyLifecycle" }
2643
+ { name: "executeServiceRequest" },
2644
+ { name: "kosLoggerAware" },
2645
+ { name: "KosLoggerAware", isTypeOnly: true }
2493
2646
  ]);
2494
- const cls = getModelClass(sf);
2495
- addDecoratedMethod(cls, {
2647
+ ensureFileEslintDisable(
2648
+ sf,
2649
+ "@typescript-eslint/no-unsafe-declaration-merging"
2650
+ );
2651
+ ensureDeclarationMerge(sf, className, "KosLoggerAware", typeParams);
2652
+ addClassDecorator(getModelClass(sf), "kosLoggerAware", { argsText: "" });
2653
+ addDecoratedMethod(getModelClass(sf), {
2496
2654
  name: options.methodName,
2497
- decoratorName: "kosServiceRequest",
2498
- decoratorArgsText: `{ path: ${JSON.stringify(
2499
- options.servicePath
2500
- )}, method: ${JSON.stringify(
2501
- method
2502
- )}, lifecycle: DependencyLifecycle.${lifecycle} }`,
2503
- returnType: "void",
2504
- statements: "// TODO: handle the typed response"
2655
+ decoratorName: "serviceRequest",
2656
+ decoratorArgsText: endpointConst,
2657
+ // Optional: the framework appends the context, callers never pass it.
2658
+ parameters: [
2659
+ {
2660
+ name: "$ctx",
2661
+ type: `EndpointCtx<typeof ${endpointConst}>`,
2662
+ optional: true
2663
+ }
2664
+ ],
2665
+ isAsync: true,
2666
+ returnType: "Promise<void>",
2667
+ statements: [
2668
+ "const data = await executeServiceRequest(this, $ctx);",
2669
+ "if (!data) return;",
2670
+ "// TODO: apply `data` to the model"
2671
+ ].join("\n")
2505
2672
  });
2506
2673
  });
2507
- return { modelFilePath, serviceModule: serviceImport };
2674
+ return { modelFilePath, serviceModule: serviceImport, mode };
2675
+ }
2676
+ function endpointConstName(methodName) {
2677
+ const snake = methodName.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").toUpperCase();
2678
+ return `ENDPOINT_${snake}`;
2508
2679
  }
2509
2680
  function modelElementType(typeText) {
2510
2681
  let m;
@@ -3405,6 +3576,8 @@ function updatePluginConfiguration(codegenFs, projectConfig, options) {
3405
3576
  exports.BasePluginHandler = BasePluginHandler;
3406
3577
  exports.CONTRIBUTION_TYPE_MAP = CONTRIBUTION_TYPE_MAP;
3407
3578
  exports.DirectFileSystem = DirectFileSystem;
3579
+ exports.KAB_OUTPUT_DIR = KAB_OUTPUT_DIR;
3580
+ exports.KAB_OUTPUT_PATH = KAB_OUTPUT_PATH;
3408
3581
  exports.KOS_JSON_PATHS = KOS_JSON_PATHS;
3409
3582
  exports.KosConfigBuilder = KosConfigBuilder;
3410
3583
  exports.LOCALIZED_PLUGIN_TYPES = LOCALIZED_PLUGIN_TYPES;
@@ -3458,6 +3631,7 @@ exports.getKosModelConfiguration = getKosModelConfiguration;
3458
3631
  exports.getKosProjectConfiguration = getKosProjectConfiguration;
3459
3632
  exports.getProject = getProject;
3460
3633
  exports.getTemplateDir = getTemplateDir;
3634
+ exports.listServiceCatalog = listServiceCatalog;
3461
3635
  exports.lookupSdkType = lookupSdkType;
3462
3636
  exports.normalizeAllValues = normalizeAllValues;
3463
3637
  exports.normalizeOptions = normalizeOptions;
@@ -3465,6 +3639,7 @@ exports.pascalCase = pascalCase;
3465
3639
  exports.properCase = properCase;
3466
3640
  exports.readJson = readJson;
3467
3641
  exports.readNxJson = readNxJson;
3642
+ exports.resolveKabPath = resolveKabPath;
3468
3643
  exports.resolveModelFilePath = resolveModelFilePath;
3469
3644
  exports.setCodegenLogger = setCodegenLogger;
3470
3645
  exports.syncCiManifests = syncCiManifests;