@dudousxd/nestjs-codegen 0.16.0 → 0.17.1

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/dist/index.cjs CHANGED
@@ -251,7 +251,7 @@ Run \`nestjs-codegen init\` to create a starter config.`
251
251
 
252
252
  // src/generate.ts
253
253
  var import_promises12 = require("fs/promises");
254
- var import_node_path13 = require("path");
254
+ var import_node_path14 = require("path");
255
255
 
256
256
  // src/discovery/pages.ts
257
257
  var import_promises2 = require("fs/promises");
@@ -580,9 +580,10 @@ function inferExpressionType(node) {
580
580
 
581
581
  // src/emit/emit-api.ts
582
582
  var import_promises3 = require("fs/promises");
583
- var import_node_path4 = require("path");
583
+ var import_node_path5 = require("path");
584
584
 
585
585
  // src/extension/registry.ts
586
+ var import_node_path4 = require("path");
586
587
  var import_ts_morph2 = require("ts-morph");
587
588
  function resolveApiSlots(extensions) {
588
589
  let layer;
@@ -615,7 +616,7 @@ function mergeExclusive(target, incoming, {
615
616
  target.set(key, { value, owner });
616
617
  }
617
618
  }
618
- function createExtensionContext(config, getRoutes) {
619
+ function createExtensionContext(config, getRoutes, trackedInputs) {
619
620
  let project;
620
621
  return {
621
622
  cwd: config.codegen.cwd,
@@ -624,6 +625,13 @@ function createExtensionContext(config, getRoutes) {
624
625
  get routes() {
625
626
  return getRoutes();
626
627
  },
628
+ trackInput(...paths) {
629
+ if (!trackedInputs) return;
630
+ for (const path of paths) {
631
+ if (!path) continue;
632
+ trackedInputs.add((0, import_node_path4.relative)(config.codegen.cwd, (0, import_node_path4.resolve)(config.codegen.cwd, path)));
633
+ }
634
+ },
627
635
  project() {
628
636
  if (!project) {
629
637
  project = new import_ts_morph2.Project({
@@ -683,7 +691,7 @@ function requestShape(route) {
683
691
  async function emitApi(routes, outDir, opts = {}) {
684
692
  await (0, import_promises3.mkdir)(outDir, { recursive: true });
685
693
  const content = buildApiFile(routes, outDir, opts);
686
- await (0, import_promises3.writeFile)((0, import_node_path4.join)(outDir, "api.ts"), content, "utf8");
694
+ await (0, import_promises3.writeFile)((0, import_node_path5.join)(outDir, "api.ts"), content, "utf8");
687
695
  }
688
696
  function splitName(name) {
689
697
  return name.split(".");
@@ -791,7 +799,7 @@ function rawResponseType(c, outDir) {
791
799
  return c.contractSource.response;
792
800
  }
793
801
  if (c.controllerRef) {
794
- let relPath = (0, import_node_path4.relative)(outDir, c.controllerRef.filePath).replace(/\.ts$/, "");
802
+ let relPath = (0, import_node_path5.relative)(outDir, c.controllerRef.filePath).replace(/\.ts$/, "");
795
803
  if (!relPath.startsWith(".")) relPath = `./${relPath}`;
796
804
  return `Awaited<ReturnType<import('${relPath}').${c.controllerRef.className}['${c.controllerRef.methodName}']>>`;
797
805
  }
@@ -1117,6 +1125,11 @@ function buildApiFile(routes, outDir, opts = {}) {
1117
1125
  config: {},
1118
1126
  project: () => {
1119
1127
  throw new Error("ExtensionContext.project() is unavailable in standalone emitApi.");
1128
+ },
1129
+ // No manifest is written on the standalone path, so there is nothing for
1130
+ // tracked dependencies to be recorded into — accept and discard them
1131
+ // rather than making every caller supply a sink.
1132
+ trackInput: () => {
1120
1133
  }
1121
1134
  };
1122
1135
  const importsByFile = /* @__PURE__ */ new Map();
@@ -1172,8 +1185,8 @@ function buildApiFile(routes, outDir, opts = {}) {
1172
1185
  const emittedNames = /* @__PURE__ */ new Set();
1173
1186
  for (const [filePath, names] of importsByFile) {
1174
1187
  let relPath;
1175
- if ((0, import_node_path4.isAbsolute)(filePath)) {
1176
- relPath = (0, import_node_path4.relative)(outDir, filePath).replace(/\.ts$/, "");
1188
+ if ((0, import_node_path5.isAbsolute)(filePath)) {
1189
+ relPath = (0, import_node_path5.relative)(outDir, filePath).replace(/\.ts$/, "");
1177
1190
  if (!relPath.startsWith(".")) relPath = `./${relPath}`;
1178
1191
  } else {
1179
1192
  relPath = filePath;
@@ -1267,7 +1280,7 @@ function buildApiFile(routes, outDir, opts = {}) {
1267
1280
 
1268
1281
  // src/emit/emit-cache.ts
1269
1282
  var import_promises4 = require("fs/promises");
1270
- var import_node_path5 = require("path");
1283
+ var import_node_path6 = require("path");
1271
1284
  async function emitCache(pages, outDir) {
1272
1285
  await (0, import_promises4.mkdir)(outDir, { recursive: true });
1273
1286
  const entries = await Promise.all(
@@ -1281,19 +1294,19 @@ async function emitCache(pages, outDir) {
1281
1294
  })
1282
1295
  );
1283
1296
  const cache = { pages: entries };
1284
- await (0, import_promises4.writeFile)((0, import_node_path5.join)(outDir, "components.json"), `${JSON.stringify(cache, null, 2)}
1297
+ await (0, import_promises4.writeFile)((0, import_node_path6.join)(outDir, "components.json"), `${JSON.stringify(cache, null, 2)}
1285
1298
  `, "utf8");
1286
1299
  }
1287
1300
 
1288
1301
  // src/emit/emit-forms.ts
1289
1302
  var import_promises5 = require("fs/promises");
1290
- var import_node_path6 = require("path");
1303
+ var import_node_path7 = require("path");
1291
1304
  async function emitForms(routes, outDir, config, adapter) {
1292
1305
  if (config && config.enabled === false) return false;
1293
1306
  const content = buildFormsFileWithAdapter(routes, outDir, adapter, config);
1294
1307
  if (content === null) return false;
1295
1308
  await (0, import_promises5.mkdir)(outDir, { recursive: true });
1296
- await (0, import_promises5.writeFile)((0, import_node_path6.join)(outDir, "forms.ts"), content, "utf8");
1309
+ await (0, import_promises5.writeFile)((0, import_node_path7.join)(outDir, "forms.ts"), content, "utf8");
1297
1310
  return true;
1298
1311
  }
1299
1312
  function pascal(segment) {
@@ -1306,7 +1319,7 @@ function deriveBaseName(routeName) {
1306
1319
  return { method, full };
1307
1320
  }
1308
1321
  function relImport(outDir, filePath) {
1309
- let relPath = (0, import_node_path6.relative)(outDir, filePath).replace(/\.ts$/, "");
1322
+ let relPath = (0, import_node_path7.relative)(outDir, filePath).replace(/\.ts$/, "");
1310
1323
  if (!relPath.startsWith(".")) relPath = `./${relPath}`;
1311
1324
  return relPath;
1312
1325
  }
@@ -1580,7 +1593,7 @@ function buildFormsFileWithAdapter(routes, outDir, adapter, config) {
1580
1593
 
1581
1594
  // src/emit/emit-index.ts
1582
1595
  var import_promises6 = require("fs/promises");
1583
- var import_node_path7 = require("path");
1596
+ var import_node_path8 = require("path");
1584
1597
  async function emitIndex(outDir, hasContracts = false, hasForms = false) {
1585
1598
  await (0, import_promises6.mkdir)(outDir, { recursive: true });
1586
1599
  const exports2 = ["export * from './pages.js';", "export * from './routes.js';"];
@@ -1593,12 +1606,12 @@ async function emitIndex(outDir, hasContracts = false, hasForms = false) {
1593
1606
  const content = ["// Generated by @dudousxd/nestjs-codegen. Do not edit.", ...exports2, ""].join(
1594
1607
  "\n"
1595
1608
  );
1596
- await (0, import_promises6.writeFile)((0, import_node_path7.join)(outDir, "index.d.ts"), content, "utf8");
1609
+ await (0, import_promises6.writeFile)((0, import_node_path8.join)(outDir, "index.d.ts"), content, "utf8");
1597
1610
  }
1598
1611
 
1599
1612
  // src/emit/emit-mocks.ts
1600
1613
  var import_promises7 = require("fs/promises");
1601
- var import_node_path8 = require("path");
1614
+ var import_node_path9 = require("path");
1602
1615
 
1603
1616
  // src/ir/schema-node-to-json-schema.ts
1604
1617
  var DEFAULT_CTX = { refPrefix: "#/components/schemas/" };
@@ -1925,12 +1938,12 @@ async function emitMocks(routes, outDir, opts = {}) {
1925
1938
  await (0, import_promises7.mkdir)(outDir, { recursive: true });
1926
1939
  const content = buildMocksFile(routes, opts);
1927
1940
  const fileName = opts.fileName ?? "mocks.ts";
1928
- await (0, import_promises7.writeFile)((0, import_node_path8.join)(outDir, fileName), content, "utf8");
1941
+ await (0, import_promises7.writeFile)((0, import_node_path9.join)(outDir, fileName), content, "utf8");
1929
1942
  }
1930
1943
 
1931
1944
  // src/emit/emit-openapi.ts
1932
1945
  var import_promises8 = require("fs/promises");
1933
- var import_node_path9 = require("path");
1946
+ var import_node_path10 = require("path");
1934
1947
  var REF_PREFIX2 = "#/components/schemas/";
1935
1948
  function toOpenApiPath(path) {
1936
1949
  return path.replace(/:([^/]+)/g, "{$1}");
@@ -2051,13 +2064,13 @@ async function emitOpenApi(routes, outDir, opts = {}) {
2051
2064
  await (0, import_promises8.mkdir)(outDir, { recursive: true });
2052
2065
  const doc = buildOpenApiSpec(routes, opts);
2053
2066
  const fileName = opts.fileName ?? "openapi.json";
2054
- await (0, import_promises8.writeFile)((0, import_node_path9.join)(outDir, fileName), `${JSON.stringify(doc, null, 2)}
2067
+ await (0, import_promises8.writeFile)((0, import_node_path10.join)(outDir, fileName), `${JSON.stringify(doc, null, 2)}
2055
2068
  `, "utf8");
2056
2069
  }
2057
2070
 
2058
2071
  // src/emit/emit-pages.ts
2059
2072
  var import_promises9 = require("fs/promises");
2060
- var import_node_path10 = require("path");
2073
+ var import_node_path11 = require("path");
2061
2074
  async function emitPages(pages, outDir, _options = {}) {
2062
2075
  await (0, import_promises9.mkdir)(outDir, { recursive: true });
2063
2076
  const pageNameUnion = pages.length > 0 ? pages.map((p) => JSON.stringify(p.name)).join(" | ") : "never";
@@ -2078,7 +2091,7 @@ ${augBody}
2078
2091
  }
2079
2092
  ${sharedPropsBlock}}
2080
2093
  `;
2081
- await (0, import_promises9.writeFile)((0, import_node_path10.join)(outDir, "pages.d.ts"), content, "utf8");
2094
+ await (0, import_promises9.writeFile)((0, import_node_path11.join)(outDir, "pages.d.ts"), content, "utf8");
2082
2095
  }
2083
2096
  function buildSharedPropsBlock(sharedProps) {
2084
2097
  if (!sharedProps) return "";
@@ -2097,7 +2110,7 @@ ${propsBody}
2097
2110
  `;
2098
2111
  }
2099
2112
  function buildAugmentationType(page, outDir) {
2100
- let importPath = (0, import_node_path10.relative)(outDir, page.absolutePath).replace(/\.(tsx?|vue|svelte)$/, "");
2113
+ let importPath = (0, import_node_path11.relative)(outDir, page.absolutePath).replace(/\.(tsx?|vue|svelte)$/, "");
2101
2114
  if (!importPath.startsWith(".")) {
2102
2115
  importPath = `./${importPath}`;
2103
2116
  }
@@ -2109,11 +2122,11 @@ function needsQuotes(name) {
2109
2122
 
2110
2123
  // src/emit/emit-routes.ts
2111
2124
  var import_promises10 = require("fs/promises");
2112
- var import_node_path11 = require("path");
2125
+ var import_node_path12 = require("path");
2113
2126
  async function emitRoutes(routes, outDir) {
2114
2127
  await (0, import_promises10.mkdir)(outDir, { recursive: true });
2115
2128
  const content = buildRoutesFile(routes);
2116
- await (0, import_promises10.writeFile)((0, import_node_path11.join)(outDir, "routes.ts"), content, "utf8");
2129
+ await (0, import_promises10.writeFile)((0, import_node_path12.join)(outDir, "routes.ts"), content, "utf8");
2117
2130
  }
2118
2131
  function buildRoutesFile(routes) {
2119
2132
  if (routes.length === 0) {
@@ -2228,7 +2241,7 @@ function buildEmpty() {
2228
2241
  // src/generate-manifest.ts
2229
2242
  var import_node_crypto = require("crypto");
2230
2243
  var import_promises11 = require("fs/promises");
2231
- var import_node_path12 = require("path");
2244
+ var import_node_path13 = require("path");
2232
2245
  var import_fast_glob2 = __toESM(require("fast-glob"), 1);
2233
2246
  var MANIFEST_FILE = ".codegen-manifest.json";
2234
2247
  var LOCK_FILE = ".watcher.lock";
@@ -2251,6 +2264,9 @@ function isManifestShape(value) {
2251
2264
  if (candidate.configKeyHashes !== void 0 && !isStringRecord(candidate.configKeyHashes)) {
2252
2265
  return false;
2253
2266
  }
2267
+ if (candidate.extraInputs !== void 0 && (!Array.isArray(candidate.extraInputs) || !candidate.extraInputs.every((entry) => typeof entry === "string"))) {
2268
+ return false;
2269
+ }
2254
2270
  if (!Array.isArray(candidate.files)) return false;
2255
2271
  return candidate.files.every((entry) => typeof entry === "string");
2256
2272
  }
@@ -2290,26 +2306,35 @@ async function discoverInputFiles(config) {
2290
2306
  const matched = await (0, import_fast_glob2.default)(globs, { cwd, absolute: true, onlyFiles: true });
2291
2307
  return [...new Set(matched)].sort();
2292
2308
  }
2293
- async function computeInputsHash(config) {
2309
+ async function computeInputsHash(config, extraInputs = []) {
2294
2310
  const hash = (0, import_node_crypto.createHash)("sha256");
2295
2311
  hash.update(`version:${VERSION}
2296
2312
  `);
2297
2313
  hash.update(`config:${serializeConfig(config)}
2298
2314
  `);
2299
- const inputFiles = await discoverInputFiles(config);
2300
2315
  const cwd = config.codegen.cwd;
2301
- for (const file of inputFiles) {
2316
+ const globbed = await discoverInputFiles(config);
2317
+ const globbedRelative = new Set(globbed.map((file) => (0, import_node_path13.relative)(cwd, file)));
2318
+ const extra = [...new Set(extraInputs)].filter((file) => !globbedRelative.has(file)).sort();
2319
+ for (const file of globbed) {
2302
2320
  const contents = await (0, import_promises11.readFile)(file, "utf8");
2303
- hash.update(`file:${(0, import_node_path12.relative)(cwd, file)}
2321
+ hash.update(`file:${(0, import_node_path13.relative)(cwd, file)}
2304
2322
  `);
2305
2323
  hash.update(contents);
2306
2324
  hash.update("\n");
2307
2325
  }
2326
+ for (const file of extra) {
2327
+ const contents = await (0, import_promises11.readFile)((0, import_node_path13.join)(cwd, file), "utf8").catch(() => null);
2328
+ hash.update(`extra:${file}
2329
+ `);
2330
+ hash.update(contents ?? "\0missing");
2331
+ hash.update("\n");
2332
+ }
2308
2333
  return hash.digest("hex");
2309
2334
  }
2310
2335
  async function readManifest(outDir) {
2311
2336
  try {
2312
- const raw = await (0, import_promises11.readFile)((0, import_node_path12.join)(outDir, MANIFEST_FILE), "utf8");
2337
+ const raw = await (0, import_promises11.readFile)((0, import_node_path13.join)(outDir, MANIFEST_FILE), "utf8");
2313
2338
  const parsed = JSON.parse(raw);
2314
2339
  if (!isManifestShape(parsed)) return null;
2315
2340
  return {
@@ -2318,6 +2343,7 @@ async function readManifest(outDir) {
2318
2343
  ...parsed.entryPoint ? { entryPoint: parsed.entryPoint } : {},
2319
2344
  ...parsed.configHash ? { configHash: parsed.configHash } : {},
2320
2345
  ...parsed.configKeyHashes ? { configKeyHashes: parsed.configKeyHashes } : {},
2346
+ ...parsed.extraInputs ? { extraInputs: parsed.extraInputs } : {},
2321
2347
  files: parsed.files
2322
2348
  };
2323
2349
  } catch {
@@ -2328,7 +2354,7 @@ function computeConfigHash(config) {
2328
2354
  return (0, import_node_crypto.createHash)("sha256").update(serializeConfig(config)).digest("hex");
2329
2355
  }
2330
2356
  async function writeManifest(outDir, manifest) {
2331
- await (0, import_promises11.writeFile)((0, import_node_path12.join)(outDir, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}
2357
+ await (0, import_promises11.writeFile)((0, import_node_path13.join)(outDir, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}
2332
2358
  `, "utf8");
2333
2359
  }
2334
2360
  async function listOutputFiles(outDir) {
@@ -2336,11 +2362,11 @@ async function listOutputFiles(outDir) {
2336
2362
  async function walk(dir) {
2337
2363
  const entries = await (0, import_promises11.readdir)(dir, { withFileTypes: true }).catch(() => []);
2338
2364
  for (const entry of entries) {
2339
- const abs = (0, import_node_path12.join)(dir, entry.name);
2365
+ const abs = (0, import_node_path13.join)(dir, entry.name);
2340
2366
  if (entry.isDirectory()) {
2341
2367
  await walk(abs);
2342
2368
  } else if (entry.isFile()) {
2343
- const rel = (0, import_node_path12.relative)(outDir, abs);
2369
+ const rel = (0, import_node_path13.relative)(outDir, abs);
2344
2370
  if (rel === MANIFEST_FILE || rel === LOCK_FILE) continue;
2345
2371
  found.push(rel);
2346
2372
  }
@@ -2377,8 +2403,8 @@ function driftGuardMessage(outDir, previousEntryPoint, currentEntryPoint, differ
2377
2403
  }
2378
2404
  async function generate(config, inputRoutes = [], entryPoint = "cli") {
2379
2405
  setCodegenDebug(config.debug);
2380
- const inputsHash = await computeInputsHash(config);
2381
2406
  const manifest = await readManifest(config.codegen.outDir);
2407
+ const inputsHash = await computeInputsHash(config, manifest?.extraInputs ?? []);
2382
2408
  if (await isManifestFresh(config.codegen.outDir, manifest, inputsHash)) {
2383
2409
  console.log(`[nestjs-codegen] ${config.codegen.outDir} up to date, skipped`);
2384
2410
  return;
@@ -2399,7 +2425,8 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
2399
2425
  }
2400
2426
  const extensions = config.extensions ?? [];
2401
2427
  let routes = inputRoutes;
2402
- const ctx = createExtensionContext(config, () => routes);
2428
+ const trackedInputs = /* @__PURE__ */ new Set();
2429
+ const ctx = createExtensionContext(config, () => routes, trackedInputs);
2403
2430
  if (extensions.length > 0) {
2404
2431
  routes = await applyTransformRoutes(routes, extensions, ctx);
2405
2432
  }
@@ -2453,29 +2480,32 @@ async function generate(config, inputRoutes = [], entryPoint = "cli") {
2453
2480
  if (extensions.length > 0) {
2454
2481
  const extraFiles = await collectEmittedFiles(extensions, ctx);
2455
2482
  for (const file of extraFiles) {
2456
- const dest = (0, import_node_path13.join)(config.codegen.outDir, file.path);
2457
- await (0, import_promises12.mkdir)((0, import_node_path13.dirname)(dest), { recursive: true });
2483
+ const dest = (0, import_node_path14.join)(config.codegen.outDir, file.path);
2484
+ await (0, import_promises12.mkdir)((0, import_node_path14.dirname)(dest), { recursive: true });
2458
2485
  await (0, import_promises12.writeFile)(dest, file.contents, "utf8");
2459
2486
  }
2460
2487
  }
2461
2488
  const outputFiles = await listOutputFiles(config.codegen.outDir);
2489
+ const extraInputs = [...trackedInputs].sort();
2490
+ const recordedHash = extraInputs.length > 0 ? await computeInputsHash(config, extraInputs) : inputsHash;
2462
2491
  await writeManifest(config.codegen.outDir, {
2463
2492
  version: VERSION,
2464
- hash: inputsHash,
2493
+ hash: recordedHash,
2465
2494
  entryPoint,
2466
2495
  configHash,
2467
2496
  configKeyHashes,
2468
- files: outputFiles
2497
+ files: outputFiles,
2498
+ ...extraInputs.length > 0 ? { extraInputs } : {}
2469
2499
  });
2470
2500
  }
2471
2501
 
2472
2502
  // src/watch/watcher.ts
2473
2503
  var import_promises15 = require("fs/promises");
2474
- var import_node_path17 = require("path");
2504
+ var import_node_path18 = require("path");
2475
2505
  var import_chokidar = __toESM(require("chokidar"), 1);
2476
2506
 
2477
2507
  // src/discovery/contracts-fast.ts
2478
- var import_node_path15 = require("path");
2508
+ var import_node_path16 = require("path");
2479
2509
  var import_fast_glob3 = __toESM(require("fast-glob"), 1);
2480
2510
  var import_ts_morph10 = require("ts-morph");
2481
2511
 
@@ -2487,7 +2517,7 @@ var import_ts_morph4 = require("ts-morph");
2487
2517
 
2488
2518
  // src/discovery/type-ref-resolution.ts
2489
2519
  var import_node_fs = require("fs");
2490
- var import_node_path14 = require("path");
2520
+ var import_node_path15 = require("path");
2491
2521
  var import_ts_morph3 = require("ts-morph");
2492
2522
  var _EMPTY_CTX = { projectRoot: "", tsconfigPaths: null };
2493
2523
  var _ctxByProject = /* @__PURE__ */ new WeakMap();
@@ -2539,12 +2569,12 @@ function findTypeInFile(name, file) {
2539
2569
  }
2540
2570
  function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
2541
2571
  if (moduleSpecifier.startsWith(".")) {
2542
- const dir = (0, import_node_path14.dirname)(sourceFile.getFilePath());
2572
+ const dir = (0, import_node_path15.dirname)(sourceFile.getFilePath());
2543
2573
  const noExt = moduleSpecifier.replace(/\.(js|ts)$/, "");
2544
2574
  return [
2545
- (0, import_node_path14.resolve)(dir, `${noExt}.ts`),
2546
- (0, import_node_path14.resolve)(dir, `${moduleSpecifier}.ts`),
2547
- (0, import_node_path14.resolve)(dir, moduleSpecifier, "index.ts")
2575
+ (0, import_node_path15.resolve)(dir, `${noExt}.ts`),
2576
+ (0, import_node_path15.resolve)(dir, `${moduleSpecifier}.ts`),
2577
+ (0, import_node_path15.resolve)(dir, moduleSpecifier, "index.ts")
2548
2578
  ];
2549
2579
  }
2550
2580
  const ctx = _ctxFor(project);
@@ -2565,8 +2595,8 @@ function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
2565
2595
  const rest = moduleSpecifier.slice(prefix.length);
2566
2596
  const candidates = [];
2567
2597
  for (const mapping of mappings) {
2568
- const resolved = (0, import_node_path14.resolve)(baseUrl, mapping.replace("*", rest));
2569
- candidates.push(`${resolved}.ts`, (0, import_node_path14.resolve)(resolved, "index.ts"));
2598
+ const resolved = (0, import_node_path15.resolve)(baseUrl, mapping.replace("*", rest));
2599
+ candidates.push(`${resolved}.ts`, (0, import_node_path15.resolve)(resolved, "index.ts"));
2570
2600
  }
2571
2601
  dbg(" resolved candidates:", candidates);
2572
2602
  return candidates;
@@ -4208,6 +4238,19 @@ function resolveParamClass(method, decoratorName, sourceFile, project) {
4208
4238
 
4209
4239
  // src/discovery/heritage.ts
4210
4240
  var import_ts_morph8 = require("ts-morph");
4241
+ function resolveHeritageCall(node) {
4242
+ if (!node) return void 0;
4243
+ const expr = unwrapExpression(node);
4244
+ if (import_ts_morph8.Node.isCallExpression(expr)) return expr;
4245
+ if (!import_ts_morph8.Node.isIdentifier(expr)) return void 0;
4246
+ const decl = expr.getDefinitions().map((d) => d.getDeclarationNode()).find(
4247
+ (n) => n !== void 0 && import_ts_morph8.Node.isVariableDeclaration(n)
4248
+ );
4249
+ const init = decl?.getInitializer();
4250
+ if (!init) return void 0;
4251
+ const unwrapped = unwrapExpression(init);
4252
+ return import_ts_morph8.Node.isCallExpression(unwrapped) ? unwrapped : void 0;
4253
+ }
4211
4254
  function unwrapExpression(node) {
4212
4255
  let current = node;
4213
4256
  while (import_ts_morph8.Node.isAsExpression(current) || import_ts_morph8.Node.isSatisfiesExpression(current) || import_ts_morph8.Node.isParenthesizedExpression(current) || import_ts_morph8.Node.isTypeAssertion(current)) {
@@ -4235,8 +4278,8 @@ function resolveReturnedClass(factoryDecl) {
4235
4278
  return void 0;
4236
4279
  }
4237
4280
  function resolveInheritedMethods(cls) {
4238
- const expr = cls.getExtends()?.getExpression();
4239
- if (!expr || !import_ts_morph8.Node.isCallExpression(expr)) return void 0;
4281
+ const expr = resolveHeritageCall(cls.getExtends()?.getExpression());
4282
+ if (!expr) return void 0;
4240
4283
  const callee = expr.getExpression();
4241
4284
  if (!import_ts_morph8.Node.isIdentifier(callee)) return void 0;
4242
4285
  const factoryDecl = callee.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => import_ts_morph8.Node.isFunctionDeclaration(n));
@@ -4425,7 +4468,7 @@ async function discoverContractsFast(opts) {
4425
4468
  return extractAllRoutes(project);
4426
4469
  }
4427
4470
  function resolveTsconfigPath(cwd, tsconfig) {
4428
- return tsconfig ? (0, import_node_path15.resolve)(tsconfig) : (0, import_node_path15.join)(cwd, "tsconfig.json");
4471
+ return tsconfig ? (0, import_node_path16.resolve)(tsconfig) : (0, import_node_path16.join)(cwd, "tsconfig.json");
4429
4472
  }
4430
4473
  function createDiscoveryProject(tsconfigPath) {
4431
4474
  try {
@@ -4511,7 +4554,7 @@ var PersistentDiscovery = class _PersistentDiscovery {
4511
4554
  async rediscover(changedPaths) {
4512
4555
  if (changedPaths) {
4513
4556
  for (const p of changedPaths) {
4514
- const abs = (0, import_node_path15.resolve)(p);
4557
+ const abs = (0, import_node_path16.resolve)(p);
4515
4558
  const sf = this.project.getSourceFile(abs);
4516
4559
  if (sf) {
4517
4560
  await sf.refreshFromFileSystem();
@@ -4791,9 +4834,11 @@ function extractFromSourceFile(sourceFile, project) {
4791
4834
  factoryFilePath: inherited.factoryFilePath,
4792
4835
  classArgs: inherited.classArgs
4793
4836
  } : void 0;
4837
+ const ownMethods = cls.getMethods();
4838
+ const ownNames = new Set(ownMethods.map((m) => m.getName()));
4794
4839
  const methods = [
4795
- ...cls.getMethods().map((method) => ({ method })),
4796
- ...(inherited?.methods ?? []).map((method) => ({ method, mixin: mixinBinding }))
4840
+ ...ownMethods.map((method) => ({ method, mixin: mixinBinding })),
4841
+ ...(inherited?.methods ?? []).filter((method) => !ownNames.has(method.getName())).map((method) => ({ method, mixin: mixinBinding }))
4797
4842
  ];
4798
4843
  for (const { method, mixin } of methods) {
4799
4844
  const verb = resolveVerb(method);
@@ -4829,7 +4874,7 @@ function extractFromSourceFile(sourceFile, project) {
4829
4874
  // src/watch/lock-file.ts
4830
4875
  var import_promises13 = require("fs/promises");
4831
4876
  var import_promises14 = require("fs/promises");
4832
- var import_node_path16 = require("path");
4877
+ var import_node_path17 = require("path");
4833
4878
  var LOCK_FILE2 = ".watcher.lock";
4834
4879
  function isProcessAlive(pid) {
4835
4880
  try {
@@ -4841,7 +4886,7 @@ function isProcessAlive(pid) {
4841
4886
  }
4842
4887
  async function acquireLock(outDir) {
4843
4888
  await (0, import_promises14.mkdir)(outDir, { recursive: true });
4844
- const lockPath = (0, import_node_path16.join)(outDir, LOCK_FILE2);
4889
+ const lockPath = (0, import_node_path17.join)(outDir, LOCK_FILE2);
4845
4890
  const lockData = { pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
4846
4891
  try {
4847
4892
  const fd = await (0, import_promises13.open)(lockPath, "wx");
@@ -4882,7 +4927,7 @@ async function watch(config, onChange, options = {}) {
4882
4927
  if (lock === null) {
4883
4928
  let holderPid = "unknown";
4884
4929
  try {
4885
- const raw = await (0, import_promises15.readFile)((0, import_node_path17.join)(config.codegen.outDir, ".watcher.lock"), "utf8");
4930
+ const raw = await (0, import_promises15.readFile)((0, import_node_path18.join)(config.codegen.outDir, ".watcher.lock"), "utf8");
4886
4931
  const data = JSON.parse(raw);
4887
4932
  if (data.pid !== void 0) holderPid = String(data.pid);
4888
4933
  } catch {
@@ -4935,7 +4980,7 @@ async function watch(config, onChange, options = {}) {
4935
4980
  }
4936
4981
  let pagesDebounceTimer;
4937
4982
  const pagesGlob = config.pages?.glob ?? ".nestjs-codegen-no-pages";
4938
- const pagesWatcher = import_chokidar.default.watch((0, import_node_path17.join)(config.codegen.cwd, pagesGlob), {
4983
+ const pagesWatcher = import_chokidar.default.watch((0, import_node_path18.join)(config.codegen.cwd, pagesGlob), {
4939
4984
  ignoreInitial: true,
4940
4985
  persistent: true,
4941
4986
  awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
@@ -4962,7 +5007,7 @@ async function watch(config, onChange, options = {}) {
4962
5007
  pagesWatcher.on("unlink", schedulePagesRegenerate);
4963
5008
  let contractsDebounceTimer;
4964
5009
  const pendingChangedPaths = /* @__PURE__ */ new Set();
4965
- const contractsWatcher = import_chokidar.default.watch((0, import_node_path17.join)(config.codegen.cwd, config.contracts.glob), {
5010
+ const contractsWatcher = import_chokidar.default.watch((0, import_node_path18.join)(config.codegen.cwd, config.contracts.glob), {
4966
5011
  ignoreInitial: true,
4967
5012
  persistent: true,
4968
5013
  awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
@@ -4992,7 +5037,7 @@ async function watch(config, onChange, options = {}) {
4992
5037
  contractsWatcher.on("add", (p) => scheduleContractsRegenerate(p));
4993
5038
  contractsWatcher.on("change", (p) => scheduleContractsRegenerate(p));
4994
5039
  contractsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p));
4995
- const formsWatcher = import_chokidar.default.watch((0, import_node_path17.join)(config.codegen.cwd, config.forms.watch), {
5040
+ const formsWatcher = import_chokidar.default.watch((0, import_node_path18.join)(config.codegen.cwd, config.forms.watch), {
4996
5041
  ignoreInitial: true,
4997
5042
  persistent: true,
4998
5043
  awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
@@ -5102,7 +5147,7 @@ function createChainModuleRenderer(opts) {
5102
5147
  }
5103
5148
 
5104
5149
  // src/index.ts
5105
- var VERSION = "0.16.0";
5150
+ var VERSION = "0.17.1";
5106
5151
  // Annotate the CommonJS export names for ESM import in node:
5107
5152
  0 && (module.exports = {
5108
5153
  CodegenError,