@kosdev-code/kos-codegen-core 0.1.0-next.789 → 0.1.0-next.792
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 +2 -2
- package/index.d.ts.map +1 -1
- package/index.js +381 -450
- package/index.js.map +1 -1
- package/index.mjs +381 -450
- package/index.mjs.map +1 -1
- package/lib/generators/add-future-to-model/model-transformer.d.ts +3 -4
- package/lib/generators/add-future-to-model/model-transformer.d.ts.map +1 -1
- package/lib/generators/add-future-to-model/normalize-options.d.ts.map +1 -1
- package/lib/generators/add-future-to-model/registration-transformer.d.ts.map +1 -1
- package/lib/generators/augment/resolve-model-file.d.ts.map +1 -1
- package/lib/generators/index.d.ts +1 -0
- package/lib/generators/index.d.ts.map +1 -1
- package/package.json +2 -2
package/index.mjs
CHANGED
|
@@ -955,6 +955,63 @@ function generateModel(params) {
|
|
|
955
955
|
);
|
|
956
956
|
}
|
|
957
957
|
}
|
|
958
|
+
function resolveModelFilePath(codegenFs, query, projects) {
|
|
959
|
+
const kosConfig = getKosProjectConfiguration(
|
|
960
|
+
codegenFs,
|
|
961
|
+
query.modelProject,
|
|
962
|
+
projects
|
|
963
|
+
);
|
|
964
|
+
const internal = !!kosConfig?.generator?.internal;
|
|
965
|
+
const project = findProjectByName(
|
|
966
|
+
codegenFs.root,
|
|
967
|
+
query.modelProject,
|
|
968
|
+
projects
|
|
969
|
+
);
|
|
970
|
+
const sourceRoot = project ? project.sourceRoot || path.join(project.root, "src") : void 0;
|
|
971
|
+
if (query.modelPath) {
|
|
972
|
+
return { modelFilePath: query.modelPath, internal, sourceRoot };
|
|
973
|
+
}
|
|
974
|
+
if (!project) {
|
|
975
|
+
throw new Error(
|
|
976
|
+
`Project not found: ${query.modelProject}. Ensure a project.json exists.`
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
const { modelNameDashCase } = normalizeAllValues({
|
|
980
|
+
modelName: query.modelName
|
|
981
|
+
});
|
|
982
|
+
const modelLocation = kosConfig?.generator?.defaults?.model?.folder || "";
|
|
983
|
+
const modelFilePath = path.join(
|
|
984
|
+
sourceRoot,
|
|
985
|
+
modelLocation,
|
|
986
|
+
modelNameDashCase,
|
|
987
|
+
`${modelNameDashCase}-model.ts`
|
|
988
|
+
);
|
|
989
|
+
if (codegenFs.exists(modelFilePath)) {
|
|
990
|
+
return { modelFilePath, internal, sourceRoot };
|
|
991
|
+
}
|
|
992
|
+
const discovered = findModelFileByName(
|
|
993
|
+
codegenFs,
|
|
994
|
+
path.join(sourceRoot, modelLocation),
|
|
995
|
+
modelNameDashCase
|
|
996
|
+
);
|
|
997
|
+
if (discovered) {
|
|
998
|
+
return { modelFilePath: discovered, internal, sourceRoot };
|
|
999
|
+
}
|
|
1000
|
+
return { modelFilePath, internal, sourceRoot };
|
|
1001
|
+
}
|
|
1002
|
+
function findModelFileByName(codegenFs, searchRoot, modelNameDashCase) {
|
|
1003
|
+
const fileName = `${modelNameDashCase}-model.ts`;
|
|
1004
|
+
const candidates = codegenFs.listFiles(searchRoot).filter((f) => path.basename(f) === fileName).sort();
|
|
1005
|
+
if (candidates.length === 0) return null;
|
|
1006
|
+
if (candidates.length > 1) {
|
|
1007
|
+
throw new Error(
|
|
1008
|
+
`Model name '${modelNameDashCase}' is ambiguous — multiple files match ${fileName}:
|
|
1009
|
+
` + candidates.map((c) => ` - ${c}`).join("\n") + `
|
|
1010
|
+
Pass modelPath to pick one.`
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
return candidates[0];
|
|
1014
|
+
}
|
|
958
1015
|
function normalizeAddFutureOptions(codegenFs, options, projects) {
|
|
959
1016
|
const projectConfiguration = findProjectByName(
|
|
960
1017
|
codegenFs.root,
|
|
@@ -983,9 +1040,12 @@ function normalizeAddFutureOptions(codegenFs, options, projects) {
|
|
|
983
1040
|
const nameLowerCase = normalizedValues.modelNameLowerCase;
|
|
984
1041
|
const projectRoot = projectConfiguration.root;
|
|
985
1042
|
const sourceRoot = projectConfiguration.sourceRoot || path.join(projectRoot, "src");
|
|
986
|
-
const
|
|
987
|
-
|
|
988
|
-
|
|
1043
|
+
const { modelFilePath } = resolveModelFilePath(
|
|
1044
|
+
codegenFs,
|
|
1045
|
+
{ modelName: options.modelName, modelProject: options.modelProject },
|
|
1046
|
+
projects
|
|
1047
|
+
);
|
|
1048
|
+
const modelDirectory = path.dirname(modelFilePath);
|
|
989
1049
|
const servicesDirectory = path.join(modelDirectory, "services");
|
|
990
1050
|
const servicesFilePath = codegenFs.exists(servicesDirectory) ? path.join(servicesDirectory, `${nameDashCase}-services.ts`) : void 0;
|
|
991
1051
|
const registrationFilePath = path.join(
|
|
@@ -1008,6 +1068,173 @@ function normalizeAddFutureOptions(codegenFs, options, projects) {
|
|
|
1008
1068
|
internal
|
|
1009
1069
|
};
|
|
1010
1070
|
}
|
|
1071
|
+
function transformSourceFile(codegenFs, filePath, mutate) {
|
|
1072
|
+
const content = codegenFs.read(filePath);
|
|
1073
|
+
if (content === null) {
|
|
1074
|
+
throw new Error(`File not found: ${filePath}`);
|
|
1075
|
+
}
|
|
1076
|
+
const project = new Project({
|
|
1077
|
+
useInMemoryFileSystem: true,
|
|
1078
|
+
manipulationSettings: {
|
|
1079
|
+
indentationText: IndentationText.TwoSpaces,
|
|
1080
|
+
quoteKind: QuoteKind.Double
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
const sourceFile = project.createSourceFile(filePath, content, {
|
|
1084
|
+
overwrite: true
|
|
1085
|
+
});
|
|
1086
|
+
mutate(sourceFile);
|
|
1087
|
+
codegenFs.write(filePath, sourceFile.getFullText());
|
|
1088
|
+
}
|
|
1089
|
+
function ensureNamedImport(sourceFile, moduleSpecifier, names) {
|
|
1090
|
+
const decls = sourceFile.getImportDeclarations().filter((d) => d.getModuleSpecifierValue() === moduleSpecifier);
|
|
1091
|
+
const existing = /* @__PURE__ */ new Set();
|
|
1092
|
+
for (const d of decls) {
|
|
1093
|
+
for (const n of d.getNamedImports()) existing.add(n.getName());
|
|
1094
|
+
}
|
|
1095
|
+
let target = decls.find((d) => !d.isTypeOnly());
|
|
1096
|
+
if (!target) {
|
|
1097
|
+
target = sourceFile.addImportDeclaration({ moduleSpecifier });
|
|
1098
|
+
}
|
|
1099
|
+
for (const { name, isTypeOnly } of names) {
|
|
1100
|
+
if (existing.has(name)) continue;
|
|
1101
|
+
target.addNamedImport({ name, isTypeOnly: !!isTypeOnly });
|
|
1102
|
+
existing.add(name);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
function resolveSdkModuleSpecifier(sourceFile) {
|
|
1106
|
+
const decl = sourceFile.getImportDeclarations().find((d) => d.getNamedImports().some((n) => n.getName() === "kosModel"));
|
|
1107
|
+
return decl?.getModuleSpecifierValue() ?? "@kosdev-code/kos-ui-sdk";
|
|
1108
|
+
}
|
|
1109
|
+
function getModelClass(sourceFile, preferName) {
|
|
1110
|
+
const classes = sourceFile.getClasses();
|
|
1111
|
+
const byName = preferName ? classes.find((c) => c.getName() === preferName) : void 0;
|
|
1112
|
+
if (byName) return byName;
|
|
1113
|
+
const impl = classes.find((c) => /ModelImpl$/.test(c.getName() ?? ""));
|
|
1114
|
+
if (impl) return impl;
|
|
1115
|
+
const exported = classes.find((c) => c.isExported());
|
|
1116
|
+
if (exported) return exported;
|
|
1117
|
+
if (classes.length > 0) return classes[0];
|
|
1118
|
+
throw new Error("No class declaration found in model file.");
|
|
1119
|
+
}
|
|
1120
|
+
function addClassDecorator(cls, name, opts) {
|
|
1121
|
+
if (cls.getDecorator(name)) return;
|
|
1122
|
+
const typeArgs = opts?.typeArgs?.length ? `<${opts.typeArgs.join(", ")}>` : "";
|
|
1123
|
+
const args = opts?.argsText ?? "";
|
|
1124
|
+
const decorators = cls.getDecorators();
|
|
1125
|
+
const kosModelIdx = decorators.findIndex((d) => d.getName() === "kosModel");
|
|
1126
|
+
const insertIdx = kosModelIdx >= 0 ? kosModelIdx + 1 : decorators.length;
|
|
1127
|
+
cls.insertDecorator(insertIdx, {
|
|
1128
|
+
name: `${name}${typeArgs}`,
|
|
1129
|
+
arguments: args ? [args] : []
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
function ensureDeclarationMerge(sourceFile, interfaceName, extendsExpr, typeParameters = []) {
|
|
1133
|
+
const baseType = extendsExpr.split("<")[0].trim();
|
|
1134
|
+
let iface = sourceFile.getInterface(interfaceName);
|
|
1135
|
+
if (iface) {
|
|
1136
|
+
const already = iface.getExtends().some((e) => e.getText().split("<")[0].trim() === baseType);
|
|
1137
|
+
if (!already) iface.addExtends(extendsExpr);
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
iface = sourceFile.addInterface({
|
|
1141
|
+
name: interfaceName,
|
|
1142
|
+
isExported: true,
|
|
1143
|
+
typeParameters,
|
|
1144
|
+
extends: [extendsExpr]
|
|
1145
|
+
});
|
|
1146
|
+
sourceFile.insertText(
|
|
1147
|
+
iface.getStart(),
|
|
1148
|
+
"// eslint-disable-next-line @typescript-eslint/no-empty-interface\n"
|
|
1149
|
+
);
|
|
1150
|
+
}
|
|
1151
|
+
function ensureFileEslintDisable(sourceFile, rule) {
|
|
1152
|
+
if (sourceFile.getFullText().includes(rule)) return;
|
|
1153
|
+
sourceFile.insertText(0, `/* eslint-disable ${rule} */
|
|
1154
|
+
`);
|
|
1155
|
+
}
|
|
1156
|
+
function addDecoratedMethod(cls, spec) {
|
|
1157
|
+
if (cls.getMethod(spec.name)) return false;
|
|
1158
|
+
cls.addMethod({
|
|
1159
|
+
name: spec.name,
|
|
1160
|
+
isAsync: spec.isAsync,
|
|
1161
|
+
returnType: spec.returnType,
|
|
1162
|
+
parameters: spec.parameters?.map((p) => {
|
|
1163
|
+
return { name: p.name, type: p.type };
|
|
1164
|
+
}),
|
|
1165
|
+
statements: spec.statements,
|
|
1166
|
+
decorators: [
|
|
1167
|
+
{
|
|
1168
|
+
name: spec.decoratorName,
|
|
1169
|
+
arguments: spec.decoratorArgsText ? [spec.decoratorArgsText] : []
|
|
1170
|
+
}
|
|
1171
|
+
]
|
|
1172
|
+
});
|
|
1173
|
+
return true;
|
|
1174
|
+
}
|
|
1175
|
+
function propertyInsertIndex(cls) {
|
|
1176
|
+
const props = cls.getProperties();
|
|
1177
|
+
if (props.length === 0) return 0;
|
|
1178
|
+
return props[props.length - 1].getChildIndex() + 1;
|
|
1179
|
+
}
|
|
1180
|
+
function addPlainProperty(cls, spec) {
|
|
1181
|
+
if (cls.getProperty(spec.name)) return false;
|
|
1182
|
+
cls.insertProperty(propertyInsertIndex(cls), {
|
|
1183
|
+
name: spec.name,
|
|
1184
|
+
type: spec.type,
|
|
1185
|
+
initializer: spec.initializer,
|
|
1186
|
+
isReadonly: !!spec.readonly,
|
|
1187
|
+
scope: spec.scope ? SCOPE_MAP[spec.scope] : void 0,
|
|
1188
|
+
hasExclamationToken: spec.initializer ? false : !!spec.hasExclamation
|
|
1189
|
+
});
|
|
1190
|
+
return true;
|
|
1191
|
+
}
|
|
1192
|
+
const SCOPE_MAP = {
|
|
1193
|
+
private: Scope.Private,
|
|
1194
|
+
protected: Scope.Protected,
|
|
1195
|
+
public: Scope.Public
|
|
1196
|
+
};
|
|
1197
|
+
function addDecoratedProperty(cls, spec) {
|
|
1198
|
+
if (cls.getProperty(spec.name)) return false;
|
|
1199
|
+
cls.insertProperty(propertyInsertIndex(cls), {
|
|
1200
|
+
name: spec.name,
|
|
1201
|
+
type: spec.type,
|
|
1202
|
+
initializer: spec.initializer,
|
|
1203
|
+
scope: spec.scope ? SCOPE_MAP[spec.scope] : void 0,
|
|
1204
|
+
hasExclamationToken: spec.initializer ? false : !!spec.hasExclamation,
|
|
1205
|
+
decorators: [
|
|
1206
|
+
spec.bare && !spec.decoratorArgsText ? { name: spec.decoratorName } : {
|
|
1207
|
+
name: spec.decoratorName,
|
|
1208
|
+
arguments: spec.decoratorArgsText ? [spec.decoratorArgsText] : []
|
|
1209
|
+
}
|
|
1210
|
+
]
|
|
1211
|
+
});
|
|
1212
|
+
return true;
|
|
1213
|
+
}
|
|
1214
|
+
function addGetter(cls, spec) {
|
|
1215
|
+
if (cls.getGetAccessor(spec.name)) return false;
|
|
1216
|
+
const ctor = cls.getConstructors()[0];
|
|
1217
|
+
const index = ctor ? ctor.getChildIndex() + 1 : propertyInsertIndex(cls);
|
|
1218
|
+
cls.insertGetAccessor(index, {
|
|
1219
|
+
name: spec.name,
|
|
1220
|
+
returnType: spec.returnType,
|
|
1221
|
+
statements: spec.statements ?? "// TODO: derive and return the computed value"
|
|
1222
|
+
});
|
|
1223
|
+
return true;
|
|
1224
|
+
}
|
|
1225
|
+
const LEGACY_IMPORTS = /* @__PURE__ */ new Set([
|
|
1226
|
+
"setupCompleteFutureSupport",
|
|
1227
|
+
"setupMinimalFutureSupport",
|
|
1228
|
+
"FutureAwareContainer",
|
|
1229
|
+
"FutureHandlerContainer",
|
|
1230
|
+
"FutureStateAccessor",
|
|
1231
|
+
"FutureUpdateHandler"
|
|
1232
|
+
]);
|
|
1233
|
+
const LEGACY_IMPLEMENTS = /* @__PURE__ */ new Set([
|
|
1234
|
+
"FutureUpdateHandler",
|
|
1235
|
+
"FutureHandlerContainer",
|
|
1236
|
+
"FutureStateAccessor"
|
|
1237
|
+
]);
|
|
1011
1238
|
class ModelFileTransformer {
|
|
1012
1239
|
constructor(codegenFs, options) {
|
|
1013
1240
|
this.codegenFs = codegenFs;
|
|
@@ -1015,292 +1242,173 @@ class ModelFileTransformer {
|
|
|
1015
1242
|
}
|
|
1016
1243
|
codegenFs;
|
|
1017
1244
|
options;
|
|
1245
|
+
get progressType() {
|
|
1246
|
+
const { nameProperCase, updateServices } = this.options;
|
|
1247
|
+
return updateServices ? `${nameProperCase}OperationProgress` : "Record<string, unknown>";
|
|
1248
|
+
}
|
|
1018
1249
|
transform() {
|
|
1019
1250
|
const { modelFilePath } = this.options;
|
|
1020
1251
|
if (!this.codegenFs.exists(modelFilePath)) {
|
|
1021
1252
|
throw new Error(`Model file not found: ${modelFilePath}`);
|
|
1022
1253
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
if (content.includes("@typescript-eslint/no-unsafe-declaration-merging")) {
|
|
1039
|
-
return content;
|
|
1040
|
-
}
|
|
1041
|
-
const eslintDisable = "/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */\n";
|
|
1042
|
-
return eslintDisable + content;
|
|
1043
|
-
}
|
|
1044
|
-
addImports(content) {
|
|
1045
|
-
const { internal, futureType } = this.options;
|
|
1046
|
-
const isComplete = futureType === "complete";
|
|
1047
|
-
const kosModelImportRegex = internal ? /import\s*{\s*([^}]*kosModel[^}]*)\s*}\s*from\s*"\.\.\/\.\.\/\.\.\/core\/core\/decorators"/ : /import\s*{\s*([^}]*kosModel[^}]*)\s*}\s*from\s*"@kosdev-code\/kos-ui-sdk"/;
|
|
1048
|
-
const kosModelMatch = content.match(kosModelImportRegex);
|
|
1049
|
-
if (kosModelMatch) {
|
|
1050
|
-
const existingImportsStr = kosModelMatch[1] || "";
|
|
1051
|
-
const newImports = [
|
|
1052
|
-
"kosFuture",
|
|
1053
|
-
"kosFutureAware",
|
|
1054
|
-
isComplete ? "KosFutureAwareFull" : "KosFutureAwareMinimal"
|
|
1055
|
-
];
|
|
1056
|
-
const existingImports = existingImportsStr.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1057
|
-
const importsToRemove = [
|
|
1058
|
-
"setupCompleteFutureSupport",
|
|
1059
|
-
"setupMinimalFutureSupport"
|
|
1060
|
-
];
|
|
1061
|
-
const cleanedImports = existingImports.filter(
|
|
1062
|
-
(imp) => !importsToRemove.includes(imp)
|
|
1063
|
-
);
|
|
1064
|
-
const importsToAdd = newImports.filter(
|
|
1065
|
-
(imp) => !cleanedImports.includes(imp)
|
|
1066
|
-
);
|
|
1067
|
-
const allImports = [...cleanedImports, ...importsToAdd];
|
|
1068
|
-
const newImportLine = internal ? `import { ${allImports.join(
|
|
1069
|
-
", "
|
|
1070
|
-
)} } from "../../../core/core/decorators"` : `import { ${allImports.join(", ")} } from "@kosdev-code/kos-ui-sdk"`;
|
|
1071
|
-
content = content.replace(kosModelImportRegex, newImportLine);
|
|
1072
|
-
}
|
|
1073
|
-
const typeImportsBase = internal ? "../../../models/types/future-interfaces" : "@kosdev-code/kos-ui-sdk";
|
|
1074
|
-
const futureTypeImports = ["ExternalFutureInterface", "IFutureModel"];
|
|
1075
|
-
const typeImportRegex = internal ? /import type {([^}]*)} from "\.\.\/\.\.\/\.\.\/models\/types\/future-interfaces"/ : /import type {([^}]*)} from "@kosdev-code\/kos-ui-sdk"/;
|
|
1076
|
-
const typeImportMatch = content.match(typeImportRegex);
|
|
1077
|
-
if (typeImportMatch) {
|
|
1078
|
-
const existingTypes = typeImportMatch[1] || "";
|
|
1079
|
-
const existingTypesList = existingTypes.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1080
|
-
const typesToRemove = [
|
|
1081
|
-
"FutureAwareContainer",
|
|
1082
|
-
"FutureHandlerContainer",
|
|
1083
|
-
"FutureStateAccessor",
|
|
1084
|
-
"FutureUpdateHandler"
|
|
1085
|
-
];
|
|
1086
|
-
const cleanedTypes = existingTypesList.filter(
|
|
1087
|
-
(type) => !typesToRemove.includes(type)
|
|
1088
|
-
);
|
|
1089
|
-
const typesToAdd = futureTypeImports.filter(
|
|
1090
|
-
(type) => !cleanedTypes.includes(type)
|
|
1254
|
+
transformSourceFile(this.codegenFs, modelFilePath, (sf) => {
|
|
1255
|
+
this.removeLegacyImports(sf);
|
|
1256
|
+
this.addImports(sf);
|
|
1257
|
+
const cls = getModelClass(sf, `${this.options.nameProperCase}ModelImpl`);
|
|
1258
|
+
this.removeLegacyClassMembers(cls);
|
|
1259
|
+
this.addDecorator(cls);
|
|
1260
|
+
this.addFutureMethod(cls);
|
|
1261
|
+
if (this.options.futureType === "complete") {
|
|
1262
|
+
this.addOnFutureUpdateMethod(cls);
|
|
1263
|
+
}
|
|
1264
|
+
this.updatePublicType(sf);
|
|
1265
|
+
ensureDeclarationMerge(
|
|
1266
|
+
sf,
|
|
1267
|
+
`${this.options.nameProperCase}ModelImpl`,
|
|
1268
|
+
`${this.options.futureType === "complete" ? "KosFutureAwareFull" : "KosFutureAwareMinimal"}<${this.progressType}>`
|
|
1091
1269
|
);
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
} else {
|
|
1096
|
-
const importLines = content.split("\n");
|
|
1097
|
-
const lastImportIndex = importLines.findLastIndex(
|
|
1098
|
-
(line) => line.trim().startsWith("import")
|
|
1270
|
+
ensureFileEslintDisable(
|
|
1271
|
+
sf,
|
|
1272
|
+
"@typescript-eslint/no-unsafe-declaration-merging"
|
|
1099
1273
|
);
|
|
1100
|
-
|
|
1101
|
-
const newTypeImport = `import type { ${futureTypeImports.join(
|
|
1102
|
-
", "
|
|
1103
|
-
)} } from "${typeImportsBase}";`;
|
|
1104
|
-
importLines.splice(lastImportIndex + 1, 0, newTypeImport);
|
|
1105
|
-
content = importLines.join("\n");
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
return content;
|
|
1274
|
+
});
|
|
1109
1275
|
}
|
|
1110
|
-
|
|
1111
|
-
const
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
}
|
|
1115
|
-
if (content.includes(`${nameProperCase}OperationProgress`)) {
|
|
1116
|
-
return content;
|
|
1117
|
-
}
|
|
1118
|
-
const servicesImportRegex = /import\s*{([^}]*)}\s*from\s*["']\.\/services["'];?/s;
|
|
1119
|
-
const servicesMatch = content.match(servicesImportRegex);
|
|
1120
|
-
if (servicesMatch) {
|
|
1121
|
-
const existingImports = servicesMatch[1];
|
|
1122
|
-
const cleanedImports = existingImports.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1123
|
-
cleanedImports.push(`${nameProperCase}OperationProgress`);
|
|
1124
|
-
const newImport = `import { ${cleanedImports.join(
|
|
1125
|
-
", "
|
|
1126
|
-
)} } from "./services";`;
|
|
1127
|
-
content = content.replace(servicesImportRegex, newImport);
|
|
1128
|
-
} else {
|
|
1129
|
-
const typesImportRegex = /import\s+type\s+{[^}]*}\s+from\s+["']\.\/types["'];?/;
|
|
1130
|
-
const typesMatch = content.match(typesImportRegex);
|
|
1131
|
-
if (typesMatch) {
|
|
1132
|
-
const newImport = `
|
|
1133
|
-
import type { ${nameProperCase}OperationProgress } from "./services";`;
|
|
1134
|
-
content = content.replace(typesMatch[0], typesMatch[0] + newImport);
|
|
1276
|
+
removeLegacyImports(sf) {
|
|
1277
|
+
for (const decl of sf.getImportDeclarations()) {
|
|
1278
|
+
for (const named of decl.getNamedImports()) {
|
|
1279
|
+
if (LEGACY_IMPORTS.has(named.getName())) named.remove();
|
|
1135
1280
|
}
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
}
|
|
1139
|
-
addInterfaceMerging(content) {
|
|
1140
|
-
const { nameProperCase, futureType } = this.options;
|
|
1141
|
-
const isComplete = futureType === "complete";
|
|
1142
|
-
const interfaceType = isComplete ? "KosFutureAwareFull" : "KosFutureAwareMinimal";
|
|
1143
|
-
const progressType = this.options.updateServices ? `${nameProperCase}OperationProgress` : "Record<string, unknown>";
|
|
1144
|
-
const classRegex = new RegExp(
|
|
1145
|
-
`(@kosModel[^\\n]*\\n)([^\\n]*export\\s+class\\s+${nameProperCase}ModelImpl)`,
|
|
1146
|
-
"m"
|
|
1147
|
-
);
|
|
1148
|
-
const classMatch = content.match(classRegex);
|
|
1149
|
-
if (classMatch) {
|
|
1150
|
-
const interfaceRegex = new RegExp(
|
|
1151
|
-
`interface\\s+${nameProperCase}ModelImpl\\s+extends`
|
|
1152
|
-
);
|
|
1153
|
-
if (!content.match(interfaceRegex)) {
|
|
1154
|
-
const interfaceMerging = `
|
|
1155
|
-
// Interface merging for Future Container type safety
|
|
1156
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
1157
|
-
export interface ${nameProperCase}ModelImpl extends ${interfaceType}<${progressType}> {}
|
|
1158
|
-
|
|
1159
|
-
`;
|
|
1160
|
-
content = content.replace(
|
|
1161
|
-
classMatch[0],
|
|
1162
|
-
interfaceMerging + classMatch[0]
|
|
1163
|
-
);
|
|
1281
|
+
if (decl.getNamedImports().length === 0 && !decl.getDefaultImport() && !decl.getNamespaceImport()) {
|
|
1282
|
+
decl.remove();
|
|
1164
1283
|
}
|
|
1165
1284
|
}
|
|
1166
|
-
return content;
|
|
1167
1285
|
}
|
|
1168
|
-
|
|
1169
|
-
const {
|
|
1286
|
+
addImports(sf) {
|
|
1287
|
+
const { internal, futureType, updateServices, nameProperCase } = this.options;
|
|
1170
1288
|
const isComplete = futureType === "complete";
|
|
1171
|
-
const
|
|
1172
|
-
|
|
1173
|
-
"
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
const decoratorOptions = isComplete ? "" : "{ mode: 'minimal' }";
|
|
1179
|
-
const futureDecorator = `@kosFutureAware(${decoratorOptions})
|
|
1180
|
-
`;
|
|
1181
|
-
content = content.replace(
|
|
1182
|
-
classMatch[0],
|
|
1183
|
-
classMatch[1] + classMatch[2] + futureDecorator + classMatch[3]
|
|
1184
|
-
);
|
|
1289
|
+
const sdkSpec = resolveSdkModuleSpecifier(sf);
|
|
1290
|
+
ensureNamedImport(sf, sdkSpec, [
|
|
1291
|
+
{ name: "kosFuture" },
|
|
1292
|
+
{ name: "kosFutureAware" },
|
|
1293
|
+
{
|
|
1294
|
+
name: isComplete ? "KosFutureAwareFull" : "KosFutureAwareMinimal",
|
|
1295
|
+
isTypeOnly: true
|
|
1185
1296
|
}
|
|
1297
|
+
]);
|
|
1298
|
+
const typeSpec = internal ? "../../../models/types/future-interfaces" : sdkSpec;
|
|
1299
|
+
ensureNamedImport(sf, typeSpec, [
|
|
1300
|
+
{ name: "ExternalFutureInterface", isTypeOnly: true },
|
|
1301
|
+
...isComplete ? [{ name: "IFutureModel", isTypeOnly: true }] : []
|
|
1302
|
+
]);
|
|
1303
|
+
if (updateServices) {
|
|
1304
|
+
ensureNamedImport(sf, "./services", [
|
|
1305
|
+
{ name: `${nameProperCase}OperationProgress`, isTypeOnly: true }
|
|
1306
|
+
]);
|
|
1186
1307
|
}
|
|
1187
|
-
return content;
|
|
1188
1308
|
}
|
|
1189
|
-
|
|
1190
|
-
const
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
if (typeMatch) {
|
|
1198
|
-
if (!typeMatch[1].includes("ExternalFutureInterface")) {
|
|
1199
|
-
const newType = `export type ${nameProperCase}Model = PublicModelInterface<${nameProperCase}ModelImpl> & ExternalFutureInterface<${progressType}>;`;
|
|
1200
|
-
content = content.replace(typeMatch[0], newType);
|
|
1309
|
+
removeLegacyClassMembers(cls) {
|
|
1310
|
+
for (const ctor of cls.getConstructors()) {
|
|
1311
|
+
for (const stmt of ctor.getStatements()) {
|
|
1312
|
+
if (/setup(Complete|Minimal)FutureSupport\s*\(\s*this\s*\)/.test(
|
|
1313
|
+
stmt.getText()
|
|
1314
|
+
)) {
|
|
1315
|
+
stmt.remove();
|
|
1316
|
+
}
|
|
1201
1317
|
}
|
|
1202
1318
|
}
|
|
1203
|
-
|
|
1319
|
+
const futureHandler = cls.getProperty("futureHandler");
|
|
1320
|
+
if (futureHandler?.getTypeNode()?.getText().includes("FutureAwareContainer")) {
|
|
1321
|
+
futureHandler.remove();
|
|
1322
|
+
}
|
|
1323
|
+
const future = cls.getProperty("future");
|
|
1324
|
+
if (future?.getTypeNode()?.getText().includes("IFutureModel")) {
|
|
1325
|
+
future.remove();
|
|
1326
|
+
}
|
|
1327
|
+
const impls = cls.getImplements();
|
|
1328
|
+
for (let i = impls.length - 1; i >= 0; i--) {
|
|
1329
|
+
const base = impls[i].getText().split("<")[0].trim();
|
|
1330
|
+
if (LEGACY_IMPLEMENTS.has(base)) cls.removeImplements(i);
|
|
1331
|
+
}
|
|
1204
1332
|
}
|
|
1205
|
-
|
|
1206
|
-
const
|
|
1207
|
-
|
|
1208
|
-
const propertyRegex = /\s*(public|private|protected)?\s*(declare\s+)?futureHandler[!?]?:\s*FutureAwareContainer[^;]*;\s*/g;
|
|
1209
|
-
content = content.replace(propertyRegex, "");
|
|
1210
|
-
const futurePropertyRegex = /\s*(public|private|protected)?\s*(declare\s+)?future\??:\s*IFutureModel[^;]*;\s*/g;
|
|
1211
|
-
content = content.replace(futurePropertyRegex, "");
|
|
1212
|
-
const implementsRegex = new RegExp(
|
|
1213
|
-
`(implements\\s+[^{]*?)\\s*,?\\s*(FutureUpdateHandler|FutureHandlerContainer|FutureStateAccessor)`,
|
|
1214
|
-
"g"
|
|
1215
|
-
);
|
|
1216
|
-
content = content.replace(implementsRegex, "$1");
|
|
1217
|
-
content = content.replace(/,\s*,/g, ",");
|
|
1218
|
-
content = content.replace(/implements\s*,/g, "implements");
|
|
1219
|
-
return content;
|
|
1333
|
+
addDecorator(cls) {
|
|
1334
|
+
const argsText = this.options.futureType === "complete" ? "" : `{ mode: "minimal" }`;
|
|
1335
|
+
addClassDecorator(cls, "kosFutureAware", { argsText });
|
|
1220
1336
|
}
|
|
1221
|
-
|
|
1337
|
+
updatePublicType(sf) {
|
|
1222
1338
|
const { nameProperCase } = this.options;
|
|
1223
|
-
|
|
1224
|
-
|
|
1339
|
+
const alias = sf.getTypeAlias(`${nameProperCase}Model`);
|
|
1340
|
+
if (!alias) return;
|
|
1341
|
+
const text = alias.getTypeNode()?.getText() ?? "";
|
|
1342
|
+
if (!text.includes(`PublicModelInterface<${nameProperCase}ModelImpl>`) || text.includes("ExternalFutureInterface")) {
|
|
1343
|
+
return;
|
|
1225
1344
|
}
|
|
1226
|
-
|
|
1227
|
-
`
|
|
1228
|
-
"m"
|
|
1345
|
+
alias.setType(
|
|
1346
|
+
`PublicModelInterface<${nameProperCase}ModelImpl> & ExternalFutureInterface<${this.progressType}>`
|
|
1229
1347
|
);
|
|
1230
|
-
const classMatch = content.match(classRegex);
|
|
1231
|
-
if (classMatch) {
|
|
1232
|
-
const methodCode = `
|
|
1233
|
-
/**
|
|
1234
|
-
* Placeholder method for Future operations
|
|
1235
|
-
* Replace this with your actual long-running operation
|
|
1236
|
-
*/
|
|
1237
|
-
@kosFuture()
|
|
1238
|
-
async performLongRunningOperation(): Promise<void> {
|
|
1239
|
-
// TODO: Implement your long-running operation here
|
|
1240
|
-
// This method should use a service that returns a Future for progress tracking
|
|
1241
|
-
|
|
1242
|
-
this.logger.debug(\`Starting long-running operation for \${this.id}\`);
|
|
1243
|
-
|
|
1244
|
-
// Example implementation pattern using services:
|
|
1245
|
-
// import { perform${nameProperCase}Operation } from './services';
|
|
1246
|
-
//
|
|
1247
|
-
// const future = await perform${nameProperCase}Operation();
|
|
1248
|
-
// return this.futureHandler.setFuture(future);
|
|
1249
|
-
|
|
1250
|
-
// Placeholder that doesn't actually do anything
|
|
1251
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
1252
|
-
|
|
1253
|
-
this.logger.debug(\`Completed long-running operation for \${this.id}\`);
|
|
1254
1348
|
}
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1349
|
+
addFutureMethod(cls) {
|
|
1350
|
+
const { nameProperCase } = this.options;
|
|
1351
|
+
const hasFutureMethod = cls.getMethods().some((m) => m.getDecorator("kosFuture"));
|
|
1352
|
+
if (hasFutureMethod || cls.getMethod("performLongRunningOperation")) return;
|
|
1353
|
+
cls.addMethod({
|
|
1354
|
+
name: "performLongRunningOperation",
|
|
1355
|
+
isAsync: true,
|
|
1356
|
+
returnType: "Promise<void>",
|
|
1357
|
+
decorators: [{ name: "kosFuture", arguments: [] }],
|
|
1358
|
+
docs: [
|
|
1359
|
+
{
|
|
1360
|
+
description: "Placeholder method for Future operations\nReplace this with your actual long-running operation"
|
|
1361
|
+
}
|
|
1362
|
+
],
|
|
1363
|
+
statements: [
|
|
1364
|
+
"// TODO: Implement your long-running operation here",
|
|
1365
|
+
"// This method should use a service that returns a Future for progress tracking",
|
|
1366
|
+
"",
|
|
1367
|
+
"this.logger.debug(`Starting long-running operation for ${this.id}`);",
|
|
1368
|
+
"",
|
|
1369
|
+
"// Example implementation pattern using services:",
|
|
1370
|
+
`// import { perform${nameProperCase}Operation } from './services';`,
|
|
1371
|
+
"//",
|
|
1372
|
+
`// const future = await perform${nameProperCase}Operation();`,
|
|
1373
|
+
"// return this.futureHandler.setFuture(future);",
|
|
1374
|
+
"",
|
|
1375
|
+
"// Placeholder that doesn't actually do anything",
|
|
1376
|
+
"await new Promise((resolve) => setTimeout(resolve, 1000));",
|
|
1377
|
+
"",
|
|
1378
|
+
"this.logger.debug(`Completed long-running operation for ${this.id}`);"
|
|
1379
|
+
]
|
|
1380
|
+
});
|
|
1267
1381
|
}
|
|
1268
|
-
addOnFutureUpdateMethod(
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1382
|
+
addOnFutureUpdateMethod(cls) {
|
|
1383
|
+
if (cls.getMethod("onFutureUpdate")) return;
|
|
1384
|
+
cls.addMethod({
|
|
1385
|
+
name: "onFutureUpdate",
|
|
1386
|
+
hasQuestionToken: true,
|
|
1387
|
+
returnType: "void",
|
|
1388
|
+
parameters: [
|
|
1389
|
+
{ name: "update", type: `IFutureModel<${this.progressType}>` }
|
|
1390
|
+
],
|
|
1391
|
+
docs: [
|
|
1392
|
+
{
|
|
1393
|
+
description: "Optional: Custom Future update handling\nCalled whenever the Future state changes (progress, status, completion, etc.)"
|
|
1394
|
+
}
|
|
1395
|
+
],
|
|
1396
|
+
statements: [
|
|
1397
|
+
"// Add custom Future update logic here",
|
|
1398
|
+
"// Examples:",
|
|
1399
|
+
"// - Log progress milestones",
|
|
1400
|
+
"// - Update derived state based on progress",
|
|
1401
|
+
"// - Handle specific error conditions",
|
|
1402
|
+
"// - Trigger notifications at certain thresholds",
|
|
1403
|
+
"",
|
|
1404
|
+
"this.logger.debug(`Future update for ${this.id}:`, {",
|
|
1405
|
+
" progress: update.progress,",
|
|
1406
|
+
" status: update.status,",
|
|
1407
|
+
" endState: update.endState,",
|
|
1408
|
+
" clientData: update.clientData,",
|
|
1409
|
+
"});"
|
|
1410
|
+
]
|
|
1296
1411
|
});
|
|
1297
|
-
}`;
|
|
1298
|
-
content = content.replace(
|
|
1299
|
-
futureMethodMatch[0],
|
|
1300
|
-
futureMethodMatch[0] + methodCode
|
|
1301
|
-
);
|
|
1302
|
-
}
|
|
1303
|
-
return content;
|
|
1304
1412
|
}
|
|
1305
1413
|
}
|
|
1306
1414
|
class ServiceFileTransformer {
|
|
@@ -1483,9 +1591,16 @@ class RegistrationFileTransformer {
|
|
|
1483
1591
|
logger.warn("Registration file not found, skipping registration updates");
|
|
1484
1592
|
return;
|
|
1485
1593
|
}
|
|
1486
|
-
|
|
1594
|
+
const original = this.codegenFs.read(registrationFilePath);
|
|
1595
|
+
let content = original;
|
|
1487
1596
|
content = this.addTypeCast(content);
|
|
1488
1597
|
content = this.updateDocumentation(content);
|
|
1598
|
+
if (content === original) {
|
|
1599
|
+
logger.info(
|
|
1600
|
+
"Registration file has no legacy future patterns — leaving it untouched"
|
|
1601
|
+
);
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1489
1604
|
this.codegenFs.write(registrationFilePath, content);
|
|
1490
1605
|
}
|
|
1491
1606
|
addTypeCast(content) {
|
|
@@ -1553,7 +1668,7 @@ function addFutureToModel(codegenFs, options, projects) {
|
|
|
1553
1668
|
}
|
|
1554
1669
|
if (normalized.registrationFilePath) {
|
|
1555
1670
|
logger.info(
|
|
1556
|
-
`Would
|
|
1671
|
+
`Would update registration file (only if legacy patterns are present): ${normalized.registrationFilePath}`
|
|
1557
1672
|
);
|
|
1558
1673
|
}
|
|
1559
1674
|
return;
|
|
@@ -1604,191 +1719,6 @@ function addFutureToModel(codegenFs, options, projects) {
|
|
|
1604
1719
|
throw error;
|
|
1605
1720
|
}
|
|
1606
1721
|
}
|
|
1607
|
-
function resolveModelFilePath(codegenFs, query, projects) {
|
|
1608
|
-
const kosConfig = getKosProjectConfiguration(
|
|
1609
|
-
codegenFs,
|
|
1610
|
-
query.modelProject,
|
|
1611
|
-
projects
|
|
1612
|
-
);
|
|
1613
|
-
const internal = !!kosConfig?.generator?.internal;
|
|
1614
|
-
const project = findProjectByName(
|
|
1615
|
-
codegenFs.root,
|
|
1616
|
-
query.modelProject,
|
|
1617
|
-
projects
|
|
1618
|
-
);
|
|
1619
|
-
const sourceRoot = project ? project.sourceRoot || path.join(project.root, "src") : void 0;
|
|
1620
|
-
if (query.modelPath) {
|
|
1621
|
-
return { modelFilePath: query.modelPath, internal, sourceRoot };
|
|
1622
|
-
}
|
|
1623
|
-
if (!project) {
|
|
1624
|
-
throw new Error(
|
|
1625
|
-
`Project not found: ${query.modelProject}. Ensure a project.json exists.`
|
|
1626
|
-
);
|
|
1627
|
-
}
|
|
1628
|
-
const { modelNameDashCase } = normalizeAllValues({
|
|
1629
|
-
modelName: query.modelName
|
|
1630
|
-
});
|
|
1631
|
-
const modelLocation = kosConfig?.generator?.defaults?.model?.folder || "";
|
|
1632
|
-
const modelFilePath = path.join(
|
|
1633
|
-
sourceRoot,
|
|
1634
|
-
modelLocation,
|
|
1635
|
-
modelNameDashCase,
|
|
1636
|
-
`${modelNameDashCase}-model.ts`
|
|
1637
|
-
);
|
|
1638
|
-
return { modelFilePath, internal, sourceRoot };
|
|
1639
|
-
}
|
|
1640
|
-
function transformSourceFile(codegenFs, filePath, mutate) {
|
|
1641
|
-
const content = codegenFs.read(filePath);
|
|
1642
|
-
if (content === null) {
|
|
1643
|
-
throw new Error(`File not found: ${filePath}`);
|
|
1644
|
-
}
|
|
1645
|
-
const project = new Project({
|
|
1646
|
-
useInMemoryFileSystem: true,
|
|
1647
|
-
manipulationSettings: {
|
|
1648
|
-
indentationText: IndentationText.TwoSpaces,
|
|
1649
|
-
quoteKind: QuoteKind.Double
|
|
1650
|
-
}
|
|
1651
|
-
});
|
|
1652
|
-
const sourceFile = project.createSourceFile(filePath, content, {
|
|
1653
|
-
overwrite: true
|
|
1654
|
-
});
|
|
1655
|
-
mutate(sourceFile);
|
|
1656
|
-
codegenFs.write(filePath, sourceFile.getFullText());
|
|
1657
|
-
}
|
|
1658
|
-
function ensureNamedImport(sourceFile, moduleSpecifier, names) {
|
|
1659
|
-
const decls = sourceFile.getImportDeclarations().filter((d) => d.getModuleSpecifierValue() === moduleSpecifier);
|
|
1660
|
-
const existing = /* @__PURE__ */ new Set();
|
|
1661
|
-
for (const d of decls) {
|
|
1662
|
-
for (const n of d.getNamedImports()) existing.add(n.getName());
|
|
1663
|
-
}
|
|
1664
|
-
let target = decls.find((d) => !d.isTypeOnly());
|
|
1665
|
-
if (!target) {
|
|
1666
|
-
target = sourceFile.addImportDeclaration({ moduleSpecifier });
|
|
1667
|
-
}
|
|
1668
|
-
for (const { name, isTypeOnly } of names) {
|
|
1669
|
-
if (existing.has(name)) continue;
|
|
1670
|
-
target.addNamedImport({ name, isTypeOnly: !!isTypeOnly });
|
|
1671
|
-
existing.add(name);
|
|
1672
|
-
}
|
|
1673
|
-
}
|
|
1674
|
-
function resolveSdkModuleSpecifier(sourceFile) {
|
|
1675
|
-
const decl = sourceFile.getImportDeclarations().find((d) => d.getNamedImports().some((n) => n.getName() === "kosModel"));
|
|
1676
|
-
return decl?.getModuleSpecifierValue() ?? "@kosdev-code/kos-ui-sdk";
|
|
1677
|
-
}
|
|
1678
|
-
function getModelClass(sourceFile, preferName) {
|
|
1679
|
-
const classes = sourceFile.getClasses();
|
|
1680
|
-
const impl = classes.find((c) => /ModelImpl$/.test(c.getName() ?? ""));
|
|
1681
|
-
if (impl) return impl;
|
|
1682
|
-
const exported = classes.find((c) => c.isExported());
|
|
1683
|
-
if (exported) return exported;
|
|
1684
|
-
if (classes.length > 0) return classes[0];
|
|
1685
|
-
throw new Error("No class declaration found in model file.");
|
|
1686
|
-
}
|
|
1687
|
-
function addClassDecorator(cls, name, opts) {
|
|
1688
|
-
if (cls.getDecorator(name)) return;
|
|
1689
|
-
const typeArgs = opts?.typeArgs?.length ? `<${opts.typeArgs.join(", ")}>` : "";
|
|
1690
|
-
const args = opts?.argsText ?? "";
|
|
1691
|
-
const decorators = cls.getDecorators();
|
|
1692
|
-
const kosModelIdx = decorators.findIndex((d) => d.getName() === "kosModel");
|
|
1693
|
-
const insertIdx = kosModelIdx >= 0 ? kosModelIdx + 1 : decorators.length;
|
|
1694
|
-
cls.insertDecorator(insertIdx, {
|
|
1695
|
-
name: `${name}${typeArgs}`,
|
|
1696
|
-
arguments: args ? [args] : []
|
|
1697
|
-
});
|
|
1698
|
-
}
|
|
1699
|
-
function ensureDeclarationMerge(sourceFile, interfaceName, extendsExpr, typeParameters = []) {
|
|
1700
|
-
const baseType = extendsExpr.split("<")[0].trim();
|
|
1701
|
-
let iface = sourceFile.getInterface(interfaceName);
|
|
1702
|
-
if (iface) {
|
|
1703
|
-
const already = iface.getExtends().some((e) => e.getText().split("<")[0].trim() === baseType);
|
|
1704
|
-
if (!already) iface.addExtends(extendsExpr);
|
|
1705
|
-
return;
|
|
1706
|
-
}
|
|
1707
|
-
iface = sourceFile.addInterface({
|
|
1708
|
-
name: interfaceName,
|
|
1709
|
-
isExported: true,
|
|
1710
|
-
typeParameters,
|
|
1711
|
-
extends: [extendsExpr]
|
|
1712
|
-
});
|
|
1713
|
-
sourceFile.insertText(
|
|
1714
|
-
iface.getStart(),
|
|
1715
|
-
"// eslint-disable-next-line @typescript-eslint/no-empty-interface\n"
|
|
1716
|
-
);
|
|
1717
|
-
}
|
|
1718
|
-
function ensureFileEslintDisable(sourceFile, rule) {
|
|
1719
|
-
if (sourceFile.getFullText().includes(rule)) return;
|
|
1720
|
-
sourceFile.insertText(0, `/* eslint-disable ${rule} */
|
|
1721
|
-
`);
|
|
1722
|
-
}
|
|
1723
|
-
function addDecoratedMethod(cls, spec) {
|
|
1724
|
-
if (cls.getMethod(spec.name)) return false;
|
|
1725
|
-
cls.addMethod({
|
|
1726
|
-
name: spec.name,
|
|
1727
|
-
isAsync: spec.isAsync,
|
|
1728
|
-
returnType: spec.returnType,
|
|
1729
|
-
parameters: spec.parameters?.map((p) => {
|
|
1730
|
-
return { name: p.name, type: p.type };
|
|
1731
|
-
}),
|
|
1732
|
-
statements: spec.statements,
|
|
1733
|
-
decorators: [
|
|
1734
|
-
{
|
|
1735
|
-
name: spec.decoratorName,
|
|
1736
|
-
arguments: spec.decoratorArgsText ? [spec.decoratorArgsText] : []
|
|
1737
|
-
}
|
|
1738
|
-
]
|
|
1739
|
-
});
|
|
1740
|
-
return true;
|
|
1741
|
-
}
|
|
1742
|
-
function propertyInsertIndex(cls) {
|
|
1743
|
-
const props = cls.getProperties();
|
|
1744
|
-
if (props.length === 0) return 0;
|
|
1745
|
-
return props[props.length - 1].getChildIndex() + 1;
|
|
1746
|
-
}
|
|
1747
|
-
function addPlainProperty(cls, spec) {
|
|
1748
|
-
if (cls.getProperty(spec.name)) return false;
|
|
1749
|
-
cls.insertProperty(propertyInsertIndex(cls), {
|
|
1750
|
-
name: spec.name,
|
|
1751
|
-
type: spec.type,
|
|
1752
|
-
initializer: spec.initializer,
|
|
1753
|
-
isReadonly: !!spec.readonly,
|
|
1754
|
-
scope: spec.scope ? SCOPE_MAP[spec.scope] : void 0,
|
|
1755
|
-
hasExclamationToken: spec.initializer ? false : !!spec.hasExclamation
|
|
1756
|
-
});
|
|
1757
|
-
return true;
|
|
1758
|
-
}
|
|
1759
|
-
const SCOPE_MAP = {
|
|
1760
|
-
private: Scope.Private,
|
|
1761
|
-
protected: Scope.Protected,
|
|
1762
|
-
public: Scope.Public
|
|
1763
|
-
};
|
|
1764
|
-
function addDecoratedProperty(cls, spec) {
|
|
1765
|
-
if (cls.getProperty(spec.name)) return false;
|
|
1766
|
-
cls.insertProperty(propertyInsertIndex(cls), {
|
|
1767
|
-
name: spec.name,
|
|
1768
|
-
type: spec.type,
|
|
1769
|
-
initializer: spec.initializer,
|
|
1770
|
-
scope: spec.scope ? SCOPE_MAP[spec.scope] : void 0,
|
|
1771
|
-
hasExclamationToken: spec.initializer ? false : !!spec.hasExclamation,
|
|
1772
|
-
decorators: [
|
|
1773
|
-
spec.bare && !spec.decoratorArgsText ? { name: spec.decoratorName } : {
|
|
1774
|
-
name: spec.decoratorName,
|
|
1775
|
-
arguments: spec.decoratorArgsText ? [spec.decoratorArgsText] : []
|
|
1776
|
-
}
|
|
1777
|
-
]
|
|
1778
|
-
});
|
|
1779
|
-
return true;
|
|
1780
|
-
}
|
|
1781
|
-
function addGetter(cls, spec) {
|
|
1782
|
-
if (cls.getGetAccessor(spec.name)) return false;
|
|
1783
|
-
const ctor = cls.getConstructors()[0];
|
|
1784
|
-
const index = ctor ? ctor.getChildIndex() + 1 : propertyInsertIndex(cls);
|
|
1785
|
-
cls.insertGetAccessor(index, {
|
|
1786
|
-
name: spec.name,
|
|
1787
|
-
returnType: spec.returnType,
|
|
1788
|
-
statements: spec.statements ?? "// TODO: derive and return the computed value"
|
|
1789
|
-
});
|
|
1790
|
-
return true;
|
|
1791
|
-
}
|
|
1792
1722
|
function buildDecoratorArgs(options) {
|
|
1793
1723
|
const top = [];
|
|
1794
1724
|
if (options.containerProperty) {
|
|
@@ -3099,6 +3029,7 @@ export {
|
|
|
3099
3029
|
properCase,
|
|
3100
3030
|
readJson,
|
|
3101
3031
|
readNxJson,
|
|
3032
|
+
resolveModelFilePath,
|
|
3102
3033
|
setCodegenLogger,
|
|
3103
3034
|
updateJson,
|
|
3104
3035
|
updateModelIndex,
|