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