@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/index.js
CHANGED
|
@@ -519,9 +519,10 @@ function inferExpressionType(node) {
|
|
|
519
519
|
|
|
520
520
|
// src/emit/emit-api.ts
|
|
521
521
|
import { mkdir, writeFile } from "fs/promises";
|
|
522
|
-
import { isAbsolute as isAbsolute2, join as join4, relative as
|
|
522
|
+
import { isAbsolute as isAbsolute2, join as join4, relative as relative4 } from "path";
|
|
523
523
|
|
|
524
524
|
// src/extension/registry.ts
|
|
525
|
+
import { relative as relative3, resolve as resolve2 } from "path";
|
|
525
526
|
import { Project as Project2 } from "ts-morph";
|
|
526
527
|
function resolveApiSlots(extensions) {
|
|
527
528
|
let layer;
|
|
@@ -554,7 +555,7 @@ function mergeExclusive(target, incoming, {
|
|
|
554
555
|
target.set(key, { value, owner });
|
|
555
556
|
}
|
|
556
557
|
}
|
|
557
|
-
function createExtensionContext(config, getRoutes) {
|
|
558
|
+
function createExtensionContext(config, getRoutes, trackedInputs) {
|
|
558
559
|
let project;
|
|
559
560
|
return {
|
|
560
561
|
cwd: config.codegen.cwd,
|
|
@@ -563,6 +564,13 @@ function createExtensionContext(config, getRoutes) {
|
|
|
563
564
|
get routes() {
|
|
564
565
|
return getRoutes();
|
|
565
566
|
},
|
|
567
|
+
trackInput(...paths) {
|
|
568
|
+
if (!trackedInputs) return;
|
|
569
|
+
for (const path of paths) {
|
|
570
|
+
if (!path) continue;
|
|
571
|
+
trackedInputs.add(relative3(config.codegen.cwd, resolve2(config.codegen.cwd, path)));
|
|
572
|
+
}
|
|
573
|
+
},
|
|
566
574
|
project() {
|
|
567
575
|
if (!project) {
|
|
568
576
|
project = new Project2({
|
|
@@ -730,7 +738,7 @@ function rawResponseType(c, outDir) {
|
|
|
730
738
|
return c.contractSource.response;
|
|
731
739
|
}
|
|
732
740
|
if (c.controllerRef) {
|
|
733
|
-
let relPath =
|
|
741
|
+
let relPath = relative4(outDir, c.controllerRef.filePath).replace(/\.ts$/, "");
|
|
734
742
|
if (!relPath.startsWith(".")) relPath = `./${relPath}`;
|
|
735
743
|
return `Awaited<ReturnType<import('${relPath}').${c.controllerRef.className}['${c.controllerRef.methodName}']>>`;
|
|
736
744
|
}
|
|
@@ -1056,6 +1064,11 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
1056
1064
|
config: {},
|
|
1057
1065
|
project: () => {
|
|
1058
1066
|
throw new Error("ExtensionContext.project() is unavailable in standalone emitApi.");
|
|
1067
|
+
},
|
|
1068
|
+
// No manifest is written on the standalone path, so there is nothing for
|
|
1069
|
+
// tracked dependencies to be recorded into — accept and discard them
|
|
1070
|
+
// rather than making every caller supply a sink.
|
|
1071
|
+
trackInput: () => {
|
|
1059
1072
|
}
|
|
1060
1073
|
};
|
|
1061
1074
|
const importsByFile = /* @__PURE__ */ new Map();
|
|
@@ -1112,7 +1125,7 @@ function buildApiFile(routes, outDir, opts = {}) {
|
|
|
1112
1125
|
for (const [filePath, names] of importsByFile) {
|
|
1113
1126
|
let relPath;
|
|
1114
1127
|
if (isAbsolute2(filePath)) {
|
|
1115
|
-
relPath =
|
|
1128
|
+
relPath = relative4(outDir, filePath).replace(/\.ts$/, "");
|
|
1116
1129
|
if (!relPath.startsWith(".")) relPath = `./${relPath}`;
|
|
1117
1130
|
} else {
|
|
1118
1131
|
relPath = filePath;
|
|
@@ -1226,7 +1239,7 @@ async function emitCache(pages, outDir) {
|
|
|
1226
1239
|
|
|
1227
1240
|
// src/emit/emit-forms.ts
|
|
1228
1241
|
import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
|
|
1229
|
-
import { join as join6, relative as
|
|
1242
|
+
import { join as join6, relative as relative5 } from "path";
|
|
1230
1243
|
async function emitForms(routes, outDir, config, adapter) {
|
|
1231
1244
|
if (config && config.enabled === false) return false;
|
|
1232
1245
|
const content = buildFormsFileWithAdapter(routes, outDir, adapter, config);
|
|
@@ -1245,7 +1258,7 @@ function deriveBaseName(routeName) {
|
|
|
1245
1258
|
return { method, full };
|
|
1246
1259
|
}
|
|
1247
1260
|
function relImport(outDir, filePath) {
|
|
1248
|
-
let relPath =
|
|
1261
|
+
let relPath = relative5(outDir, filePath).replace(/\.ts$/, "");
|
|
1249
1262
|
if (!relPath.startsWith(".")) relPath = `./${relPath}`;
|
|
1250
1263
|
return relPath;
|
|
1251
1264
|
}
|
|
@@ -1996,7 +2009,7 @@ async function emitOpenApi(routes, outDir, opts = {}) {
|
|
|
1996
2009
|
|
|
1997
2010
|
// src/emit/emit-pages.ts
|
|
1998
2011
|
import { mkdir as mkdir7, writeFile as writeFile7 } from "fs/promises";
|
|
1999
|
-
import { join as join10, relative as
|
|
2012
|
+
import { join as join10, relative as relative6 } from "path";
|
|
2000
2013
|
async function emitPages(pages, outDir, _options = {}) {
|
|
2001
2014
|
await mkdir7(outDir, { recursive: true });
|
|
2002
2015
|
const pageNameUnion = pages.length > 0 ? pages.map((p) => JSON.stringify(p.name)).join(" | ") : "never";
|
|
@@ -2036,7 +2049,7 @@ ${propsBody}
|
|
|
2036
2049
|
`;
|
|
2037
2050
|
}
|
|
2038
2051
|
function buildAugmentationType(page, outDir) {
|
|
2039
|
-
let importPath =
|
|
2052
|
+
let importPath = relative6(outDir, page.absolutePath).replace(/\.(tsx?|vue|svelte)$/, "");
|
|
2040
2053
|
if (!importPath.startsWith(".")) {
|
|
2041
2054
|
importPath = `./${importPath}`;
|
|
2042
2055
|
}
|
|
@@ -2167,7 +2180,7 @@ function buildEmpty() {
|
|
|
2167
2180
|
// src/generate-manifest.ts
|
|
2168
2181
|
import { createHash } from "crypto";
|
|
2169
2182
|
import { readFile as readFile2, readdir, writeFile as writeFile9 } from "fs/promises";
|
|
2170
|
-
import { join as join12, relative as
|
|
2183
|
+
import { join as join12, relative as relative7 } from "path";
|
|
2171
2184
|
import fg2 from "fast-glob";
|
|
2172
2185
|
var MANIFEST_FILE = ".codegen-manifest.json";
|
|
2173
2186
|
var LOCK_FILE = ".watcher.lock";
|
|
@@ -2190,6 +2203,9 @@ function isManifestShape(value) {
|
|
|
2190
2203
|
if (candidate.configKeyHashes !== void 0 && !isStringRecord(candidate.configKeyHashes)) {
|
|
2191
2204
|
return false;
|
|
2192
2205
|
}
|
|
2206
|
+
if (candidate.extraInputs !== void 0 && (!Array.isArray(candidate.extraInputs) || !candidate.extraInputs.every((entry) => typeof entry === "string"))) {
|
|
2207
|
+
return false;
|
|
2208
|
+
}
|
|
2193
2209
|
if (!Array.isArray(candidate.files)) return false;
|
|
2194
2210
|
return candidate.files.every((entry) => typeof entry === "string");
|
|
2195
2211
|
}
|
|
@@ -2229,21 +2245,30 @@ async function discoverInputFiles(config) {
|
|
|
2229
2245
|
const matched = await fg2(globs, { cwd, absolute: true, onlyFiles: true });
|
|
2230
2246
|
return [...new Set(matched)].sort();
|
|
2231
2247
|
}
|
|
2232
|
-
async function computeInputsHash(config) {
|
|
2248
|
+
async function computeInputsHash(config, extraInputs = []) {
|
|
2233
2249
|
const hash = createHash("sha256");
|
|
2234
2250
|
hash.update(`version:${VERSION}
|
|
2235
2251
|
`);
|
|
2236
2252
|
hash.update(`config:${serializeConfig(config)}
|
|
2237
2253
|
`);
|
|
2238
|
-
const inputFiles = await discoverInputFiles(config);
|
|
2239
2254
|
const cwd = config.codegen.cwd;
|
|
2240
|
-
|
|
2255
|
+
const globbed = await discoverInputFiles(config);
|
|
2256
|
+
const globbedRelative = new Set(globbed.map((file) => relative7(cwd, file)));
|
|
2257
|
+
const extra = [...new Set(extraInputs)].filter((file) => !globbedRelative.has(file)).sort();
|
|
2258
|
+
for (const file of globbed) {
|
|
2241
2259
|
const contents = await readFile2(file, "utf8");
|
|
2242
|
-
hash.update(`file:${
|
|
2260
|
+
hash.update(`file:${relative7(cwd, file)}
|
|
2243
2261
|
`);
|
|
2244
2262
|
hash.update(contents);
|
|
2245
2263
|
hash.update("\n");
|
|
2246
2264
|
}
|
|
2265
|
+
for (const file of extra) {
|
|
2266
|
+
const contents = await readFile2(join12(cwd, file), "utf8").catch(() => null);
|
|
2267
|
+
hash.update(`extra:${file}
|
|
2268
|
+
`);
|
|
2269
|
+
hash.update(contents ?? "\0missing");
|
|
2270
|
+
hash.update("\n");
|
|
2271
|
+
}
|
|
2247
2272
|
return hash.digest("hex");
|
|
2248
2273
|
}
|
|
2249
2274
|
async function readManifest(outDir) {
|
|
@@ -2257,6 +2282,7 @@ async function readManifest(outDir) {
|
|
|
2257
2282
|
...parsed.entryPoint ? { entryPoint: parsed.entryPoint } : {},
|
|
2258
2283
|
...parsed.configHash ? { configHash: parsed.configHash } : {},
|
|
2259
2284
|
...parsed.configKeyHashes ? { configKeyHashes: parsed.configKeyHashes } : {},
|
|
2285
|
+
...parsed.extraInputs ? { extraInputs: parsed.extraInputs } : {},
|
|
2260
2286
|
files: parsed.files
|
|
2261
2287
|
};
|
|
2262
2288
|
} catch {
|
|
@@ -2279,7 +2305,7 @@ async function listOutputFiles(outDir) {
|
|
|
2279
2305
|
if (entry.isDirectory()) {
|
|
2280
2306
|
await walk(abs);
|
|
2281
2307
|
} else if (entry.isFile()) {
|
|
2282
|
-
const rel =
|
|
2308
|
+
const rel = relative7(outDir, abs);
|
|
2283
2309
|
if (rel === MANIFEST_FILE || rel === LOCK_FILE) continue;
|
|
2284
2310
|
found.push(rel);
|
|
2285
2311
|
}
|
|
@@ -2316,8 +2342,8 @@ function driftGuardMessage(outDir, previousEntryPoint, currentEntryPoint, differ
|
|
|
2316
2342
|
}
|
|
2317
2343
|
async function generate(config, inputRoutes = [], entryPoint = "cli") {
|
|
2318
2344
|
setCodegenDebug(config.debug);
|
|
2319
|
-
const inputsHash = await computeInputsHash(config);
|
|
2320
2345
|
const manifest = await readManifest(config.codegen.outDir);
|
|
2346
|
+
const inputsHash = await computeInputsHash(config, manifest?.extraInputs ?? []);
|
|
2321
2347
|
if (await isManifestFresh(config.codegen.outDir, manifest, inputsHash)) {
|
|
2322
2348
|
console.log(`[nestjs-codegen] ${config.codegen.outDir} up to date, skipped`);
|
|
2323
2349
|
return;
|
|
@@ -2338,7 +2364,8 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
|
|
|
2338
2364
|
}
|
|
2339
2365
|
const extensions = config.extensions ?? [];
|
|
2340
2366
|
let routes = inputRoutes;
|
|
2341
|
-
const
|
|
2367
|
+
const trackedInputs = /* @__PURE__ */ new Set();
|
|
2368
|
+
const ctx = createExtensionContext(config, () => routes, trackedInputs);
|
|
2342
2369
|
if (extensions.length > 0) {
|
|
2343
2370
|
routes = await applyTransformRoutes(routes, extensions, ctx);
|
|
2344
2371
|
}
|
|
@@ -2398,13 +2425,16 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
|
|
|
2398
2425
|
}
|
|
2399
2426
|
}
|
|
2400
2427
|
const outputFiles = await listOutputFiles(config.codegen.outDir);
|
|
2428
|
+
const extraInputs = [...trackedInputs].sort();
|
|
2429
|
+
const recordedHash = extraInputs.length > 0 ? await computeInputsHash(config, extraInputs) : inputsHash;
|
|
2401
2430
|
await writeManifest(config.codegen.outDir, {
|
|
2402
2431
|
version: VERSION,
|
|
2403
|
-
hash:
|
|
2432
|
+
hash: recordedHash,
|
|
2404
2433
|
entryPoint,
|
|
2405
2434
|
configHash,
|
|
2406
2435
|
configKeyHashes,
|
|
2407
|
-
files: outputFiles
|
|
2436
|
+
files: outputFiles,
|
|
2437
|
+
...extraInputs.length > 0 ? { extraInputs } : {}
|
|
2408
2438
|
});
|
|
2409
2439
|
}
|
|
2410
2440
|
|
|
@@ -2414,11 +2444,11 @@ import { join as join16 } from "path";
|
|
|
2414
2444
|
import chokidar from "chokidar";
|
|
2415
2445
|
|
|
2416
2446
|
// src/discovery/contracts-fast.ts
|
|
2417
|
-
import { join as join14, resolve as
|
|
2447
|
+
import { join as join14, resolve as resolve4 } from "path";
|
|
2418
2448
|
import fg3 from "fast-glob";
|
|
2419
2449
|
import {
|
|
2420
|
-
Node as
|
|
2421
|
-
Project as
|
|
2450
|
+
Node as Node9,
|
|
2451
|
+
Project as Project4
|
|
2422
2452
|
} from "ts-morph";
|
|
2423
2453
|
|
|
2424
2454
|
// src/discovery/dto-type-resolver.ts
|
|
@@ -2434,7 +2464,7 @@ import {
|
|
|
2434
2464
|
|
|
2435
2465
|
// src/discovery/type-ref-resolution.ts
|
|
2436
2466
|
import { readFileSync } from "fs";
|
|
2437
|
-
import { dirname as dirname3, resolve as
|
|
2467
|
+
import { dirname as dirname3, resolve as resolve3 } from "path";
|
|
2438
2468
|
import {
|
|
2439
2469
|
Node as Node2
|
|
2440
2470
|
} from "ts-morph";
|
|
@@ -2491,9 +2521,9 @@ function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
|
|
|
2491
2521
|
const dir = dirname3(sourceFile.getFilePath());
|
|
2492
2522
|
const noExt = moduleSpecifier.replace(/\.(js|ts)$/, "");
|
|
2493
2523
|
return [
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2524
|
+
resolve3(dir, `${noExt}.ts`),
|
|
2525
|
+
resolve3(dir, `${moduleSpecifier}.ts`),
|
|
2526
|
+
resolve3(dir, moduleSpecifier, "index.ts")
|
|
2497
2527
|
];
|
|
2498
2528
|
}
|
|
2499
2529
|
const ctx = _ctxFor(project);
|
|
@@ -2514,8 +2544,8 @@ function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
|
|
|
2514
2544
|
const rest = moduleSpecifier.slice(prefix.length);
|
|
2515
2545
|
const candidates = [];
|
|
2516
2546
|
for (const mapping of mappings) {
|
|
2517
|
-
const resolved =
|
|
2518
|
-
candidates.push(`${resolved}.ts`,
|
|
2547
|
+
const resolved = resolve3(baseUrl, mapping.replace("*", rest));
|
|
2548
|
+
candidates.push(`${resolved}.ts`, resolve3(resolved, "index.ts"));
|
|
2519
2549
|
}
|
|
2520
2550
|
dbg(" resolved candidates:", candidates);
|
|
2521
2551
|
return candidates;
|
|
@@ -3410,6 +3440,12 @@ function toFilterFieldType(name, r) {
|
|
|
3410
3440
|
}
|
|
3411
3441
|
|
|
3412
3442
|
// src/discovery/filter-for.ts
|
|
3443
|
+
function resolveMixinEntityClass(mixin, project) {
|
|
3444
|
+
const entityArg = mixin?.classArgs[0];
|
|
3445
|
+
if (!entityArg) return void 0;
|
|
3446
|
+
const file = project.getSourceFile(entityArg.filePath) ?? project.addSourceFileAtPathIfExists(entityArg.filePath);
|
|
3447
|
+
return file?.getClass(entityArg.name);
|
|
3448
|
+
}
|
|
3413
3449
|
function classifyFilterForHint(typeInit) {
|
|
3414
3450
|
if (Node5.isStringLiteral(typeInit)) {
|
|
3415
3451
|
switch (typeInit.getLiteralValue()) {
|
|
@@ -3483,7 +3519,9 @@ function extractFilterForHints(classDecl, project) {
|
|
|
3483
3519
|
}
|
|
3484
3520
|
return hints;
|
|
3485
3521
|
}
|
|
3486
|
-
function extractApplyFilterInfo(method, sourceFile, project) {
|
|
3522
|
+
function extractApplyFilterInfo(method, sourceFile, project, mixin) {
|
|
3523
|
+
const declFile = method.getSourceFile();
|
|
3524
|
+
const mixinEntity = resolveMixinEntityClass(mixin, project);
|
|
3487
3525
|
for (const param of method.getParameters()) {
|
|
3488
3526
|
const filterDecorator = param.getDecorators().find((d) => d.getName() === "ApplyFilter");
|
|
3489
3527
|
if (!filterDecorator) continue;
|
|
@@ -3503,12 +3541,12 @@ function extractApplyFilterInfo(method, sourceFile, project) {
|
|
|
3503
3541
|
}
|
|
3504
3542
|
}
|
|
3505
3543
|
const filterClassName = filterClassArg.getText();
|
|
3506
|
-
const resolved = findType(filterClassName,
|
|
3507
|
-
|
|
3508
|
-
|
|
3544
|
+
const resolved = findType(filterClassName, declFile, project);
|
|
3545
|
+
const classDecl = resolved?.kind === "class" ? resolved.decl : resolveLocalClassDeclaration(filterClassArg);
|
|
3546
|
+
if (classDecl) {
|
|
3509
3547
|
let fieldTypes = extractClassPropertyTypes(classDecl, project);
|
|
3510
3548
|
if (fieldTypes.length === 0) {
|
|
3511
|
-
fieldTypes = extractFilterableEntityFields(classDecl, project);
|
|
3549
|
+
fieldTypes = extractFilterableEntityFields(classDecl, project, mixinEntity);
|
|
3512
3550
|
}
|
|
3513
3551
|
const filterForHints = extractFilterForHints(classDecl, project);
|
|
3514
3552
|
if (filterForHints.size > 0) {
|
|
@@ -3593,22 +3631,29 @@ function extractClassPropertyTypes(classDecl, project) {
|
|
|
3593
3631
|
}
|
|
3594
3632
|
return fields;
|
|
3595
3633
|
}
|
|
3596
|
-
function
|
|
3634
|
+
function resolveLocalClassDeclaration(identifier) {
|
|
3635
|
+
if (!Node5.isIdentifier(identifier)) return void 0;
|
|
3636
|
+
return identifier.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => n !== void 0 && Node5.isClassDeclaration(n));
|
|
3637
|
+
}
|
|
3638
|
+
function resolveDeclaredEntity(optionsArg, filterClass, project) {
|
|
3639
|
+
if (!Node5.isObjectLiteralExpression(optionsArg)) return void 0;
|
|
3640
|
+
const entityProp = optionsArg.getProperty("entity");
|
|
3641
|
+
if (!entityProp || !Node5.isPropertyAssignment(entityProp)) return void 0;
|
|
3642
|
+
const entityInit = entityProp.getInitializer();
|
|
3643
|
+
if (!entityInit || !Node5.isIdentifier(entityInit)) return void 0;
|
|
3644
|
+
const resolved = findType(entityInit.getText(), filterClass.getSourceFile(), project);
|
|
3645
|
+
if (!resolved || resolved.kind !== "class") return void 0;
|
|
3646
|
+
return resolved.decl;
|
|
3647
|
+
}
|
|
3648
|
+
function extractFilterableEntityFields(filterClass, project, entityOverride) {
|
|
3597
3649
|
const filterableDecorator = filterClass.getDecorators().find((d) => d.getName() === "Filterable");
|
|
3598
3650
|
if (!filterableDecorator) return [];
|
|
3599
3651
|
const args = filterableDecorator.getArguments();
|
|
3600
3652
|
if (args.length === 0) return [];
|
|
3601
3653
|
const optionsArg = args[0];
|
|
3602
3654
|
if (!Node5.isObjectLiteralExpression(optionsArg)) return [];
|
|
3603
|
-
const
|
|
3604
|
-
if (!
|
|
3605
|
-
const entityInit = entityProp.getInitializer();
|
|
3606
|
-
if (!entityInit || !Node5.isIdentifier(entityInit)) return [];
|
|
3607
|
-
const entityName = entityInit.getText();
|
|
3608
|
-
const filterSourceFile = filterClass.getSourceFile();
|
|
3609
|
-
const resolvedEntity = findType(entityName, filterSourceFile, project);
|
|
3610
|
-
if (!resolvedEntity || resolvedEntity.kind !== "class") return [];
|
|
3611
|
-
const entityDecl = resolvedEntity.decl;
|
|
3655
|
+
const entityDecl = entityOverride ?? resolveDeclaredEntity(optionsArg, filterClass, project);
|
|
3656
|
+
if (!entityDecl) return [];
|
|
3612
3657
|
const fields = collectEntityFields(
|
|
3613
3658
|
entityDecl,
|
|
3614
3659
|
entityDecl.getSourceFile(),
|
|
@@ -4033,9 +4078,9 @@ function unwrapNamedContainer(node, names) {
|
|
|
4033
4078
|
}
|
|
4034
4079
|
return node;
|
|
4035
4080
|
}
|
|
4036
|
-
function extractDtoContract(method, sourceFile, project) {
|
|
4081
|
+
function extractDtoContract(method, sourceFile, project, mixin) {
|
|
4037
4082
|
let body = extractBodyType(method, sourceFile, project);
|
|
4038
|
-
const filterInfo = extractApplyFilterInfo(method, sourceFile, project);
|
|
4083
|
+
const filterInfo = extractApplyFilterInfo(method, sourceFile, project, mixin);
|
|
4039
4084
|
const query = extractQueryType(method, sourceFile, project);
|
|
4040
4085
|
const uploads = extractUploadedFiles(method);
|
|
4041
4086
|
const multipartBody = uploads.fields ? `{ ${uploads.fields} }` : null;
|
|
@@ -4145,12 +4190,94 @@ function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
|
4145
4190
|
return null;
|
|
4146
4191
|
}
|
|
4147
4192
|
|
|
4193
|
+
// src/discovery/heritage.ts
|
|
4194
|
+
import { Node as Node7, Project as Project3 } from "ts-morph";
|
|
4195
|
+
function unwrapExpression(node) {
|
|
4196
|
+
let current = node;
|
|
4197
|
+
while (Node7.isAsExpression(current) || Node7.isSatisfiesExpression(current) || Node7.isParenthesizedExpression(current) || Node7.isTypeAssertion(current)) {
|
|
4198
|
+
current = current.getExpression();
|
|
4199
|
+
}
|
|
4200
|
+
return current;
|
|
4201
|
+
}
|
|
4202
|
+
function resolveReturnedClass(factoryDecl) {
|
|
4203
|
+
const body = factoryDecl.getBody();
|
|
4204
|
+
if (!body) return void 0;
|
|
4205
|
+
const returns = body.getDescendants().filter((n) => Node7.isReturnStatement(n));
|
|
4206
|
+
for (const ret of returns) {
|
|
4207
|
+
const expr = ret.getExpression();
|
|
4208
|
+
if (!expr) continue;
|
|
4209
|
+
const inner = unwrapExpression(expr);
|
|
4210
|
+
if (Node7.isClassExpression(inner)) {
|
|
4211
|
+
return inner;
|
|
4212
|
+
}
|
|
4213
|
+
if (Node7.isIdentifier(inner)) {
|
|
4214
|
+
const name = inner.getText();
|
|
4215
|
+
const decl = body.getDescendants().find((n) => Node7.isClassDeclaration(n) && n.getName() === name);
|
|
4216
|
+
if (decl) return decl;
|
|
4217
|
+
}
|
|
4218
|
+
}
|
|
4219
|
+
return void 0;
|
|
4220
|
+
}
|
|
4221
|
+
function resolveInheritedMethods(cls) {
|
|
4222
|
+
const expr = cls.getExtends()?.getExpression();
|
|
4223
|
+
if (!expr || !Node7.isCallExpression(expr)) return void 0;
|
|
4224
|
+
const callee = expr.getExpression();
|
|
4225
|
+
if (!Node7.isIdentifier(callee)) return void 0;
|
|
4226
|
+
const factoryDecl = callee.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => Node7.isFunctionDeclaration(n));
|
|
4227
|
+
if (!factoryDecl) return void 0;
|
|
4228
|
+
const returnedClass = resolveReturnedClass(factoryDecl);
|
|
4229
|
+
if (!returnedClass) return void 0;
|
|
4230
|
+
const classArgs = [];
|
|
4231
|
+
for (const arg of expr.getArguments()) {
|
|
4232
|
+
if (!Node7.isIdentifier(arg)) continue;
|
|
4233
|
+
const decl = arg.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => Node7.isClassDeclaration(n));
|
|
4234
|
+
if (decl) {
|
|
4235
|
+
classArgs.push({
|
|
4236
|
+
name: decl.getName() ?? arg.getText(),
|
|
4237
|
+
filePath: decl.getSourceFile().getFilePath()
|
|
4238
|
+
});
|
|
4239
|
+
}
|
|
4240
|
+
}
|
|
4241
|
+
return {
|
|
4242
|
+
methods: returnedClass.getMethods(),
|
|
4243
|
+
factoryName: callee.getText(),
|
|
4244
|
+
factoryFilePath: factoryDecl.getSourceFile().getFilePath(),
|
|
4245
|
+
classArgs
|
|
4246
|
+
};
|
|
4247
|
+
}
|
|
4248
|
+
var mixinTypeProject;
|
|
4249
|
+
function getMixinTypeProject() {
|
|
4250
|
+
mixinTypeProject ??= new Project3({
|
|
4251
|
+
skipAddingFilesFromTsConfig: true,
|
|
4252
|
+
compilerOptions: { strict: true }
|
|
4253
|
+
});
|
|
4254
|
+
return mixinTypeProject;
|
|
4255
|
+
}
|
|
4256
|
+
function clearMixinTypeProject() {
|
|
4257
|
+
mixinTypeProject = void 0;
|
|
4258
|
+
}
|
|
4259
|
+
function resolveInstantiatedReturnType(cls, methodName) {
|
|
4260
|
+
const filePath = cls.getSourceFile().getFilePath();
|
|
4261
|
+
const className = cls.getName();
|
|
4262
|
+
if (!className) return void 0;
|
|
4263
|
+
const project = getMixinTypeProject();
|
|
4264
|
+
const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPathIfExists(filePath);
|
|
4265
|
+
const typedCls = sourceFile?.getClass(className);
|
|
4266
|
+
if (!typedCls) return void 0;
|
|
4267
|
+
const prop = typedCls.getType().getProperty(methodName);
|
|
4268
|
+
if (!prop) return void 0;
|
|
4269
|
+
const returnType = prop.getTypeAtLocation(typedCls).getCallSignatures()[0]?.getReturnType();
|
|
4270
|
+
if (!returnType) return void 0;
|
|
4271
|
+
const unwrapped = returnType.getSymbol()?.getName() === "Promise" ? returnType.getTypeArguments()[0] ?? returnType : returnType;
|
|
4272
|
+
return unwrapped.getText(typedCls);
|
|
4273
|
+
}
|
|
4274
|
+
|
|
4148
4275
|
// src/discovery/zod-ast-to-ts.ts
|
|
4149
|
-
import { Node as
|
|
4276
|
+
import { Node as Node8, SyntaxKind as SyntaxKind4 } from "ts-morph";
|
|
4150
4277
|
function zodAstToTs(node) {
|
|
4151
|
-
if (!
|
|
4278
|
+
if (!Node8.isCallExpression(node)) return "unknown";
|
|
4152
4279
|
const expr = node.getExpression();
|
|
4153
|
-
if (
|
|
4280
|
+
if (Node8.isPropertyAccessExpression(expr)) {
|
|
4154
4281
|
const methodName = expr.getName();
|
|
4155
4282
|
const receiver = expr.getExpression();
|
|
4156
4283
|
if (methodName === "optional") {
|
|
@@ -4174,17 +4301,17 @@ function zodAstToTs(node) {
|
|
|
4174
4301
|
case "literal": {
|
|
4175
4302
|
const lit = args[0];
|
|
4176
4303
|
if (!lit) return "unknown";
|
|
4177
|
-
if (
|
|
4178
|
-
if (
|
|
4304
|
+
if (Node8.isStringLiteral(lit)) return JSON.stringify(lit.getLiteralValue());
|
|
4305
|
+
if (Node8.isNumericLiteral(lit)) return lit.getLiteralValue().toString();
|
|
4179
4306
|
if (lit.getKind() === SyntaxKind4.TrueKeyword) return "true";
|
|
4180
4307
|
if (lit.getKind() === SyntaxKind4.FalseKeyword) return "false";
|
|
4181
4308
|
return "unknown";
|
|
4182
4309
|
}
|
|
4183
4310
|
case "enum": {
|
|
4184
4311
|
const arrArg = args[0];
|
|
4185
|
-
if (!arrArg || !
|
|
4312
|
+
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
4186
4313
|
const members = arrArg.getElements().map(
|
|
4187
|
-
(el) =>
|
|
4314
|
+
(el) => Node8.isStringLiteral(el) ? JSON.stringify(el.getLiteralValue()) : "unknown"
|
|
4188
4315
|
);
|
|
4189
4316
|
return members.join(" | ");
|
|
4190
4317
|
}
|
|
@@ -4195,10 +4322,10 @@ function zodAstToTs(node) {
|
|
|
4195
4322
|
}
|
|
4196
4323
|
case "object": {
|
|
4197
4324
|
const objArg = args[0];
|
|
4198
|
-
if (!objArg || !
|
|
4325
|
+
if (!objArg || !Node8.isObjectLiteralExpression(objArg)) return "unknown";
|
|
4199
4326
|
const lines = [];
|
|
4200
4327
|
for (const prop of objArg.getProperties()) {
|
|
4201
|
-
if (!
|
|
4328
|
+
if (!Node8.isPropertyAssignment(prop)) continue;
|
|
4202
4329
|
const key = prop.getName();
|
|
4203
4330
|
const valNode = prop.getInitializer();
|
|
4204
4331
|
if (!valNode) continue;
|
|
@@ -4210,7 +4337,7 @@ function zodAstToTs(node) {
|
|
|
4210
4337
|
}
|
|
4211
4338
|
case "union": {
|
|
4212
4339
|
const arrArg = args[0];
|
|
4213
|
-
if (!arrArg || !
|
|
4340
|
+
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
4214
4341
|
return arrArg.getElements().map(zodAstToTs).join(" | ");
|
|
4215
4342
|
}
|
|
4216
4343
|
case "record": {
|
|
@@ -4220,7 +4347,7 @@ function zodAstToTs(node) {
|
|
|
4220
4347
|
}
|
|
4221
4348
|
case "tuple": {
|
|
4222
4349
|
const arrArg = args[0];
|
|
4223
|
-
if (!arrArg || !
|
|
4350
|
+
if (!arrArg || !Node8.isArrayLiteralExpression(arrArg)) return "unknown";
|
|
4224
4351
|
return `[${arrArg.getElements().map(zodAstToTs).join(", ")}]`;
|
|
4225
4352
|
}
|
|
4226
4353
|
default:
|
|
@@ -4230,18 +4357,18 @@ function zodAstToTs(node) {
|
|
|
4230
4357
|
return "unknown";
|
|
4231
4358
|
}
|
|
4232
4359
|
function isOptionalChain(node) {
|
|
4233
|
-
if (!
|
|
4360
|
+
if (!Node8.isCallExpression(node)) return false;
|
|
4234
4361
|
const expr = node.getExpression();
|
|
4235
|
-
return
|
|
4362
|
+
return Node8.isPropertyAccessExpression(expr) && expr.getName() === "optional";
|
|
4236
4363
|
}
|
|
4237
4364
|
function parseDefineContractCall(callExpr) {
|
|
4238
|
-
if (!
|
|
4365
|
+
if (!Node8.isCallExpression(callExpr)) return null;
|
|
4239
4366
|
const callee = callExpr.getExpression();
|
|
4240
|
-
const calleeName =
|
|
4367
|
+
const calleeName = Node8.isIdentifier(callee) ? callee.getText() : Node8.isPropertyAccessExpression(callee) ? callee.getName() : "";
|
|
4241
4368
|
if (calleeName !== "defineContract") return null;
|
|
4242
4369
|
const args = callExpr.getArguments();
|
|
4243
4370
|
const optsArg = args[0];
|
|
4244
|
-
if (!optsArg || !
|
|
4371
|
+
if (!optsArg || !Node8.isObjectLiteralExpression(optsArg)) return null;
|
|
4245
4372
|
let query = null;
|
|
4246
4373
|
let body = null;
|
|
4247
4374
|
let response = "unknown";
|
|
@@ -4249,7 +4376,7 @@ function parseDefineContractCall(callExpr) {
|
|
|
4249
4376
|
let bodyZodText = null;
|
|
4250
4377
|
let queryZodText = null;
|
|
4251
4378
|
for (const prop of optsArg.getProperties()) {
|
|
4252
|
-
if (!
|
|
4379
|
+
if (!Node8.isPropertyAssignment(prop)) continue;
|
|
4253
4380
|
const propName = prop.getName();
|
|
4254
4381
|
const val = prop.getInitializer();
|
|
4255
4382
|
if (!val) continue;
|
|
@@ -4278,21 +4405,22 @@ async function discoverContractsFast(opts) {
|
|
|
4278
4405
|
project.addSourceFileAtPath(f);
|
|
4279
4406
|
}
|
|
4280
4407
|
bindDiscoveryContext(project, cwd, tsconfigPath);
|
|
4408
|
+
clearMixinTypeProject();
|
|
4281
4409
|
return extractAllRoutes(project);
|
|
4282
4410
|
}
|
|
4283
4411
|
function resolveTsconfigPath(cwd, tsconfig) {
|
|
4284
|
-
return tsconfig ?
|
|
4412
|
+
return tsconfig ? resolve4(tsconfig) : join14(cwd, "tsconfig.json");
|
|
4285
4413
|
}
|
|
4286
4414
|
function createDiscoveryProject(tsconfigPath) {
|
|
4287
4415
|
try {
|
|
4288
|
-
return new
|
|
4416
|
+
return new Project4({
|
|
4289
4417
|
tsConfigFilePath: tsconfigPath,
|
|
4290
4418
|
skipAddingFilesFromTsConfig: true,
|
|
4291
4419
|
skipLoadingLibFiles: true,
|
|
4292
4420
|
skipFileDependencyResolution: true
|
|
4293
4421
|
});
|
|
4294
4422
|
} catch {
|
|
4295
|
-
return new
|
|
4423
|
+
return new Project4({
|
|
4296
4424
|
skipAddingFilesFromTsConfig: true,
|
|
4297
4425
|
skipLoadingLibFiles: true,
|
|
4298
4426
|
skipFileDependencyResolution: true,
|
|
@@ -4367,7 +4495,7 @@ var PersistentDiscovery = class _PersistentDiscovery {
|
|
|
4367
4495
|
async rediscover(changedPaths) {
|
|
4368
4496
|
if (changedPaths) {
|
|
4369
4497
|
for (const p of changedPaths) {
|
|
4370
|
-
const abs =
|
|
4498
|
+
const abs = resolve4(p);
|
|
4371
4499
|
const sf = this.project.getSourceFile(abs);
|
|
4372
4500
|
if (sf) {
|
|
4373
4501
|
await sf.refreshFromFileSystem();
|
|
@@ -4399,15 +4527,16 @@ var PersistentDiscovery = class _PersistentDiscovery {
|
|
|
4399
4527
|
runExtraction() {
|
|
4400
4528
|
clearTypeResolutionCaches(this.project);
|
|
4401
4529
|
clearEnumCache(this.project);
|
|
4530
|
+
clearMixinTypeProject();
|
|
4402
4531
|
return extractRoutesFrom(this.project, this.controllerPaths);
|
|
4403
4532
|
}
|
|
4404
4533
|
};
|
|
4405
4534
|
function decoratorStringArg(decoratorExpr) {
|
|
4406
4535
|
if (!decoratorExpr) return void 0;
|
|
4407
|
-
if (
|
|
4408
|
-
if (
|
|
4536
|
+
if (Node9.isStringLiteral(decoratorExpr)) return decoratorExpr.getLiteralValue();
|
|
4537
|
+
if (Node9.isArrayLiteralExpression(decoratorExpr)) {
|
|
4409
4538
|
const first = decoratorExpr.getElements()[0];
|
|
4410
|
-
if (first &&
|
|
4539
|
+
if (first && Node9.isStringLiteral(first)) return first.getLiteralValue();
|
|
4411
4540
|
}
|
|
4412
4541
|
return void 0;
|
|
4413
4542
|
}
|
|
@@ -4429,7 +4558,8 @@ function joinPaths(prefix, suffix) {
|
|
|
4429
4558
|
if (!prefix && !suffix) return "/";
|
|
4430
4559
|
if (!prefix) return suffix.startsWith("/") ? suffix : `/${suffix}`;
|
|
4431
4560
|
if (!suffix) return prefix.startsWith("/") ? prefix : `/${prefix}`;
|
|
4432
|
-
const
|
|
4561
|
+
const withLeadingSlash = prefix.startsWith("/") ? prefix : `/${prefix}`;
|
|
4562
|
+
const p = withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
|
|
4433
4563
|
const s = suffix.startsWith("/") ? suffix : `/${suffix}`;
|
|
4434
4564
|
const combined = p + s;
|
|
4435
4565
|
return combined === "" ? "/" : combined;
|
|
@@ -4483,7 +4613,8 @@ function buildRoute(args) {
|
|
|
4483
4613
|
methodAs,
|
|
4484
4614
|
sourceFile,
|
|
4485
4615
|
seenNames,
|
|
4486
|
-
contractSource
|
|
4616
|
+
contractSource,
|
|
4617
|
+
mixin
|
|
4487
4618
|
} = args;
|
|
4488
4619
|
const routeName = resolveRouteName(className, methodName, classAs, methodAs);
|
|
4489
4620
|
const qualifiedRef = `${className}.${methodName}`;
|
|
@@ -4499,7 +4630,12 @@ function buildRoute(args) {
|
|
|
4499
4630
|
path: combinedPath,
|
|
4500
4631
|
name: routeName,
|
|
4501
4632
|
params: extractParams(combinedPath),
|
|
4502
|
-
controllerRef: {
|
|
4633
|
+
controllerRef: {
|
|
4634
|
+
className,
|
|
4635
|
+
methodName,
|
|
4636
|
+
filePath: sourceFile.getFilePath(),
|
|
4637
|
+
...mixin ? { mixin } : {}
|
|
4638
|
+
},
|
|
4503
4639
|
contract: { contractSource }
|
|
4504
4640
|
};
|
|
4505
4641
|
}
|
|
@@ -4513,16 +4649,17 @@ function extractContractRoute(args) {
|
|
|
4513
4649
|
className,
|
|
4514
4650
|
sourceFile,
|
|
4515
4651
|
project,
|
|
4516
|
-
seenNames
|
|
4652
|
+
seenNames,
|
|
4653
|
+
mixin
|
|
4517
4654
|
} = args;
|
|
4518
4655
|
const firstDecoratorArg = applyContractDecorator.getArguments()[0];
|
|
4519
4656
|
if (!firstDecoratorArg) return null;
|
|
4520
4657
|
let contractDef = null;
|
|
4521
4658
|
let bodyZodRef = null;
|
|
4522
4659
|
let queryZodRef = null;
|
|
4523
|
-
if (
|
|
4660
|
+
if (Node9.isCallExpression(firstDecoratorArg)) {
|
|
4524
4661
|
contractDef = parseDefineContractCall(firstDecoratorArg);
|
|
4525
|
-
} else if (
|
|
4662
|
+
} else if (Node9.isIdentifier(firstDecoratorArg)) {
|
|
4526
4663
|
const identName = firstDecoratorArg.getText();
|
|
4527
4664
|
const resolvedVar = resolveImportedVariable(identName, sourceFile, project);
|
|
4528
4665
|
if (!resolvedVar) {
|
|
@@ -4565,6 +4702,7 @@ function extractContractRoute(args) {
|
|
|
4565
4702
|
methodAs,
|
|
4566
4703
|
sourceFile,
|
|
4567
4704
|
seenNames,
|
|
4705
|
+
mixin,
|
|
4568
4706
|
contractSource: {
|
|
4569
4707
|
query: contractDef.query,
|
|
4570
4708
|
body: contractDef.body,
|
|
@@ -4581,13 +4719,14 @@ function extractContractRoute(args) {
|
|
|
4581
4719
|
});
|
|
4582
4720
|
}
|
|
4583
4721
|
function extractDtoRoute(args) {
|
|
4584
|
-
const { cls, method, verb, prefix, className, sourceFile, project, seenNames } = args;
|
|
4722
|
+
const { cls, method, verb, prefix, className, sourceFile, project, seenNames, mixin } = args;
|
|
4585
4723
|
if (!verb) return null;
|
|
4586
4724
|
const combined = joinPaths(prefix, verb.handlerPath);
|
|
4587
4725
|
const methodName = method.getName();
|
|
4588
4726
|
const classAs = readAsDecorator(cls, `class ${className}`);
|
|
4589
4727
|
const methodAs = readAsDecorator(method, `${className}.${methodName}`);
|
|
4590
|
-
const dtoContract = extractDtoContract(method, sourceFile, project);
|
|
4728
|
+
const dtoContract = extractDtoContract(method, sourceFile, project, mixin);
|
|
4729
|
+
const mixinResponse = mixin ? resolveInstantiatedReturnType(cls, methodName) : void 0;
|
|
4591
4730
|
return buildRoute({
|
|
4592
4731
|
className,
|
|
4593
4732
|
methodName,
|
|
@@ -4597,10 +4736,11 @@ function extractDtoRoute(args) {
|
|
|
4597
4736
|
methodAs,
|
|
4598
4737
|
sourceFile,
|
|
4599
4738
|
seenNames,
|
|
4739
|
+
mixin,
|
|
4600
4740
|
contractSource: {
|
|
4601
4741
|
query: dtoContract?.query ?? null,
|
|
4602
4742
|
body: dtoContract?.body ?? null,
|
|
4603
|
-
response: dtoContract?.response ?? "unknown",
|
|
4743
|
+
response: mixinResponse ?? dtoContract?.response ?? "unknown",
|
|
4604
4744
|
error: dtoContract?.error ?? null,
|
|
4605
4745
|
queryRef: dtoContract?.queryRef ?? null,
|
|
4606
4746
|
bodyRef: dtoContract?.bodyRef ?? null,
|
|
@@ -4629,7 +4769,17 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
4629
4769
|
const firstArg2 = controllerDecorator.getArguments()[0];
|
|
4630
4770
|
const prefix = decoratorStringArg(firstArg2) ?? "";
|
|
4631
4771
|
const className = cls.getName() ?? "Unknown";
|
|
4632
|
-
|
|
4772
|
+
const inherited = resolveInheritedMethods(cls);
|
|
4773
|
+
const mixinBinding = inherited ? {
|
|
4774
|
+
factoryName: inherited.factoryName,
|
|
4775
|
+
factoryFilePath: inherited.factoryFilePath,
|
|
4776
|
+
classArgs: inherited.classArgs
|
|
4777
|
+
} : void 0;
|
|
4778
|
+
const methods = [
|
|
4779
|
+
...cls.getMethods().map((method) => ({ method })),
|
|
4780
|
+
...(inherited?.methods ?? []).map((method) => ({ method, mixin: mixinBinding }))
|
|
4781
|
+
];
|
|
4782
|
+
for (const { method, mixin } of methods) {
|
|
4633
4783
|
const verb = resolveVerb(method);
|
|
4634
4784
|
const applyContractDecorator = method.getDecorator("ApplyContract");
|
|
4635
4785
|
const route = applyContractDecorator ? extractContractRoute({
|
|
@@ -4641,7 +4791,8 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
4641
4791
|
className,
|
|
4642
4792
|
sourceFile,
|
|
4643
4793
|
project,
|
|
4644
|
-
seenNames
|
|
4794
|
+
seenNames,
|
|
4795
|
+
mixin
|
|
4645
4796
|
}) : extractDtoRoute({
|
|
4646
4797
|
cls,
|
|
4647
4798
|
method,
|
|
@@ -4650,7 +4801,8 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
4650
4801
|
className,
|
|
4651
4802
|
sourceFile,
|
|
4652
4803
|
project,
|
|
4653
|
-
seenNames
|
|
4804
|
+
seenNames,
|
|
4805
|
+
mixin
|
|
4654
4806
|
});
|
|
4655
4807
|
if (route) routes.push(route);
|
|
4656
4808
|
}
|
|
@@ -4934,7 +5086,7 @@ function createChainModuleRenderer(opts) {
|
|
|
4934
5086
|
}
|
|
4935
5087
|
|
|
4936
5088
|
// src/index.ts
|
|
4937
|
-
var VERSION = "0.
|
|
5089
|
+
var VERSION = "0.17.0";
|
|
4938
5090
|
export {
|
|
4939
5091
|
CodegenError,
|
|
4940
5092
|
ConfigError,
|