@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.mjs
CHANGED
|
@@ -3,8 +3,8 @@ import * as path from "path";
|
|
|
3
3
|
import * as ejs from "ejs";
|
|
4
4
|
import fg from "fast-glob";
|
|
5
5
|
import prettier from "prettier";
|
|
6
|
+
import { Scope, Project, QuoteKind, IndentationText, VariableDeclarationKind, Node } from "ts-morph";
|
|
6
7
|
import * as ts from "typescript";
|
|
7
|
-
import { Scope, Project, QuoteKind, IndentationText, Node } from "ts-morph";
|
|
8
8
|
class TrackingFileSystem {
|
|
9
9
|
inner;
|
|
10
10
|
_writtenPaths = [];
|
|
@@ -560,6 +560,72 @@ function generatePolyglotWorkspace(codegenFs, templateDir, options) {
|
|
|
560
560
|
);
|
|
561
561
|
return { executablePaths: [...BUILD_SCRIPTS] };
|
|
562
562
|
}
|
|
563
|
+
const KAB_OUTPUT_DIR = "target";
|
|
564
|
+
const KAB_OUTPUT_PATH = `{projectRoot}/${KAB_OUTPUT_DIR}/`;
|
|
565
|
+
function resolveKabPath(value, projectRoot) {
|
|
566
|
+
const root = projectRoot.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
567
|
+
return value.replace(/\{projectRoot\}/g, root).replace(/\{workspaceRoot\}\/?/g, "");
|
|
568
|
+
}
|
|
569
|
+
const UPDATE_RELEASE_VERSION_SCRIPT_PATH = "tools/scripts/update-release-version.mjs";
|
|
570
|
+
function buildKabTargets(options) {
|
|
571
|
+
const { name, descriptor = false, buildTarget = "build" } = options;
|
|
572
|
+
const outputPath = KAB_OUTPUT_PATH;
|
|
573
|
+
const zipPayloadDir = "dist/{projectRoot}";
|
|
574
|
+
const targets = {
|
|
575
|
+
kab: {
|
|
576
|
+
command: `node tools/scripts/kabtool.mjs build ${name} && node tools/scripts/kabtool.mjs list ${name} `,
|
|
577
|
+
options: {
|
|
578
|
+
outputPath,
|
|
579
|
+
zipName: "ui.zip",
|
|
580
|
+
kabName: `${name}.kab`
|
|
581
|
+
},
|
|
582
|
+
dependsOn: ["zip"]
|
|
583
|
+
},
|
|
584
|
+
zip: {
|
|
585
|
+
command: `node tools/scripts/archiver.js ${name}`,
|
|
586
|
+
options: {
|
|
587
|
+
outputPath,
|
|
588
|
+
zipName: "ui.zip"
|
|
589
|
+
},
|
|
590
|
+
// `sbom` is a zip dependency, not a kab one: kabtool packages an
|
|
591
|
+
// already-sealed ui.zip, so anything ordered after `zip` misses it.
|
|
592
|
+
dependsOn: descriptor ? [buildTarget, "descriptor", "sbom"] : [buildTarget, "sbom"]
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
if (descriptor) {
|
|
596
|
+
targets.descriptor = {
|
|
597
|
+
command: `node tools/scripts/descriptor.mjs ${name}`,
|
|
598
|
+
options: {
|
|
599
|
+
outputPath: zipPayloadDir,
|
|
600
|
+
fileName: "descriptor.json"
|
|
601
|
+
},
|
|
602
|
+
dependsOn: ["build"]
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
targets.version = {
|
|
606
|
+
command: `node ${UPDATE_RELEASE_VERSION_SCRIPT_PATH} ${name} {args.ver}`,
|
|
607
|
+
options: {},
|
|
608
|
+
dependsOn: []
|
|
609
|
+
};
|
|
610
|
+
targets.sbom = buildSbomTarget({ outputPath: zipPayloadDir });
|
|
611
|
+
return targets;
|
|
612
|
+
}
|
|
613
|
+
function buildSbomTarget({
|
|
614
|
+
outputPath
|
|
615
|
+
}) {
|
|
616
|
+
const dir = outputPath.replace(/\/+$/, "");
|
|
617
|
+
const outputDir = dir.startsWith("{") ? dir : `{workspaceRoot}/${dir}`;
|
|
618
|
+
return {
|
|
619
|
+
executor: "@kosdev-code/kos-nx-plugin:sbom",
|
|
620
|
+
outputs: [
|
|
621
|
+
`${outputDir}/sbom.spdx.json`,
|
|
622
|
+
`${outputDir}/sbom.cyclonedx.json`
|
|
623
|
+
],
|
|
624
|
+
cache: true,
|
|
625
|
+
inputs: ["production", "^production", "{workspaceRoot}/package-lock.json"],
|
|
626
|
+
options: { outputPath: dir, softFail: true }
|
|
627
|
+
};
|
|
628
|
+
}
|
|
563
629
|
const UI_PROJECT_DIRS = [
|
|
564
630
|
"apps",
|
|
565
631
|
"libs",
|
|
@@ -587,16 +653,25 @@ function discoverUiArtifacts(codegenFs) {
|
|
|
587
653
|
}
|
|
588
654
|
const name = project.name;
|
|
589
655
|
if (!name) continue;
|
|
656
|
+
const projectRoot = file.slice("ui/".length, file.lastIndexOf("/"));
|
|
657
|
+
const resolve = (value) => resolveKabPath(value, projectRoot);
|
|
590
658
|
const kabOptions = project.targets?.kab?.options;
|
|
591
659
|
if (kabOptions?.outputPath && kabOptions?.kabName) {
|
|
592
660
|
artifacts.push({
|
|
593
661
|
id: name,
|
|
594
|
-
filename: `ui/${joinArtifactPath(
|
|
662
|
+
filename: `ui/${joinArtifactPath(
|
|
663
|
+
resolve(kabOptions.outputPath),
|
|
664
|
+
kabOptions.kabName
|
|
665
|
+
)}`
|
|
595
666
|
});
|
|
596
667
|
} else if (project.targets?.splash) {
|
|
668
|
+
const splashOutput = project.targets.splash.options?.outputPath;
|
|
597
669
|
artifacts.push({
|
|
598
670
|
id: name,
|
|
599
|
-
filename: `ui
|
|
671
|
+
filename: `ui/${joinArtifactPath(
|
|
672
|
+
resolve(splashOutput ?? `{projectRoot}/${KAB_OUTPUT_DIR}`),
|
|
673
|
+
`${name}.kab`
|
|
674
|
+
)}`,
|
|
600
675
|
layer: 1
|
|
601
676
|
});
|
|
602
677
|
}
|
|
@@ -851,67 +926,249 @@ function normalizeOptions(codegenFs, options, projects) {
|
|
|
851
926
|
template: ""
|
|
852
927
|
};
|
|
853
928
|
}
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
kab: {
|
|
865
|
-
command: `node tools/scripts/kabtool.mjs build ${name} && node tools/scripts/kabtool.mjs list ${name} `,
|
|
866
|
-
options: {
|
|
867
|
-
outputPath,
|
|
868
|
-
zipName: "ui.zip",
|
|
869
|
-
kabName: `${name}.kab`
|
|
870
|
-
},
|
|
871
|
-
dependsOn: ["zip"]
|
|
872
|
-
},
|
|
873
|
-
zip: {
|
|
874
|
-
command: `node tools/scripts/archiver.js ${name}`,
|
|
875
|
-
options: {
|
|
876
|
-
outputPath,
|
|
877
|
-
zipName: "ui.zip"
|
|
878
|
-
},
|
|
879
|
-
dependsOn: descriptorDir ? [buildTarget, "descriptor"] : [buildTarget]
|
|
929
|
+
function transformSourceFile(codegenFs, filePath, mutate) {
|
|
930
|
+
const content = codegenFs.read(filePath);
|
|
931
|
+
if (content === null) {
|
|
932
|
+
throw new Error(`File not found: ${filePath}`);
|
|
933
|
+
}
|
|
934
|
+
const project = new Project({
|
|
935
|
+
useInMemoryFileSystem: true,
|
|
936
|
+
manipulationSettings: {
|
|
937
|
+
indentationText: IndentationText.TwoSpaces,
|
|
938
|
+
quoteKind: QuoteKind.Double
|
|
880
939
|
}
|
|
881
|
-
};
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
940
|
+
});
|
|
941
|
+
const sourceFile = project.createSourceFile(filePath, content, {
|
|
942
|
+
overwrite: true
|
|
943
|
+
});
|
|
944
|
+
mutate(sourceFile);
|
|
945
|
+
codegenFs.write(filePath, sourceFile.getFullText());
|
|
946
|
+
}
|
|
947
|
+
function readSourceFile(codegenFs, filePath, inspect) {
|
|
948
|
+
const content = codegenFs.read(filePath);
|
|
949
|
+
if (content === null) {
|
|
950
|
+
throw new Error(`File not found: ${filePath}`);
|
|
891
951
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
};
|
|
897
|
-
targets.sbom = buildSbomTarget({ outputPath });
|
|
898
|
-
targets.kab.dependsOn = [...targets.kab.dependsOn ?? [], "sbom"];
|
|
899
|
-
return targets;
|
|
952
|
+
const project = new Project({ useInMemoryFileSystem: true });
|
|
953
|
+
return inspect(
|
|
954
|
+
project.createSourceFile(filePath, content, { overwrite: true })
|
|
955
|
+
);
|
|
900
956
|
}
|
|
901
|
-
function
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
const
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
957
|
+
function ensureNamedImport(sourceFile, moduleSpecifier, names) {
|
|
958
|
+
const decls = sourceFile.getImportDeclarations().filter((d) => d.getModuleSpecifierValue() === moduleSpecifier);
|
|
959
|
+
const existing = /* @__PURE__ */ new Set();
|
|
960
|
+
for (const d of decls) {
|
|
961
|
+
for (const n of d.getNamedImports()) existing.add(n.getName());
|
|
962
|
+
}
|
|
963
|
+
let target = decls.find((d) => !d.isTypeOnly());
|
|
964
|
+
if (!target) {
|
|
965
|
+
target = sourceFile.addImportDeclaration({ moduleSpecifier });
|
|
966
|
+
}
|
|
967
|
+
for (const { name, isTypeOnly } of names) {
|
|
968
|
+
if (existing.has(name)) continue;
|
|
969
|
+
target.addNamedImport({ name, isTypeOnly: !!isTypeOnly });
|
|
970
|
+
existing.add(name);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
function resolveSdkModuleSpecifier(sourceFile) {
|
|
974
|
+
const decl = sourceFile.getImportDeclarations().find((d) => d.getNamedImports().some((n) => n.getName() === "kosModel"));
|
|
975
|
+
return decl?.getModuleSpecifierValue() ?? "@kosdev-code/kos-ui-sdk";
|
|
976
|
+
}
|
|
977
|
+
function getModelClass(sourceFile, preferName) {
|
|
978
|
+
const classes = sourceFile.getClasses();
|
|
979
|
+
const byName = preferName ? classes.find((c) => c.getName() === preferName) : void 0;
|
|
980
|
+
if (byName) return byName;
|
|
981
|
+
const impl = classes.find((c) => /ModelImpl$/.test(c.getName() ?? ""));
|
|
982
|
+
if (impl) return impl;
|
|
983
|
+
const exported = classes.find((c) => c.isExported());
|
|
984
|
+
if (exported) return exported;
|
|
985
|
+
if (classes.length > 0) return classes[0];
|
|
986
|
+
throw new Error("No class declaration found in model file.");
|
|
987
|
+
}
|
|
988
|
+
function addClassDecorator(cls, name, opts) {
|
|
989
|
+
if (cls.getDecorator(name)) return;
|
|
990
|
+
const typeArgs = opts?.typeArgs?.length ? `<${opts.typeArgs.join(", ")}>` : "";
|
|
991
|
+
const args = opts?.argsText ?? "";
|
|
992
|
+
const decorators = cls.getDecorators();
|
|
993
|
+
const kosModelIdx = decorators.findIndex((d) => d.getName() === "kosModel");
|
|
994
|
+
const insertIdx = kosModelIdx >= 0 ? kosModelIdx + 1 : decorators.length;
|
|
995
|
+
cls.insertDecorator(insertIdx, {
|
|
996
|
+
name: `${name}${typeArgs}`,
|
|
997
|
+
arguments: args ? [args] : []
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
function ensureDeclarationMerge(sourceFile, interfaceName, extendsExpr, typeParameters = []) {
|
|
1001
|
+
const baseType = extendsExpr.split("<")[0].trim();
|
|
1002
|
+
let iface = sourceFile.getInterface(interfaceName);
|
|
1003
|
+
if (iface) {
|
|
1004
|
+
const already = iface.getExtends().some((e) => e.getText().split("<")[0].trim() === baseType);
|
|
1005
|
+
if (!already) iface.addExtends(extendsExpr);
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
iface = sourceFile.addInterface({
|
|
1009
|
+
name: interfaceName,
|
|
1010
|
+
isExported: true,
|
|
1011
|
+
typeParameters,
|
|
1012
|
+
extends: [extendsExpr]
|
|
1013
|
+
});
|
|
1014
|
+
sourceFile.insertText(
|
|
1015
|
+
iface.getStart(),
|
|
1016
|
+
"// eslint-disable-next-line @typescript-eslint/no-empty-interface\n"
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
function ensureFileEslintDisable(sourceFile, rule) {
|
|
1020
|
+
if (sourceFile.getFullText().includes(rule)) return;
|
|
1021
|
+
sourceFile.insertText(0, `/* eslint-disable ${rule} */
|
|
1022
|
+
`);
|
|
1023
|
+
}
|
|
1024
|
+
function ensureModuleConst(sourceFile, spec) {
|
|
1025
|
+
if (sourceFile.getVariableDeclaration(spec.name)) return false;
|
|
1026
|
+
const imports = sourceFile.getImportDeclarations();
|
|
1027
|
+
const index = imports.length > 0 ? imports[imports.length - 1].getChildIndex() + 1 : 0;
|
|
1028
|
+
sourceFile.insertVariableStatement(index, {
|
|
1029
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
1030
|
+
declarations: [{ name: spec.name, initializer: spec.initializer }]
|
|
1031
|
+
});
|
|
1032
|
+
return true;
|
|
1033
|
+
}
|
|
1034
|
+
function addDecoratedMethod(cls, spec) {
|
|
1035
|
+
if (cls.getMethod(spec.name)) return false;
|
|
1036
|
+
cls.addMethod({
|
|
1037
|
+
name: spec.name,
|
|
1038
|
+
isAsync: spec.isAsync,
|
|
1039
|
+
returnType: spec.returnType,
|
|
1040
|
+
parameters: spec.parameters?.map((p) => {
|
|
1041
|
+
return { name: p.name, type: p.type, hasQuestionToken: p.optional };
|
|
1042
|
+
}),
|
|
1043
|
+
statements: spec.statements,
|
|
1044
|
+
decorators: [
|
|
1045
|
+
{
|
|
1046
|
+
name: spec.decoratorName,
|
|
1047
|
+
arguments: spec.decoratorArgsText ? [spec.decoratorArgsText] : []
|
|
1048
|
+
}
|
|
1049
|
+
]
|
|
1050
|
+
});
|
|
1051
|
+
return true;
|
|
1052
|
+
}
|
|
1053
|
+
function propertyInsertIndex(cls) {
|
|
1054
|
+
const props = cls.getProperties();
|
|
1055
|
+
if (props.length === 0) return 0;
|
|
1056
|
+
return props[props.length - 1].getChildIndex() + 1;
|
|
1057
|
+
}
|
|
1058
|
+
function addPlainProperty(cls, spec) {
|
|
1059
|
+
if (cls.getProperty(spec.name)) return false;
|
|
1060
|
+
cls.insertProperty(propertyInsertIndex(cls), {
|
|
1061
|
+
name: spec.name,
|
|
1062
|
+
type: spec.type,
|
|
1063
|
+
initializer: spec.initializer,
|
|
1064
|
+
isReadonly: !!spec.readonly,
|
|
1065
|
+
scope: spec.scope ? SCOPE_MAP[spec.scope] : void 0,
|
|
1066
|
+
hasExclamationToken: spec.initializer ? false : !!spec.hasExclamation
|
|
1067
|
+
});
|
|
1068
|
+
return true;
|
|
1069
|
+
}
|
|
1070
|
+
const SCOPE_MAP = {
|
|
1071
|
+
private: Scope.Private,
|
|
1072
|
+
protected: Scope.Protected,
|
|
1073
|
+
public: Scope.Public
|
|
1074
|
+
};
|
|
1075
|
+
function addDecoratedProperty(cls, spec) {
|
|
1076
|
+
if (cls.getProperty(spec.name)) return false;
|
|
1077
|
+
cls.insertProperty(propertyInsertIndex(cls), {
|
|
1078
|
+
name: spec.name,
|
|
1079
|
+
type: spec.type,
|
|
1080
|
+
initializer: spec.initializer,
|
|
1081
|
+
scope: spec.scope ? SCOPE_MAP[spec.scope] : void 0,
|
|
1082
|
+
hasExclamationToken: spec.initializer ? false : !!spec.hasExclamation,
|
|
1083
|
+
decorators: [
|
|
1084
|
+
spec.bare && !spec.decoratorArgsText ? { name: spec.decoratorName } : {
|
|
1085
|
+
name: spec.decoratorName,
|
|
1086
|
+
arguments: spec.decoratorArgsText ? [spec.decoratorArgsText] : []
|
|
1087
|
+
}
|
|
1088
|
+
]
|
|
1089
|
+
});
|
|
1090
|
+
return true;
|
|
1091
|
+
}
|
|
1092
|
+
function addGetter(cls, spec) {
|
|
1093
|
+
if (cls.getGetAccessor(spec.name)) return false;
|
|
1094
|
+
const ctor = cls.getConstructors()[0];
|
|
1095
|
+
const index = ctor ? ctor.getChildIndex() + 1 : propertyInsertIndex(cls);
|
|
1096
|
+
cls.insertGetAccessor(index, {
|
|
1097
|
+
name: spec.name,
|
|
1098
|
+
returnType: spec.returnType,
|
|
1099
|
+
statements: spec.statements ?? "// TODO: derive and return the computed value"
|
|
1100
|
+
});
|
|
1101
|
+
return true;
|
|
1102
|
+
}
|
|
1103
|
+
const CATALOG_METHODS = ["get", "put", "post", "delete", "patch"];
|
|
1104
|
+
const SERVICE_MODULE_RE$1 = /\/utils\/services\/([^/]+)\/([^/]+)\/service\.ts$/;
|
|
1105
|
+
function listServiceCatalog(codegenFs, query, projects) {
|
|
1106
|
+
const project = findProjectByName(
|
|
1107
|
+
codegenFs.root,
|
|
1108
|
+
query.modelProject,
|
|
1109
|
+
projects
|
|
1110
|
+
);
|
|
1111
|
+
if (!project) {
|
|
1112
|
+
throw new Error(`Project not found: ${query.modelProject}`);
|
|
1113
|
+
}
|
|
1114
|
+
const sourceRoot = project.sourceRoot || path.join(project.root, "src");
|
|
1115
|
+
const entries = [];
|
|
1116
|
+
for (const file of codegenFs.listFiles(sourceRoot)) {
|
|
1117
|
+
const posix = file.split(path.sep).join("/");
|
|
1118
|
+
const match = SERVICE_MODULE_RE$1.exec(posix);
|
|
1119
|
+
if (!match) continue;
|
|
1120
|
+
const [, app, version] = match;
|
|
1121
|
+
if (query.app && app !== query.app) continue;
|
|
1122
|
+
if (query.version && version !== query.version) continue;
|
|
1123
|
+
const openapiPath = posix.replace(/service\.ts$/, "openapi.d.ts");
|
|
1124
|
+
if (!codegenFs.exists(openapiPath)) continue;
|
|
1125
|
+
entries.push({
|
|
1126
|
+
app,
|
|
1127
|
+
version,
|
|
1128
|
+
serviceModulePath: posix,
|
|
1129
|
+
operations: readOperations(codegenFs, openapiPath)
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
return entries.sort(
|
|
1133
|
+
(a, b) => a.app.localeCompare(b.app) || a.version.localeCompare(b.version)
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
function readOperations(codegenFs, openapiPath) {
|
|
1137
|
+
return readSourceFile(codegenFs, openapiPath, (sf) => {
|
|
1138
|
+
const paths = sf.getInterface("paths");
|
|
1139
|
+
if (!paths) return [];
|
|
1140
|
+
const operations = [];
|
|
1141
|
+
for (const pathProp of paths.getProperties()) {
|
|
1142
|
+
const servicePath = unquote(pathProp.getName());
|
|
1143
|
+
const literal = pathProp.getTypeNode();
|
|
1144
|
+
if (!literal || !("getProperties" in literal)) continue;
|
|
1145
|
+
for (const methodProp of literal.getProperties()) {
|
|
1146
|
+
const method = methodProp.getName();
|
|
1147
|
+
if (!isCatalogMethod(method)) continue;
|
|
1148
|
+
const typeText = methodProp.getTypeNode()?.getText() ?? "";
|
|
1149
|
+
if (methodProp.hasQuestionToken() && typeText === "never") continue;
|
|
1150
|
+
operations.push({
|
|
1151
|
+
path: servicePath,
|
|
1152
|
+
method,
|
|
1153
|
+
summary: readSummary(methodProp),
|
|
1154
|
+
pathParams: [...servicePath.matchAll(/\{([^}]+)\}/g)].map(
|
|
1155
|
+
(m) => m[1]
|
|
1156
|
+
)
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
return operations;
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
function isCatalogMethod(value) {
|
|
1164
|
+
return CATALOG_METHODS.includes(value);
|
|
1165
|
+
}
|
|
1166
|
+
function readSummary(methodProp) {
|
|
1167
|
+
const text = methodProp.getLeadingCommentRanges().map((c) => c.getText()).join(" ").replace(/^\/\*+/, "").replace(/\*+\/$/, "").replace(/^\s*\*\s?/gm, "").replace(/\s+/g, " ").trim();
|
|
1168
|
+
return text || void 0;
|
|
1169
|
+
}
|
|
1170
|
+
function unquote(name) {
|
|
1171
|
+
return name.replace(/^["']|["']$/g, "");
|
|
915
1172
|
}
|
|
916
1173
|
const UPDATE_RELEASE_VERSION_SCRIPT = `import devkit from "@nx/devkit";
|
|
917
1174
|
import { resolve } from "path";
|
|
@@ -1473,160 +1730,6 @@ function normalizeAddFutureOptions(codegenFs, options, projects) {
|
|
|
1473
1730
|
internal
|
|
1474
1731
|
};
|
|
1475
1732
|
}
|
|
1476
|
-
function transformSourceFile(codegenFs, filePath, mutate) {
|
|
1477
|
-
const content = codegenFs.read(filePath);
|
|
1478
|
-
if (content === null) {
|
|
1479
|
-
throw new Error(`File not found: ${filePath}`);
|
|
1480
|
-
}
|
|
1481
|
-
const project = new Project({
|
|
1482
|
-
useInMemoryFileSystem: true,
|
|
1483
|
-
manipulationSettings: {
|
|
1484
|
-
indentationText: IndentationText.TwoSpaces,
|
|
1485
|
-
quoteKind: QuoteKind.Double
|
|
1486
|
-
}
|
|
1487
|
-
});
|
|
1488
|
-
const sourceFile = project.createSourceFile(filePath, content, {
|
|
1489
|
-
overwrite: true
|
|
1490
|
-
});
|
|
1491
|
-
mutate(sourceFile);
|
|
1492
|
-
codegenFs.write(filePath, sourceFile.getFullText());
|
|
1493
|
-
}
|
|
1494
|
-
function ensureNamedImport(sourceFile, moduleSpecifier, names) {
|
|
1495
|
-
const decls = sourceFile.getImportDeclarations().filter((d) => d.getModuleSpecifierValue() === moduleSpecifier);
|
|
1496
|
-
const existing = /* @__PURE__ */ new Set();
|
|
1497
|
-
for (const d of decls) {
|
|
1498
|
-
for (const n of d.getNamedImports()) existing.add(n.getName());
|
|
1499
|
-
}
|
|
1500
|
-
let target = decls.find((d) => !d.isTypeOnly());
|
|
1501
|
-
if (!target) {
|
|
1502
|
-
target = sourceFile.addImportDeclaration({ moduleSpecifier });
|
|
1503
|
-
}
|
|
1504
|
-
for (const { name, isTypeOnly } of names) {
|
|
1505
|
-
if (existing.has(name)) continue;
|
|
1506
|
-
target.addNamedImport({ name, isTypeOnly: !!isTypeOnly });
|
|
1507
|
-
existing.add(name);
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
function resolveSdkModuleSpecifier(sourceFile) {
|
|
1511
|
-
const decl = sourceFile.getImportDeclarations().find((d) => d.getNamedImports().some((n) => n.getName() === "kosModel"));
|
|
1512
|
-
return decl?.getModuleSpecifierValue() ?? "@kosdev-code/kos-ui-sdk";
|
|
1513
|
-
}
|
|
1514
|
-
function getModelClass(sourceFile, preferName) {
|
|
1515
|
-
const classes = sourceFile.getClasses();
|
|
1516
|
-
const byName = preferName ? classes.find((c) => c.getName() === preferName) : void 0;
|
|
1517
|
-
if (byName) return byName;
|
|
1518
|
-
const impl = classes.find((c) => /ModelImpl$/.test(c.getName() ?? ""));
|
|
1519
|
-
if (impl) return impl;
|
|
1520
|
-
const exported = classes.find((c) => c.isExported());
|
|
1521
|
-
if (exported) return exported;
|
|
1522
|
-
if (classes.length > 0) return classes[0];
|
|
1523
|
-
throw new Error("No class declaration found in model file.");
|
|
1524
|
-
}
|
|
1525
|
-
function addClassDecorator(cls, name, opts) {
|
|
1526
|
-
if (cls.getDecorator(name)) return;
|
|
1527
|
-
const typeArgs = opts?.typeArgs?.length ? `<${opts.typeArgs.join(", ")}>` : "";
|
|
1528
|
-
const args = opts?.argsText ?? "";
|
|
1529
|
-
const decorators = cls.getDecorators();
|
|
1530
|
-
const kosModelIdx = decorators.findIndex((d) => d.getName() === "kosModel");
|
|
1531
|
-
const insertIdx = kosModelIdx >= 0 ? kosModelIdx + 1 : decorators.length;
|
|
1532
|
-
cls.insertDecorator(insertIdx, {
|
|
1533
|
-
name: `${name}${typeArgs}`,
|
|
1534
|
-
arguments: args ? [args] : []
|
|
1535
|
-
});
|
|
1536
|
-
}
|
|
1537
|
-
function ensureDeclarationMerge(sourceFile, interfaceName, extendsExpr, typeParameters = []) {
|
|
1538
|
-
const baseType = extendsExpr.split("<")[0].trim();
|
|
1539
|
-
let iface = sourceFile.getInterface(interfaceName);
|
|
1540
|
-
if (iface) {
|
|
1541
|
-
const already = iface.getExtends().some((e) => e.getText().split("<")[0].trim() === baseType);
|
|
1542
|
-
if (!already) iface.addExtends(extendsExpr);
|
|
1543
|
-
return;
|
|
1544
|
-
}
|
|
1545
|
-
iface = sourceFile.addInterface({
|
|
1546
|
-
name: interfaceName,
|
|
1547
|
-
isExported: true,
|
|
1548
|
-
typeParameters,
|
|
1549
|
-
extends: [extendsExpr]
|
|
1550
|
-
});
|
|
1551
|
-
sourceFile.insertText(
|
|
1552
|
-
iface.getStart(),
|
|
1553
|
-
"// eslint-disable-next-line @typescript-eslint/no-empty-interface\n"
|
|
1554
|
-
);
|
|
1555
|
-
}
|
|
1556
|
-
function ensureFileEslintDisable(sourceFile, rule) {
|
|
1557
|
-
if (sourceFile.getFullText().includes(rule)) return;
|
|
1558
|
-
sourceFile.insertText(0, `/* eslint-disable ${rule} */
|
|
1559
|
-
`);
|
|
1560
|
-
}
|
|
1561
|
-
function addDecoratedMethod(cls, spec) {
|
|
1562
|
-
if (cls.getMethod(spec.name)) return false;
|
|
1563
|
-
cls.addMethod({
|
|
1564
|
-
name: spec.name,
|
|
1565
|
-
isAsync: spec.isAsync,
|
|
1566
|
-
returnType: spec.returnType,
|
|
1567
|
-
parameters: spec.parameters?.map((p) => {
|
|
1568
|
-
return { name: p.name, type: p.type };
|
|
1569
|
-
}),
|
|
1570
|
-
statements: spec.statements,
|
|
1571
|
-
decorators: [
|
|
1572
|
-
{
|
|
1573
|
-
name: spec.decoratorName,
|
|
1574
|
-
arguments: spec.decoratorArgsText ? [spec.decoratorArgsText] : []
|
|
1575
|
-
}
|
|
1576
|
-
]
|
|
1577
|
-
});
|
|
1578
|
-
return true;
|
|
1579
|
-
}
|
|
1580
|
-
function propertyInsertIndex(cls) {
|
|
1581
|
-
const props = cls.getProperties();
|
|
1582
|
-
if (props.length === 0) return 0;
|
|
1583
|
-
return props[props.length - 1].getChildIndex() + 1;
|
|
1584
|
-
}
|
|
1585
|
-
function addPlainProperty(cls, spec) {
|
|
1586
|
-
if (cls.getProperty(spec.name)) return false;
|
|
1587
|
-
cls.insertProperty(propertyInsertIndex(cls), {
|
|
1588
|
-
name: spec.name,
|
|
1589
|
-
type: spec.type,
|
|
1590
|
-
initializer: spec.initializer,
|
|
1591
|
-
isReadonly: !!spec.readonly,
|
|
1592
|
-
scope: spec.scope ? SCOPE_MAP[spec.scope] : void 0,
|
|
1593
|
-
hasExclamationToken: spec.initializer ? false : !!spec.hasExclamation
|
|
1594
|
-
});
|
|
1595
|
-
return true;
|
|
1596
|
-
}
|
|
1597
|
-
const SCOPE_MAP = {
|
|
1598
|
-
private: Scope.Private,
|
|
1599
|
-
protected: Scope.Protected,
|
|
1600
|
-
public: Scope.Public
|
|
1601
|
-
};
|
|
1602
|
-
function addDecoratedProperty(cls, spec) {
|
|
1603
|
-
if (cls.getProperty(spec.name)) return false;
|
|
1604
|
-
cls.insertProperty(propertyInsertIndex(cls), {
|
|
1605
|
-
name: spec.name,
|
|
1606
|
-
type: spec.type,
|
|
1607
|
-
initializer: spec.initializer,
|
|
1608
|
-
scope: spec.scope ? SCOPE_MAP[spec.scope] : void 0,
|
|
1609
|
-
hasExclamationToken: spec.initializer ? false : !!spec.hasExclamation,
|
|
1610
|
-
decorators: [
|
|
1611
|
-
spec.bare && !spec.decoratorArgsText ? { name: spec.decoratorName } : {
|
|
1612
|
-
name: spec.decoratorName,
|
|
1613
|
-
arguments: spec.decoratorArgsText ? [spec.decoratorArgsText] : []
|
|
1614
|
-
}
|
|
1615
|
-
]
|
|
1616
|
-
});
|
|
1617
|
-
return true;
|
|
1618
|
-
}
|
|
1619
|
-
function addGetter(cls, spec) {
|
|
1620
|
-
if (cls.getGetAccessor(spec.name)) return false;
|
|
1621
|
-
const ctor = cls.getConstructors()[0];
|
|
1622
|
-
const index = ctor ? ctor.getChildIndex() + 1 : propertyInsertIndex(cls);
|
|
1623
|
-
cls.insertGetAccessor(index, {
|
|
1624
|
-
name: spec.name,
|
|
1625
|
-
returnType: spec.returnType,
|
|
1626
|
-
statements: spec.statements ?? "// TODO: derive and return the computed value"
|
|
1627
|
-
});
|
|
1628
|
-
return true;
|
|
1629
|
-
}
|
|
1630
1733
|
const LEGACY_IMPORTS = /* @__PURE__ */ new Set([
|
|
1631
1734
|
"setupCompleteFutureSupport",
|
|
1632
1735
|
"setupMinimalFutureSupport",
|
|
@@ -2433,6 +2536,12 @@ function addServiceRequestToModel(codegenFs, options, projects) {
|
|
|
2433
2536
|
throw new Error(`Model file not found: ${modelFilePath}`);
|
|
2434
2537
|
}
|
|
2435
2538
|
let serviceImport = options.serviceModule;
|
|
2539
|
+
if (!serviceImport && options.serviceModulePath) {
|
|
2540
|
+
serviceImport = toImportSpecifier(
|
|
2541
|
+
modelFilePath,
|
|
2542
|
+
options.serviceModulePath.replace(/\.ts$/, "")
|
|
2543
|
+
);
|
|
2544
|
+
}
|
|
2436
2545
|
if (!serviceImport) {
|
|
2437
2546
|
if (!sourceRoot) {
|
|
2438
2547
|
throw new Error(
|
|
@@ -2458,31 +2567,93 @@ function addServiceRequestToModel(codegenFs, options, projects) {
|
|
|
2458
2567
|
serviceImport = specifiers[0];
|
|
2459
2568
|
}
|
|
2460
2569
|
const method = options.method || "get";
|
|
2570
|
+
const mode = options.mode ?? (options.lifecycle ? "lifecycle" : "method");
|
|
2461
2571
|
const lifecycle = options.lifecycle || "LOAD";
|
|
2462
2572
|
logger.info(
|
|
2463
|
-
`Adding @kosServiceRequest "${options.methodName}" (${method} ${options.servicePath}) to ${options.modelName}`
|
|
2573
|
+
`Adding ${mode}-driven @kosServiceRequest "${options.methodName}" (${method} ${options.servicePath}) to ${options.modelName}`
|
|
2464
2574
|
);
|
|
2465
2575
|
transformSourceFile(codegenFs, modelFilePath, (sf) => {
|
|
2576
|
+
const endpointConst = endpointConstName(options.methodName);
|
|
2577
|
+
const className = getModelClass(sf).getName();
|
|
2578
|
+
if (!className) throw new Error("Model class has no name.");
|
|
2579
|
+
const typeParams = getModelClass(sf).getTypeParameters().map((tp) => tp.getText());
|
|
2580
|
+
ensureNamedImport(sf, serviceImport, [
|
|
2581
|
+
{ name: "endpoint" },
|
|
2582
|
+
{ name: "serviceRequest" }
|
|
2583
|
+
]);
|
|
2584
|
+
ensureModuleConst(sf, {
|
|
2585
|
+
name: endpointConst,
|
|
2586
|
+
initializer: `endpoint(${JSON.stringify(
|
|
2587
|
+
options.servicePath
|
|
2588
|
+
)}, ${JSON.stringify(method)})`
|
|
2589
|
+
});
|
|
2590
|
+
if (mode === "lifecycle") {
|
|
2591
|
+
ensureNamedImport(sf, serviceImport, [
|
|
2592
|
+
{ name: "EndpointResponse", isTypeOnly: true }
|
|
2593
|
+
]);
|
|
2594
|
+
ensureNamedImport(sf, resolveSdkModuleSpecifier(sf), [
|
|
2595
|
+
{ name: "DependencyLifecycle" }
|
|
2596
|
+
]);
|
|
2597
|
+
addDecoratedMethod(getModelClass(sf), {
|
|
2598
|
+
name: options.methodName,
|
|
2599
|
+
decoratorName: "serviceRequest",
|
|
2600
|
+
decoratorArgsText: `${endpointConst}, { lifecycle: DependencyLifecycle.${lifecycle} }`,
|
|
2601
|
+
// The manager invokes phase handlers error-first, passing (null, data)
|
|
2602
|
+
// on success. The `error` half is only ever reached because
|
|
2603
|
+
// `serviceRequest` supplies an errorHandler — the bare decorator
|
|
2604
|
+
// defaults to `throw`, which fails the phase instead of calling back.
|
|
2605
|
+
parameters: [
|
|
2606
|
+
{ name: "error", type: "string | null" },
|
|
2607
|
+
{
|
|
2608
|
+
name: "data",
|
|
2609
|
+
type: `EndpointResponse<typeof ${endpointConst}>`
|
|
2610
|
+
}
|
|
2611
|
+
],
|
|
2612
|
+
returnType: "void",
|
|
2613
|
+
statements: "// TODO: apply `data` to the model"
|
|
2614
|
+
});
|
|
2615
|
+
return;
|
|
2616
|
+
}
|
|
2466
2617
|
ensureNamedImport(sf, serviceImport, [
|
|
2467
|
-
{ name: "
|
|
2618
|
+
{ name: "EndpointCtx", isTypeOnly: true }
|
|
2468
2619
|
]);
|
|
2469
2620
|
ensureNamedImport(sf, resolveSdkModuleSpecifier(sf), [
|
|
2470
|
-
{ name: "
|
|
2621
|
+
{ name: "executeServiceRequest" },
|
|
2622
|
+
{ name: "kosLoggerAware" },
|
|
2623
|
+
{ name: "KosLoggerAware", isTypeOnly: true }
|
|
2471
2624
|
]);
|
|
2472
|
-
|
|
2473
|
-
|
|
2625
|
+
ensureFileEslintDisable(
|
|
2626
|
+
sf,
|
|
2627
|
+
"@typescript-eslint/no-unsafe-declaration-merging"
|
|
2628
|
+
);
|
|
2629
|
+
ensureDeclarationMerge(sf, className, "KosLoggerAware", typeParams);
|
|
2630
|
+
addClassDecorator(getModelClass(sf), "kosLoggerAware", { argsText: "" });
|
|
2631
|
+
addDecoratedMethod(getModelClass(sf), {
|
|
2474
2632
|
name: options.methodName,
|
|
2475
|
-
decoratorName: "
|
|
2476
|
-
decoratorArgsText:
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2633
|
+
decoratorName: "serviceRequest",
|
|
2634
|
+
decoratorArgsText: endpointConst,
|
|
2635
|
+
// Optional: the framework appends the context, callers never pass it.
|
|
2636
|
+
parameters: [
|
|
2637
|
+
{
|
|
2638
|
+
name: "$ctx",
|
|
2639
|
+
type: `EndpointCtx<typeof ${endpointConst}>`,
|
|
2640
|
+
optional: true
|
|
2641
|
+
}
|
|
2642
|
+
],
|
|
2643
|
+
isAsync: true,
|
|
2644
|
+
returnType: "Promise<void>",
|
|
2645
|
+
statements: [
|
|
2646
|
+
"const data = await executeServiceRequest(this, $ctx);",
|
|
2647
|
+
"if (!data) return;",
|
|
2648
|
+
"// TODO: apply `data` to the model"
|
|
2649
|
+
].join("\n")
|
|
2483
2650
|
});
|
|
2484
2651
|
});
|
|
2485
|
-
return { modelFilePath, serviceModule: serviceImport };
|
|
2652
|
+
return { modelFilePath, serviceModule: serviceImport, mode };
|
|
2653
|
+
}
|
|
2654
|
+
function endpointConstName(methodName) {
|
|
2655
|
+
const snake = methodName.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").toUpperCase();
|
|
2656
|
+
return `ENDPOINT_${snake}`;
|
|
2486
2657
|
}
|
|
2487
2658
|
function modelElementType(typeText) {
|
|
2488
2659
|
let m;
|
|
@@ -3384,6 +3555,8 @@ export {
|
|
|
3384
3555
|
BasePluginHandler,
|
|
3385
3556
|
CONTRIBUTION_TYPE_MAP,
|
|
3386
3557
|
DirectFileSystem,
|
|
3558
|
+
KAB_OUTPUT_DIR,
|
|
3559
|
+
KAB_OUTPUT_PATH,
|
|
3387
3560
|
KOS_JSON_PATHS,
|
|
3388
3561
|
KosConfigBuilder,
|
|
3389
3562
|
LOCALIZED_PLUGIN_TYPES,
|
|
@@ -3437,6 +3610,7 @@ export {
|
|
|
3437
3610
|
getKosProjectConfiguration,
|
|
3438
3611
|
getProject,
|
|
3439
3612
|
getTemplateDir,
|
|
3613
|
+
listServiceCatalog,
|
|
3440
3614
|
lookupSdkType,
|
|
3441
3615
|
normalizeAllValues,
|
|
3442
3616
|
normalizeOptions,
|
|
@@ -3444,6 +3618,7 @@ export {
|
|
|
3444
3618
|
properCase,
|
|
3445
3619
|
readJson,
|
|
3446
3620
|
readNxJson,
|
|
3621
|
+
resolveKabPath,
|
|
3447
3622
|
resolveModelFilePath,
|
|
3448
3623
|
setCodegenLogger,
|
|
3449
3624
|
syncCiManifests,
|