@dudousxd/nestjs-codegen 0.15.0 → 0.17.0
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/CHANGELOG.md +56 -0
- package/dist/cli/main.cjs +299 -147
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.js +239 -87
- package/dist/cli/main.js.map +1 -1
- package/dist/extension/index.cjs.map +1 -1
- package/dist/extension/index.d.cts +1 -1
- package/dist/extension/index.d.ts +1 -1
- package/dist/extension/index.js.map +1 -1
- package/dist/{index-CjIDPMsV.d.cts → index-DCF9QSPY.d.cts} +47 -0
- package/dist/{index-CjIDPMsV.d.ts → index-DCF9QSPY.d.ts} +47 -0
- package/dist/index.cjs +262 -110
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -3
- package/dist/index.d.ts +12 -3
- package/dist/index.js +233 -81
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +261 -110
- package/dist/nest/index.cjs.map +1 -1
- package/dist/nest/index.d.cts +1 -1
- package/dist/nest/index.d.ts +1 -1
- package/dist/nest/index.js +232 -81
- package/dist/nest/index.js.map +1 -1
- package/package.json +1 -1
package/dist/nest/index.js
CHANGED
|
@@ -141,7 +141,7 @@ function applyDefaults(userConfig, cwd) {
|
|
|
141
141
|
// src/generate-manifest.ts
|
|
142
142
|
import { createHash } from "crypto";
|
|
143
143
|
import { readFile as readFile4, readdir, writeFile as writeFile10 } from "fs/promises";
|
|
144
|
-
import { join as join16, relative as
|
|
144
|
+
import { join as join16, relative as relative7 } from "path";
|
|
145
145
|
import fg3 from "fast-glob";
|
|
146
146
|
|
|
147
147
|
// src/generate.ts
|
|
@@ -475,9 +475,10 @@ function inferExpressionType(node) {
|
|
|
475
475
|
|
|
476
476
|
// src/emit/emit-api.ts
|
|
477
477
|
import { mkdir, writeFile } from "fs/promises";
|
|
478
|
-
import { isAbsolute as isAbsolute2, join as join4, relative as
|
|
478
|
+
import { isAbsolute as isAbsolute2, join as join4, relative as relative4 } from "path";
|
|
479
479
|
|
|
480
480
|
// src/extension/registry.ts
|
|
481
|
+
import { relative as relative3, resolve as resolve2 } from "path";
|
|
481
482
|
import { Project as Project2 } from "ts-morph";
|
|
482
483
|
function resolveApiSlots(extensions) {
|
|
483
484
|
let layer;
|
|
@@ -510,7 +511,7 @@ function mergeExclusive(target, incoming, {
|
|
|
510
511
|
target.set(key, { value, owner });
|
|
511
512
|
}
|
|
512
513
|
}
|
|
513
|
-
function createExtensionContext(config, getRoutes) {
|
|
514
|
+
function createExtensionContext(config, getRoutes, trackedInputs) {
|
|
514
515
|
let project;
|
|
515
516
|
return {
|
|
516
517
|
cwd: config.codegen.cwd,
|
|
@@ -519,6 +520,13 @@ function createExtensionContext(config, getRoutes) {
|
|
|
519
520
|
get routes() {
|
|
520
521
|
return getRoutes();
|
|
521
522
|
},
|
|
523
|
+
trackInput(...paths) {
|
|
524
|
+
if (!trackedInputs) return;
|
|
525
|
+
for (const path of paths) {
|
|
526
|
+
if (!path) continue;
|
|
527
|
+
trackedInputs.add(relative3(config.codegen.cwd, resolve2(config.codegen.cwd, path)));
|
|
528
|
+
}
|
|
529
|
+
},
|
|
522
530
|
project() {
|
|
523
531
|
if (!project) {
|
|
524
532
|
project = new Project2({
|
|
@@ -686,7 +694,7 @@ function rawResponseType(c, outDir) {
|
|
|
686
694
|
return c.contractSource.response;
|
|
687
695
|
}
|
|
688
696
|
if (c.controllerRef) {
|
|
689
|
-
let relPath =
|
|
697
|
+
let relPath = relative4(outDir, c.controllerRef.filePath).replace(/\.ts$/, "");
|
|
690
698
|
if (!relPath.startsWith(".")) relPath = `./${relPath}`;
|
|
691
699
|
return `Awaited<ReturnType<import('${relPath}').${c.controllerRef.className}['${c.controllerRef.methodName}']>>`;
|
|
692
700
|
}
|
|
@@ -1012,6 +1020,11 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
1012
1020
|
config: {},
|
|
1013
1021
|
project: () => {
|
|
1014
1022
|
throw new Error("ExtensionContext.project() is unavailable in standalone emitApi.");
|
|
1023
|
+
},
|
|
1024
|
+
// No manifest is written on the standalone path, so there is nothing for
|
|
1025
|
+
// tracked dependencies to be recorded into — accept and discard them
|
|
1026
|
+
// rather than making every caller supply a sink.
|
|
1027
|
+
trackInput: () => {
|
|
1015
1028
|
}
|
|
1016
1029
|
};
|
|
1017
1030
|
const importsByFile = /* @__PURE__ */ new Map();
|
|
@@ -1068,7 +1081,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
1068
1081
|
for (const [filePath, names] of importsByFile) {
|
|
1069
1082
|
let relPath;
|
|
1070
1083
|
if (isAbsolute2(filePath)) {
|
|
1071
|
-
relPath =
|
|
1084
|
+
relPath = relative4(outDir, filePath).replace(/\.ts$/, "");
|
|
1072
1085
|
if (!relPath.startsWith(".")) relPath = `./${relPath}`;
|
|
1073
1086
|
} else {
|
|
1074
1087
|
relPath = filePath;
|
|
@@ -1182,7 +1195,7 @@ async function emitCache(pages, outDir) {
|
|
|
1182
1195
|
|
|
1183
1196
|
// src/emit/emit-forms.ts
|
|
1184
1197
|
import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
|
|
1185
|
-
import { join as join6, relative as
|
|
1198
|
+
import { join as join6, relative as relative5 } from "path";
|
|
1186
1199
|
async function emitForms(routes, outDir, config, adapter) {
|
|
1187
1200
|
if (config && config.enabled === false) return false;
|
|
1188
1201
|
const content = buildFormsFileWithAdapter(routes, outDir, adapter, config);
|
|
@@ -1201,7 +1214,7 @@ function deriveBaseName(routeName) {
|
|
|
1201
1214
|
return { method, full };
|
|
1202
1215
|
}
|
|
1203
1216
|
function relImport(outDir, filePath) {
|
|
1204
|
-
let relPath =
|
|
1217
|
+
let relPath = relative5(outDir, filePath).replace(/\.ts$/, "");
|
|
1205
1218
|
if (!relPath.startsWith(".")) relPath = `./${relPath}`;
|
|
1206
1219
|
return relPath;
|
|
1207
1220
|
}
|
|
@@ -1949,7 +1962,7 @@ async function emitOpenApi(routes, outDir, opts = {}) {
|
|
|
1949
1962
|
|
|
1950
1963
|
// src/emit/emit-pages.ts
|
|
1951
1964
|
import { mkdir as mkdir7, writeFile as writeFile7 } from "fs/promises";
|
|
1952
|
-
import { join as join10, relative as
|
|
1965
|
+
import { join as join10, relative as relative6 } from "path";
|
|
1953
1966
|
async function emitPages(pages, outDir, _options = {}) {
|
|
1954
1967
|
await mkdir7(outDir, { recursive: true });
|
|
1955
1968
|
const pageNameUnion = pages.length > 0 ? pages.map((p) => JSON.stringify(p.name)).join(" | ") : "never";
|
|
@@ -1989,7 +2002,7 @@ ${propsBody}
|
|
|
1989
2002
|
`;
|
|
1990
2003
|
}
|
|
1991
2004
|
function buildAugmentationType(page, outDir) {
|
|
1992
|
-
let importPath =
|
|
2005
|
+
let importPath = relative6(outDir, page.absolutePath).replace(/\.(tsx?|vue|svelte)$/, "");
|
|
1993
2006
|
if (!importPath.startsWith(".")) {
|
|
1994
2007
|
importPath = `./${importPath}`;
|
|
1995
2008
|
}
|
|
@@ -2133,8 +2146,8 @@ function driftGuardMessage(outDir, previousEntryPoint, currentEntryPoint, differ
|
|
|
2133
2146
|
}
|
|
2134
2147
|
async function generate(config, inputRoutes = [], entryPoint = "cli") {
|
|
2135
2148
|
setCodegenDebug(config.debug);
|
|
2136
|
-
const inputsHash = await computeInputsHash(config);
|
|
2137
2149
|
const manifest = await readManifest(config.codegen.outDir);
|
|
2150
|
+
const inputsHash = await computeInputsHash(config, manifest?.extraInputs ?? []);
|
|
2138
2151
|
if (await isManifestFresh(config.codegen.outDir, manifest, inputsHash)) {
|
|
2139
2152
|
console.log(`[nestjs-codegen] ${config.codegen.outDir} up to date, skipped`);
|
|
2140
2153
|
return;
|
|
@@ -2155,7 +2168,8 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
|
|
|
2155
2168
|
}
|
|
2156
2169
|
const extensions = config.extensions ?? [];
|
|
2157
2170
|
let routes = inputRoutes;
|
|
2158
|
-
const
|
|
2171
|
+
const trackedInputs = /* @__PURE__ */ new Set();
|
|
2172
|
+
const ctx = createExtensionContext(config, () => routes, trackedInputs);
|
|
2159
2173
|
if (extensions.length > 0) {
|
|
2160
2174
|
routes = await applyTransformRoutes(routes, extensions, ctx);
|
|
2161
2175
|
}
|
|
@@ -2215,13 +2229,16 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
|
|
|
2215
2229
|
}
|
|
2216
2230
|
}
|
|
2217
2231
|
const outputFiles = await listOutputFiles(config.codegen.outDir);
|
|
2232
|
+
const extraInputs = [...trackedInputs].sort();
|
|
2233
|
+
const recordedHash = extraInputs.length > 0 ? await computeInputsHash(config, extraInputs) : inputsHash;
|
|
2218
2234
|
await writeManifest(config.codegen.outDir, {
|
|
2219
2235
|
version: VERSION,
|
|
2220
|
-
hash:
|
|
2236
|
+
hash: recordedHash,
|
|
2221
2237
|
entryPoint,
|
|
2222
2238
|
configHash,
|
|
2223
2239
|
configKeyHashes,
|
|
2224
|
-
files: outputFiles
|
|
2240
|
+
files: outputFiles,
|
|
2241
|
+
...extraInputs.length > 0 ? { extraInputs } : {}
|
|
2225
2242
|
});
|
|
2226
2243
|
}
|
|
2227
2244
|
|
|
@@ -2231,11 +2248,11 @@ import { join as join15 } from "path";
|
|
|
2231
2248
|
import chokidar from "chokidar";
|
|
2232
2249
|
|
|
2233
2250
|
// src/discovery/contracts-fast.ts
|
|
2234
|
-
import { join as join13, resolve as
|
|
2251
|
+
import { join as join13, resolve as resolve4 } from "path";
|
|
2235
2252
|
import fg2 from "fast-glob";
|
|
2236
2253
|
import {
|
|
2237
|
-
Node as
|
|
2238
|
-
Project as
|
|
2254
|
+
Node as Node9,
|
|
2255
|
+
Project as Project4
|
|
2239
2256
|
} from "ts-morph";
|
|
2240
2257
|
|
|
2241
2258
|
// src/discovery/dto-type-resolver.ts
|
|
@@ -2251,7 +2268,7 @@ import {
|
|
|
2251
2268
|
|
|
2252
2269
|
// src/discovery/type-ref-resolution.ts
|
|
2253
2270
|
import { readFileSync } from "fs";
|
|
2254
|
-
import { dirname as dirname3, resolve as
|
|
2271
|
+
import { dirname as dirname3, resolve as resolve3 } from "path";
|
|
2255
2272
|
import {
|
|
2256
2273
|
Node as Node2
|
|
2257
2274
|
} from "ts-morph";
|
|
@@ -2308,9 +2325,9 @@ function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
|
|
|
2308
2325
|
const dir = dirname3(sourceFile.getFilePath());
|
|
2309
2326
|
const noExt = moduleSpecifier.replace(/\.(js|ts)$/, "");
|
|
2310
2327
|
return [
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2328
|
+
resolve3(dir, `${noExt}.ts`),
|
|
2329
|
+
resolve3(dir, `${moduleSpecifier}.ts`),
|
|
2330
|
+
resolve3(dir, moduleSpecifier, "index.ts")
|
|
2314
2331
|
];
|
|
2315
2332
|
}
|
|
2316
2333
|
const ctx = _ctxFor(project);
|
|
@@ -2331,8 +2348,8 @@ function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
|
|
|
2331
2348
|
const rest = moduleSpecifier.slice(prefix.length);
|
|
2332
2349
|
const candidates = [];
|
|
2333
2350
|
for (const mapping of mappings) {
|
|
2334
|
-
const resolved =
|
|
2335
|
-
candidates.push(`${resolved}.ts`,
|
|
2351
|
+
const resolved = resolve3(baseUrl, mapping.replace("*", rest));
|
|
2352
|
+
candidates.push(`${resolved}.ts`, resolve3(resolved, "index.ts"));
|
|
2336
2353
|
}
|
|
2337
2354
|
dbg(" resolved candidates:", candidates);
|
|
2338
2355
|
return candidates;
|
|
@@ -3227,6 +3244,12 @@ function toFilterFieldType(name, r) {
|
|
|
3227
3244
|
}
|
|
3228
3245
|
|
|
3229
3246
|
// src/discovery/filter-for.ts
|
|
3247
|
+
function resolveMixinEntityClass(mixin, project) {
|
|
3248
|
+
const entityArg = mixin?.classArgs[0];
|
|
3249
|
+
if (!entityArg) return void 0;
|
|
3250
|
+
const file = project.getSourceFile(entityArg.filePath) ?? project.addSourceFileAtPathIfExists(entityArg.filePath);
|
|
3251
|
+
return file?.getClass(entityArg.name);
|
|
3252
|
+
}
|
|
3230
3253
|
function classifyFilterForHint(typeInit) {
|
|
3231
3254
|
if (Node5.isStringLiteral(typeInit)) {
|
|
3232
3255
|
switch (typeInit.getLiteralValue()) {
|
|
@@ -3300,7 +3323,9 @@ function extractFilterForHints(classDecl, project) {
|
|
|
3300
3323
|
}
|
|
3301
3324
|
return hints;
|
|
3302
3325
|
}
|
|
3303
|
-
function extractApplyFilterInfo(method, sourceFile, project) {
|
|
3326
|
+
function extractApplyFilterInfo(method, sourceFile, project, mixin) {
|
|
3327
|
+
const declFile = method.getSourceFile();
|
|
3328
|
+
const mixinEntity = resolveMixinEntityClass(mixin, project);
|
|
3304
3329
|
for (const param of method.getParameters()) {
|
|
3305
3330
|
const filterDecorator = param.getDecorators().find((d) => d.getName() === "ApplyFilter");
|
|
3306
3331
|
if (!filterDecorator) continue;
|
|
@@ -3320,12 +3345,12 @@ function extractApplyFilterInfo(method, sourceFile, project) {
|
|
|
3320
3345
|
}
|
|
3321
3346
|
}
|
|
3322
3347
|
const filterClassName = filterClassArg.getText();
|
|
3323
|
-
const resolved = findType(filterClassName,
|
|
3324
|
-
|
|
3325
|
-
|
|
3348
|
+
const resolved = findType(filterClassName, declFile, project);
|
|
3349
|
+
const classDecl = resolved?.kind === "class" ? resolved.decl : resolveLocalClassDeclaration(filterClassArg);
|
|
3350
|
+
if (classDecl) {
|
|
3326
3351
|
let fieldTypes = extractClassPropertyTypes(classDecl, project);
|
|
3327
3352
|
if (fieldTypes.length === 0) {
|
|
3328
|
-
fieldTypes = extractFilterableEntityFields(classDecl, project);
|
|
3353
|
+
fieldTypes = extractFilterableEntityFields(classDecl, project, mixinEntity);
|
|
3329
3354
|
}
|
|
3330
3355
|
const filterForHints = extractFilterForHints(classDecl, project);
|
|
3331
3356
|
if (filterForHints.size > 0) {
|
|
@@ -3410,22 +3435,29 @@ function extractClassPropertyTypes(classDecl, project) {
|
|
|
3410
3435
|
}
|
|
3411
3436
|
return fields;
|
|
3412
3437
|
}
|
|
3413
|
-
function
|
|
3438
|
+
function resolveLocalClassDeclaration(identifier) {
|
|
3439
|
+
if (!Node5.isIdentifier(identifier)) return void 0;
|
|
3440
|
+
return identifier.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && Node5.isClassDeclaration(n));
|
|
3441
|
+
}
|
|
3442
|
+
function resolveDeclaredEntity(optionsArg, filterClass, project) {
|
|
3443
|
+
if (!Node5.isObjectLiteralExpression(optionsArg)) return void 0;
|
|
3444
|
+
const entityProp = optionsArg.getProperty("entity");
|
|
3445
|
+
if (!entityProp || !Node5.isPropertyAssignment(entityProp)) return void 0;
|
|
3446
|
+
const entityInit = entityProp.getInitializer();
|
|
3447
|
+
if (!entityInit || !Node5.isIdentifier(entityInit)) return void 0;
|
|
3448
|
+
const resolved = findType(entityInit.getText(), filterClass.getSourceFile(), project);
|
|
3449
|
+
if (!resolved || resolved.kind !== "class") return void 0;
|
|
3450
|
+
return resolved.decl;
|
|
3451
|
+
}
|
|
3452
|
+
function extractFilterableEntityFields(filterClass, project, entityOverride) {
|
|
3414
3453
|
const filterableDecorator = filterClass.getDecorators().find((d) => d.getName() === "Filterable");
|
|
3415
3454
|
if (!filterableDecorator) return [];
|
|
3416
3455
|
const args = filterableDecorator.getArguments();
|
|
3417
3456
|
if (args.length === 0) return [];
|
|
3418
3457
|
const optionsArg = args[0];
|
|
3419
3458
|
if (!Node5.isObjectLiteralExpression(optionsArg)) return [];
|
|
3420
|
-
const
|
|
3421
|
-
if (!
|
|
3422
|
-
const entityInit = entityProp.getInitializer();
|
|
3423
|
-
if (!entityInit || !Node5.isIdentifier(entityInit)) return [];
|
|
3424
|
-
const entityName = entityInit.getText();
|
|
3425
|
-
const filterSourceFile = filterClass.getSourceFile();
|
|
3426
|
-
const resolvedEntity = findType(entityName, filterSourceFile, project);
|
|
3427
|
-
if (!resolvedEntity || resolvedEntity.kind !== "class") return [];
|
|
3428
|
-
const entityDecl = resolvedEntity.decl;
|
|
3459
|
+
const entityDecl = entityOverride ?? resolveDeclaredEntity(optionsArg, filterClass, project);
|
|
3460
|
+
if (!entityDecl) return [];
|
|
3429
3461
|
const fields = collectEntityFields(
|
|
3430
3462
|
entityDecl,
|
|
3431
3463
|
entityDecl.getSourceFile(),
|
|
@@ -3850,9 +3882,9 @@ function unwrapNamedContainer(node, names) {
|
|
|
3850
3882
|
}
|
|
3851
3883
|
return node;
|
|
3852
3884
|
}
|
|
3853
|
-
function extractDtoContract(method, sourceFile, project) {
|
|
3885
|
+
function extractDtoContract(method, sourceFile, project, mixin) {
|
|
3854
3886
|
let body = extractBodyType(method, sourceFile, project);
|
|
3855
|
-
const filterInfo = extractApplyFilterInfo(method, sourceFile, project);
|
|
3887
|
+
const filterInfo = extractApplyFilterInfo(method, sourceFile, project, mixin);
|
|
3856
3888
|
const query = extractQueryType(method, sourceFile, project);
|
|
3857
3889
|
const uploads = extractUploadedFiles(method);
|
|
3858
3890
|
const multipartBody = uploads.fields ? `{ ${uploads.fields} }` : null;
|
|
@@ -3962,12 +3994,94 @@ function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
|
3962
3994
|
return null;
|
|
3963
3995
|
}
|
|
3964
3996
|
|
|
3997
|
+
// src/discovery/heritage.ts
|
|
3998
|
+
import { Node as Node7, Project as Project3 } from "ts-morph";
|
|
3999
|
+
function unwrapExpression(node) {
|
|
4000
|
+
let current = node;
|
|
4001
|
+
while (Node7.isAsExpression(current) || Node7.isSatisfiesExpression(current) || Node7.isParenthesizedExpression(current) || Node7.isTypeAssertion(current)) {
|
|
4002
|
+
current = current.getExpression();
|
|
4003
|
+
}
|
|
4004
|
+
return current;
|
|
4005
|
+
}
|
|
4006
|
+
function resolveReturnedClass(factoryDecl) {
|
|
4007
|
+
const body = factoryDecl.getBody();
|
|
4008
|
+
if (!body) return void 0;
|
|
4009
|
+
const returns = body.getDescendants().filter((n) => Node7.isReturnStatement(n));
|
|
4010
|
+
for (const ret of returns) {
|
|
4011
|
+
const expr = ret.getExpression();
|
|
4012
|
+
if (!expr) continue;
|
|
4013
|
+
const inner = unwrapExpression(expr);
|
|
4014
|
+
if (Node7.isClassExpression(inner)) {
|
|
4015
|
+
return inner;
|
|
4016
|
+
}
|
|
4017
|
+
if (Node7.isIdentifier(inner)) {
|
|
4018
|
+
const name = inner.getText();
|
|
4019
|
+
const decl = body.getDescendants().find((n) => Node7.isClassDeclaration(n) && n.getName() === name);
|
|
4020
|
+
if (decl) return decl;
|
|
4021
|
+
}
|
|
4022
|
+
}
|
|
4023
|
+
return void 0;
|
|
4024
|
+
}
|
|
4025
|
+
function resolveInheritedMethods(cls) {
|
|
4026
|
+
const expr = cls.getExtends()?.getExpression();
|
|
4027
|
+
if (!expr || !Node7.isCallExpression(expr)) return void 0;
|
|
4028
|
+
const callee = expr.getExpression();
|
|
4029
|
+
if (!Node7.isIdentifier(callee)) return void 0;
|
|
4030
|
+
const factoryDecl = callee.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => Node7.isFunctionDeclaration(n));
|
|
4031
|
+
if (!factoryDecl) return void 0;
|
|
4032
|
+
const returnedClass = resolveReturnedClass(factoryDecl);
|
|
4033
|
+
if (!returnedClass) return void 0;
|
|
4034
|
+
const classArgs = [];
|
|
4035
|
+
for (const arg of expr.getArguments()) {
|
|
4036
|
+
if (!Node7.isIdentifier(arg)) continue;
|
|
4037
|
+
const decl = arg.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => Node7.isClassDeclaration(n));
|
|
4038
|
+
if (decl) {
|
|
4039
|
+
classArgs.push({
|
|
4040
|
+
name: decl.getName() ?? arg.getText(),
|
|
4041
|
+
filePath: decl.getSourceFile().getFilePath()
|
|
4042
|
+
});
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
return {
|
|
4046
|
+
methods: returnedClass.getMethods(),
|
|
4047
|
+
factoryName: callee.getText(),
|
|
4048
|
+
factoryFilePath: factoryDecl.getSourceFile().getFilePath(),
|
|
4049
|
+
classArgs
|
|
4050
|
+
};
|
|
4051
|
+
}
|
|
4052
|
+
var mixinTypeProject;
|
|
4053
|
+
function getMixinTypeProject() {
|
|
4054
|
+
mixinTypeProject ??= new Project3({
|
|
4055
|
+
skipAddingFilesFromTsConfig: true,
|
|
4056
|
+
compilerOptions: { strict: true }
|
|
4057
|
+
});
|
|
4058
|
+
return mixinTypeProject;
|
|
4059
|
+
}
|
|
4060
|
+
function clearMixinTypeProject() {
|
|
4061
|
+
mixinTypeProject = void 0;
|
|
4062
|
+
}
|
|
4063
|
+
function resolveInstantiatedReturnType(cls, methodName) {
|
|
4064
|
+
const filePath = cls.getSourceFile().getFilePath();
|
|
4065
|
+
const className = cls.getName();
|
|
4066
|
+
if (!className) return void 0;
|
|
4067
|
+
const project = getMixinTypeProject();
|
|
4068
|
+
const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPathIfExists(filePath);
|
|
4069
|
+
const typedCls = sourceFile?.getClass(className);
|
|
4070
|
+
if (!typedCls) return void 0;
|
|
4071
|
+
const prop = typedCls.getType().getProperty(methodName);
|
|
4072
|
+
if (!prop) return void 0;
|
|
4073
|
+
const returnType = prop.getTypeAtLocation(typedCls).getCallSignatures()[0]?.getReturnType();
|
|
4074
|
+
if (!returnType) return void 0;
|
|
4075
|
+
const unwrapped = returnType.getSymbol()?.getName() === "Promise" ? returnType.getTypeArguments()[0] ?? returnType : returnType;
|
|
4076
|
+
return unwrapped.getText(typedCls);
|
|
4077
|
+
}
|
|
4078
|
+
|
|
3965
4079
|
// src/discovery/zod-ast-to-ts.ts
|
|
3966
|
-
import { Node as
|
|
4080
|
+
import { Node as Node8, SyntaxKind as SyntaxKind4 } from "ts-morph";
|
|
3967
4081
|
function zodAstToTs(node) {
|
|
3968
|
-
if (!
|
|
4082
|
+
if (!Node8.isCallExpression(node)) return "unknown";
|
|
3969
4083
|
const expr = node.getExpression();
|
|
3970
|
-
if (
|
|
4084
|
+
if (Node8.isPropertyAccessExpression(expr)) {
|
|
3971
4085
|
const methodName = expr.getName();
|
|
3972
4086
|
const receiver = expr.getExpression();
|
|
3973
4087
|
if (methodName === "optional") {
|
|
@@ -3991,17 +4105,17 @@ function zodAstToTs(node) {
|
|
|
3991
4105
|
case "literal": {
|
|
3992
4106
|
const lit = args[0];
|
|
3993
4107
|
if (!lit) return "unknown";
|
|
3994
|
-
if (
|
|
3995
|
-
if (
|
|
4108
|
+
if (Node8.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
|
|
4109
|
+
if (Node8.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
|
|
3996
4110
|
if (lit.getKind() === SyntaxKind4.TrueKeyword) return "true";
|
|
3997
4111
|
if (lit.getKind() === SyntaxKind4.FalseKeyword) return "false";
|
|
3998
4112
|
return "unknown";
|
|
3999
4113
|
}
|
|
4000
4114
|
case "enum": {
|
|
4001
4115
|
const arrArg = args[0];
|
|
4002
|
-
if (!arrArg || !
|
|
4116
|
+
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
4003
4117
|
const members = arrArg.getElements().map(
|
|
4004
|
-
(el) =>
|
|
4118
|
+
(el) => Node8.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
|
|
4005
4119
|
);
|
|
4006
4120
|
return members.join(" | ");
|
|
4007
4121
|
}
|
|
@@ -4012,10 +4126,10 @@ function zodAstToTs(node) {
|
|
|
4012
4126
|
}
|
|
4013
4127
|
case "object": {
|
|
4014
4128
|
const objArg = args[0];
|
|
4015
|
-
if (!objArg || !
|
|
4129
|
+
if (!objArg || !Node8.isObjectLiteralExpression(objArg)) return "unknown";
|
|
4016
4130
|
const lines = [];
|
|
4017
4131
|
for (const prop of objArg.getProperties()) {
|
|
4018
|
-
if (!
|
|
4132
|
+
if (!Node8.isPropertyAssignment(prop)) continue;
|
|
4019
4133
|
const key = prop.getName();
|
|
4020
4134
|
const valNode = prop.getInitializer();
|
|
4021
4135
|
if (!valNode) continue;
|
|
@@ -4027,7 +4141,7 @@ function zodAstToTs(node) {
|
|
|
4027
4141
|
}
|
|
4028
4142
|
case "union": {
|
|
4029
4143
|
const arrArg = args[0];
|
|
4030
|
-
if (!arrArg || !
|
|
4144
|
+
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
4031
4145
|
return arrArg.getElements().map(zodAstToTs).join(" | ");
|
|
4032
4146
|
}
|
|
4033
4147
|
case "record": {
|
|
@@ -4037,7 +4151,7 @@ function zodAstToTs(node) {
|
|
|
4037
4151
|
}
|
|
4038
4152
|
case "tuple": {
|
|
4039
4153
|
const arrArg = args[0];
|
|
4040
|
-
if (!arrArg || !
|
|
4154
|
+
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
4041
4155
|
return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
|
|
4042
4156
|
}
|
|
4043
4157
|
default:
|
|
@@ -4047,18 +4161,18 @@ function zodAstToTs(node) {
|
|
|
4047
4161
|
return "unknown";
|
|
4048
4162
|
}
|
|
4049
4163
|
function isOptionalChain(node) {
|
|
4050
|
-
if (!
|
|
4164
|
+
if (!Node8.isCallExpression(node)) return false;
|
|
4051
4165
|
const expr = node.getExpression();
|
|
4052
|
-
return
|
|
4166
|
+
return Node8.isPropertyAccessExpression(expr) && expr.getName() === "optional";
|
|
4053
4167
|
}
|
|
4054
4168
|
function parseDefineContractCall(callExpr) {
|
|
4055
|
-
if (!
|
|
4169
|
+
if (!Node8.isCallExpression(callExpr)) return null;
|
|
4056
4170
|
const callee = callExpr.getExpression();
|
|
4057
|
-
const calleeName =
|
|
4171
|
+
const calleeName = Node8.isIdentifier(callee) ? callee.getText() : Node8.isPropertyAccessExpression(callee) ? callee.getName() : "";
|
|
4058
4172
|
if (calleeName !== "defineContract") return null;
|
|
4059
4173
|
const args = callExpr.getArguments();
|
|
4060
4174
|
const optsArg = args[0];
|
|
4061
|
-
if (!optsArg || !
|
|
4175
|
+
if (!optsArg || !Node8.isObjectLiteralExpression(optsArg)) return null;
|
|
4062
4176
|
let query = null;
|
|
4063
4177
|
let body = null;
|
|
4064
4178
|
let response = "unknown";
|
|
@@ -4066,7 +4180,7 @@ function parseDefineContractCall(callExpr) {
|
|
|
4066
4180
|
let bodyZodText = null;
|
|
4067
4181
|
let queryZodText = null;
|
|
4068
4182
|
for (const prop of optsArg.getProperties()) {
|
|
4069
|
-
if (!
|
|
4183
|
+
if (!Node8.isPropertyAssignment(prop)) continue;
|
|
4070
4184
|
const propName = prop.getName();
|
|
4071
4185
|
const val = prop.getInitializer();
|
|
4072
4186
|
if (!val) continue;
|
|
@@ -4087,18 +4201,18 @@ function parseDefineContractCall(callExpr) {
|
|
|
4087
4201
|
|
|
4088
4202
|
// src/discovery/contracts-fast.ts
|
|
4089
4203
|
function resolveTsconfigPath(cwd, tsconfig) {
|
|
4090
|
-
return tsconfig ?
|
|
4204
|
+
return tsconfig ? resolve4(tsconfig) : join13(cwd, "tsconfig.json");
|
|
4091
4205
|
}
|
|
4092
4206
|
function createDiscoveryProject(tsconfigPath) {
|
|
4093
4207
|
try {
|
|
4094
|
-
return new
|
|
4208
|
+
return new Project4({
|
|
4095
4209
|
tsConfigFilePath: tsconfigPath,
|
|
4096
4210
|
skipAddingFilesFromTsConfig: true,
|
|
4097
4211
|
skipLoadingLibFiles: true,
|
|
4098
4212
|
skipFileDependencyResolution: true
|
|
4099
4213
|
});
|
|
4100
4214
|
} catch {
|
|
4101
|
-
return new
|
|
4215
|
+
return new Project4({
|
|
4102
4216
|
skipAddingFilesFromTsConfig: true,
|
|
4103
4217
|
skipLoadingLibFiles: true,
|
|
4104
4218
|
skipFileDependencyResolution: true,
|
|
@@ -4166,7 +4280,7 @@ var PersistentDiscovery = class _PersistentDiscovery {
|
|
|
4166
4280
|
async rediscover(changedPaths) {
|
|
4167
4281
|
if (changedPaths) {
|
|
4168
4282
|
for (const p of changedPaths) {
|
|
4169
|
-
const abs =
|
|
4283
|
+
const abs = resolve4(p);
|
|
4170
4284
|
const sf = this.project.getSourceFile(abs);
|
|
4171
4285
|
if (sf) {
|
|
4172
4286
|
await sf.refreshFromFileSystem();
|
|
@@ -4198,15 +4312,16 @@ var PersistentDiscovery = class _PersistentDiscovery {
|
|
|
4198
4312
|
runExtraction() {
|
|
4199
4313
|
clearTypeResolutionCaches(this.project);
|
|
4200
4314
|
clearEnumCache(this.project);
|
|
4315
|
+
clearMixinTypeProject();
|
|
4201
4316
|
return extractRoutesFrom(this.project, this.controllerPaths);
|
|
4202
4317
|
}
|
|
4203
4318
|
};
|
|
4204
4319
|
function decoratorStringArg(decoratorExpr) {
|
|
4205
4320
|
if (!decoratorExpr) return void 0;
|
|
4206
|
-
if (
|
|
4207
|
-
if (
|
|
4321
|
+
if (Node9.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
|
|
4322
|
+
if (Node9.isArrayLiteralExpression(decoratorExpr)) {
|
|
4208
4323
|
const first = decoratorExpr.getElements()[0];
|
|
4209
|
-
if (first &&
|
|
4324
|
+
if (first && Node9.isStringLiteral(first)) return first.getLiteralValue();
|
|
4210
4325
|
}
|
|
4211
4326
|
return void 0;
|
|
4212
4327
|
}
|
|
@@ -4228,7 +4343,8 @@ function joinPaths(prefix, suffix) {
|
|
|
4228
4343
|
if (!prefix && !suffix) return "/";
|
|
4229
4344
|
if (!prefix) return suffix.startsWith("/") ? suffix : `/${suffix}`;
|
|
4230
4345
|
if (!suffix) return prefix.startsWith("/") ? prefix : `/${prefix}`;
|
|
4231
|
-
const
|
|
4346
|
+
const withLeadingSlash = prefix.startsWith("/") ? prefix : `/${prefix}`;
|
|
4347
|
+
const p = withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
|
|
4232
4348
|
const s = suffix.startsWith("/") ? suffix : `/${suffix}`;
|
|
4233
4349
|
const combined = p + s;
|
|
4234
4350
|
return combined === "" ? "/" : combined;
|
|
@@ -4282,7 +4398,8 @@ function buildRoute(args) {
|
|
|
4282
4398
|
methodAs,
|
|
4283
4399
|
sourceFile,
|
|
4284
4400
|
seenNames,
|
|
4285
|
-
contractSource
|
|
4401
|
+
contractSource,
|
|
4402
|
+
mixin
|
|
4286
4403
|
} = args;
|
|
4287
4404
|
const routeName = resolveRouteName(className, methodName, classAs, methodAs);
|
|
4288
4405
|
const qualifiedRef = `${className}.${methodName}`;
|
|
@@ -4298,7 +4415,12 @@ function buildRoute(args) {
|
|
|
4298
4415
|
path: combinedPath,
|
|
4299
4416
|
name: routeName,
|
|
4300
4417
|
params: extractParams(combinedPath),
|
|
4301
|
-
controllerRef: {
|
|
4418
|
+
controllerRef: {
|
|
4419
|
+
className,
|
|
4420
|
+
methodName,
|
|
4421
|
+
filePath: sourceFile.getFilePath(),
|
|
4422
|
+
...mixin ? { mixin } : {}
|
|
4423
|
+
},
|
|
4302
4424
|
contract: { contractSource }
|
|
4303
4425
|
};
|
|
4304
4426
|
}
|
|
@@ -4312,16 +4434,17 @@ function extractContractRoute(args) {
|
|
|
4312
4434
|
className,
|
|
4313
4435
|
sourceFile,
|
|
4314
4436
|
project,
|
|
4315
|
-
seenNames
|
|
4437
|
+
seenNames,
|
|
4438
|
+
mixin
|
|
4316
4439
|
} = args;
|
|
4317
4440
|
const firstDecoratorArg = applyContractDecorator.getArguments()[0];
|
|
4318
4441
|
if (!firstDecoratorArg) return null;
|
|
4319
4442
|
let contractDef = null;
|
|
4320
4443
|
let bodyZodRef = null;
|
|
4321
4444
|
let queryZodRef = null;
|
|
4322
|
-
if (
|
|
4445
|
+
if (Node9.isCallExpression(firstDecoratorArg)) {
|
|
4323
4446
|
contractDef = parseDefineContractCall(firstDecoratorArg);
|
|
4324
|
-
} else if (
|
|
4447
|
+
} else if (Node9.isIdentifier(firstDecoratorArg)) {
|
|
4325
4448
|
const identName = firstDecoratorArg.getText();
|
|
4326
4449
|
const resolvedVar = resolveImportedVariable(identName, sourceFile, project);
|
|
4327
4450
|
if (!resolvedVar) {
|
|
@@ -4364,6 +4487,7 @@ function extractContractRoute(args) {
|
|
|
4364
4487
|
methodAs,
|
|
4365
4488
|
sourceFile,
|
|
4366
4489
|
seenNames,
|
|
4490
|
+
mixin,
|
|
4367
4491
|
contractSource: {
|
|
4368
4492
|
query: contractDef.query,
|
|
4369
4493
|
body: contractDef.body,
|
|
@@ -4380,13 +4504,14 @@ function extractContractRoute(args) {
|
|
|
4380
4504
|
});
|
|
4381
4505
|
}
|
|
4382
4506
|
function extractDtoRoute(args) {
|
|
4383
|
-
const { cls, method, verb, prefix, className, sourceFile, project, seenNames } = args;
|
|
4507
|
+
const { cls, method, verb, prefix, className, sourceFile, project, seenNames, mixin } = args;
|
|
4384
4508
|
if (!verb) return null;
|
|
4385
4509
|
const combined = joinPaths(prefix, verb.handlerPath);
|
|
4386
4510
|
const methodName = method.getName();
|
|
4387
4511
|
const classAs = readAsDecorator(cls, `class ${className}`);
|
|
4388
4512
|
const methodAs = readAsDecorator(method, `${className}.${methodName}`);
|
|
4389
|
-
const dtoContract = extractDtoContract(method, sourceFile, project);
|
|
4513
|
+
const dtoContract = extractDtoContract(method, sourceFile, project, mixin);
|
|
4514
|
+
const mixinResponse = mixin ? resolveInstantiatedReturnType(cls, methodName) : void 0;
|
|
4390
4515
|
return buildRoute({
|
|
4391
4516
|
className,
|
|
4392
4517
|
methodName,
|
|
@@ -4396,10 +4521,11 @@ function extractDtoRoute(args) {
|
|
|
4396
4521
|
methodAs,
|
|
4397
4522
|
sourceFile,
|
|
4398
4523
|
seenNames,
|
|
4524
|
+
mixin,
|
|
4399
4525
|
contractSource: {
|
|
4400
4526
|
query: dtoContract?.query ?? null,
|
|
4401
4527
|
body: dtoContract?.body ?? null,
|
|
4402
|
-
response: dtoContract?.response ?? "unknown",
|
|
4528
|
+
response: mixinResponse ?? dtoContract?.response ?? "unknown",
|
|
4403
4529
|
error: dtoContract?.error ?? null,
|
|
4404
4530
|
queryRef: dtoContract?.queryRef ?? null,
|
|
4405
4531
|
bodyRef: dtoContract?.bodyRef ?? null,
|
|
@@ -4428,7 +4554,17 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
4428
4554
|
const firstArg2 = controllerDecorator.getArguments()[0];
|
|
4429
4555
|
const prefix = decoratorStringArg(firstArg2) ?? "";
|
|
4430
4556
|
const className = cls.getName() ?? "Unknown";
|
|
4431
|
-
|
|
4557
|
+
const inherited = resolveInheritedMethods(cls);
|
|
4558
|
+
const mixinBinding = inherited ? {
|
|
4559
|
+
factoryName: inherited.factoryName,
|
|
4560
|
+
factoryFilePath: inherited.factoryFilePath,
|
|
4561
|
+
classArgs: inherited.classArgs
|
|
4562
|
+
} : void 0;
|
|
4563
|
+
const methods = [
|
|
4564
|
+
...cls.getMethods().map((method) => ({ method })),
|
|
4565
|
+
...(inherited?.methods ?? []).map((method) => ({ method, mixin: mixinBinding }))
|
|
4566
|
+
];
|
|
4567
|
+
for (const { method, mixin } of methods) {
|
|
4432
4568
|
const verb = resolveVerb(method);
|
|
4433
4569
|
const applyContractDecorator = method.getDecorator("ApplyContract");
|
|
4434
4570
|
const route = applyContractDecorator ? extractContractRoute({
|
|
@@ -4440,7 +4576,8 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
4440
4576
|
className,
|
|
4441
4577
|
sourceFile,
|
|
4442
4578
|
project,
|
|
4443
|
-
seenNames
|
|
4579
|
+
seenNames,
|
|
4580
|
+
mixin
|
|
4444
4581
|
}) : extractDtoRoute({
|
|
4445
4582
|
cls,
|
|
4446
4583
|
method,
|
|
@@ -4449,7 +4586,8 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
4449
4586
|
className,
|
|
4450
4587
|
sourceFile,
|
|
4451
4588
|
project,
|
|
4452
|
-
seenNames
|
|
4589
|
+
seenNames,
|
|
4590
|
+
mixin
|
|
4453
4591
|
});
|
|
4454
4592
|
if (route) routes.push(route);
|
|
4455
4593
|
}
|
|
@@ -4650,7 +4788,7 @@ async function watch(config, onChange, options = {}) {
|
|
|
4650
4788
|
}
|
|
4651
4789
|
|
|
4652
4790
|
// src/index.ts
|
|
4653
|
-
var VERSION = "0.
|
|
4791
|
+
var VERSION = "0.17.0";
|
|
4654
4792
|
|
|
4655
4793
|
// src/generate-manifest.ts
|
|
4656
4794
|
var MANIFEST_FILE = ".codegen-manifest.json";
|
|
@@ -4674,6 +4812,9 @@ function isManifestShape(value) {
|
|
|
4674
4812
|
if (candidate.configKeyHashes !== void 0 && !isStringRecord(candidate.configKeyHashes)) {
|
|
4675
4813
|
return false;
|
|
4676
4814
|
}
|
|
4815
|
+
if (candidate.extraInputs !== void 0 && (!Array.isArray(candidate.extraInputs) || !candidate.extraInputs.every((entry) => typeof entry === "string"))) {
|
|
4816
|
+
return false;
|
|
4817
|
+
}
|
|
4677
4818
|
if (!Array.isArray(candidate.files)) return false;
|
|
4678
4819
|
return candidate.files.every((entry) => typeof entry === "string");
|
|
4679
4820
|
}
|
|
@@ -4713,21 +4854,30 @@ async function discoverInputFiles(config) {
|
|
|
4713
4854
|
const matched = await fg3(globs, { cwd, absolute: true, onlyFiles: true });
|
|
4714
4855
|
return [...new Set(matched)].sort();
|
|
4715
4856
|
}
|
|
4716
|
-
async function computeInputsHash(config) {
|
|
4857
|
+
async function computeInputsHash(config, extraInputs = []) {
|
|
4717
4858
|
const hash = createHash("sha256");
|
|
4718
4859
|
hash.update(`version:${VERSION}
|
|
4719
4860
|
`);
|
|
4720
4861
|
hash.update(`config:${serializeConfig(config)}
|
|
4721
4862
|
`);
|
|
4722
|
-
const inputFiles = await discoverInputFiles(config);
|
|
4723
4863
|
const cwd = config.codegen.cwd;
|
|
4724
|
-
|
|
4864
|
+
const globbed = await discoverInputFiles(config);
|
|
4865
|
+
const globbedRelative = new Set(globbed.map((file) => relative7(cwd, file)));
|
|
4866
|
+
const extra = [...new Set(extraInputs)].filter((file) => !globbedRelative.has(file)).sort();
|
|
4867
|
+
for (const file of globbed) {
|
|
4725
4868
|
const contents = await readFile4(file, "utf8");
|
|
4726
|
-
hash.update(`file:${
|
|
4869
|
+
hash.update(`file:${relative7(cwd, file)}
|
|
4727
4870
|
`);
|
|
4728
4871
|
hash.update(contents);
|
|
4729
4872
|
hash.update("\n");
|
|
4730
4873
|
}
|
|
4874
|
+
for (const file of extra) {
|
|
4875
|
+
const contents = await readFile4(join16(cwd, file), "utf8").catch(() => null);
|
|
4876
|
+
hash.update(`extra:${file}
|
|
4877
|
+
`);
|
|
4878
|
+
hash.update(contents ?? "\0missing");
|
|
4879
|
+
hash.update("\n");
|
|
4880
|
+
}
|
|
4731
4881
|
return hash.digest("hex");
|
|
4732
4882
|
}
|
|
4733
4883
|
async function readManifest(outDir) {
|
|
@@ -4741,6 +4891,7 @@ async function readManifest(outDir) {
|
|
|
4741
4891
|
...parsed.entryPoint ? { entryPoint: parsed.entryPoint } : {},
|
|
4742
4892
|
...parsed.configHash ? { configHash: parsed.configHash } : {},
|
|
4743
4893
|
...parsed.configKeyHashes ? { configKeyHashes: parsed.configKeyHashes } : {},
|
|
4894
|
+
...parsed.extraInputs ? { extraInputs: parsed.extraInputs } : {},
|
|
4744
4895
|
files: parsed.files
|
|
4745
4896
|
};
|
|
4746
4897
|
} catch {
|
|
@@ -4763,7 +4914,7 @@ async function listOutputFiles(outDir) {
|
|
|
4763
4914
|
if (entry.isDirectory()) {
|
|
4764
4915
|
await walk(abs);
|
|
4765
4916
|
} else if (entry.isFile()) {
|
|
4766
|
-
const rel =
|
|
4917
|
+
const rel = relative7(outDir, abs);
|
|
4767
4918
|
if (rel === MANIFEST_FILE || rel === LOCK_FILE2) continue;
|
|
4768
4919
|
found.push(rel);
|
|
4769
4920
|
}
|