@kosdev-code/kos-codegen-core 0.1.0-next.847 → 0.1.0-next.849
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.d.ts +1 -1
- package/index.d.ts.map +1 -1
- package/index.js +404 -229
- package/index.js.map +1 -1
- package/index.mjs +404 -229
- package/index.mjs.map +1 -1
- package/lib/generators/augment/ts-toolkit.d.ts +16 -0
- package/lib/generators/augment/ts-toolkit.d.ts.map +1 -1
- package/lib/generators/ci-sync.d.ts.map +1 -1
- package/lib/generators/index.d.ts +2 -1
- package/lib/generators/index.d.ts.map +1 -1
- package/lib/generators/kab-targets.d.ts +54 -10
- package/lib/generators/kab-targets.d.ts.map +1 -1
- package/lib/generators/member-mutators/add-service-request.d.ts +18 -1
- package/lib/generators/member-mutators/add-service-request.d.ts.map +1 -1
- package/lib/generators/service-catalog.d.ts +39 -0
- package/lib/generators/service-catalog.d.ts.map +1 -0
- package/package.json +2 -2
- package/templates/kos-splash-project/project/project.json.template +1 -1
- package/templates/kos-splash-project/tools/splashtool.mjs.template +14 -1
- package/templates/polyglot-workspace/github/build-release.json.template +1 -1
- package/templates/polyglot-workspace/github/build-ui.json.template +1 -1
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(
|
|
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
|
|
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
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
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
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
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
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
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
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
const
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
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: "
|
|
2640
|
+
{ name: "EndpointCtx", isTypeOnly: true }
|
|
2490
2641
|
]);
|
|
2491
2642
|
ensureNamedImport(sf, resolveSdkModuleSpecifier(sf), [
|
|
2492
|
-
{ name: "
|
|
2643
|
+
{ name: "executeServiceRequest" },
|
|
2644
|
+
{ name: "kosLoggerAware" },
|
|
2645
|
+
{ name: "KosLoggerAware", isTypeOnly: true }
|
|
2493
2646
|
]);
|
|
2494
|
-
|
|
2495
|
-
|
|
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: "
|
|
2498
|
-
decoratorArgsText:
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
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;
|