@dudousxd/nestjs-codegen 0.20.0 → 0.21.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_path14 = require("path");
254
+ var import_node_path16 = require("path");
255
255
 
256
256
  // src/discovery/pages.ts
257
257
  var import_promises2 = require("fs/promises");
@@ -2241,438 +2241,177 @@ function buildEmpty() {
2241
2241
  // src/generate-manifest.ts
2242
2242
  var import_node_crypto = require("crypto");
2243
2243
  var import_promises11 = require("fs/promises");
2244
- var import_node_path13 = require("path");
2244
+ var import_node_path15 = require("path");
2245
+ var import_fast_glob3 = __toESM(require("fast-glob"), 1);
2246
+
2247
+ // src/discovery/contracts-fast.ts
2248
+ var import_node_fs = require("fs");
2249
+ var import_node_path14 = require("path");
2245
2250
  var import_fast_glob2 = __toESM(require("fast-glob"), 1);
2246
- var MANIFEST_FILE = ".codegen-manifest.json";
2247
- var LOCK_FILE = ".watcher.lock";
2248
- var DriftGuardError = class extends Error {
2249
- constructor(message) {
2250
- super(message);
2251
- this.name = "DriftGuardError";
2252
- }
2253
- };
2254
- function isEntryPoint(value) {
2255
- return value === "cli" || value === "module";
2251
+ var import_ts_morph10 = require("ts-morph");
2252
+
2253
+ // src/discovery/dto-type-resolver.ts
2254
+ var import_ts_morph8 = require("ts-morph");
2255
+
2256
+ // src/discovery/dto-to-ir.ts
2257
+ var import_ts_morph4 = require("ts-morph");
2258
+
2259
+ // src/util/debug-log.ts
2260
+ var debugEnabled = false;
2261
+ function setCodegenDebug(enabled) {
2262
+ debugEnabled = enabled;
2256
2263
  }
2257
- function isManifestShape(value) {
2258
- if (typeof value !== "object" || value === null) return false;
2259
- const candidate = value;
2260
- if (typeof candidate.version !== "string") return false;
2261
- if (typeof candidate.hash !== "string") return false;
2262
- if (candidate.entryPoint !== void 0 && !isEntryPoint(candidate.entryPoint)) return false;
2263
- if (candidate.configHash !== void 0 && typeof candidate.configHash !== "string") return false;
2264
- if (candidate.configKeyHashes !== void 0 && !isStringRecord(candidate.configKeyHashes)) {
2265
- return false;
2266
- }
2267
- if (candidate.extraInputs !== void 0 && (!Array.isArray(candidate.extraInputs) || !candidate.extraInputs.every((entry) => typeof entry === "string"))) {
2268
- return false;
2269
- }
2270
- if (!Array.isArray(candidate.files)) return false;
2271
- return candidate.files.every((entry) => typeof entry === "string");
2264
+ function debugWarn(message) {
2265
+ if (debugEnabled) console.warn(`[nestjs-codegen] ${message}`);
2272
2266
  }
2273
- function isStringRecord(value) {
2274
- if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
2275
- return Object.values(value).every((entry) => typeof entry === "string");
2267
+
2268
+ // src/discovery/type-ref-resolution.ts
2269
+ var import_node_path13 = require("path");
2270
+ var import_ts_morph3 = require("ts-morph");
2271
+ var _EMPTY_CTX = { projectRoot: "", tsconfigPaths: null };
2272
+ var _ctxByProject = /* @__PURE__ */ new WeakMap();
2273
+ function setDiscoveryContext(project, ctx) {
2274
+ _ctxByProject.set(project, ctx);
2276
2275
  }
2277
- function serializeConfig(config) {
2278
- return serializeConfigValue(config, `unserializable:${config.codegen.outDir}`);
2276
+ function _ctxFor(project) {
2277
+ return _ctxByProject.get(project) ?? _EMPTY_CTX;
2279
2278
  }
2280
- function serializeConfigValue(value, unserializableMarker) {
2281
- try {
2282
- return JSON.stringify(value, (_key, entry) => {
2283
- if (typeof entry === "function") return `[fn:${entry.name}]`;
2284
- return entry;
2279
+ var _debug = process.env.NESTJS_INERTIA_DEBUG === "1";
2280
+ function dbg(...args) {
2281
+ if (_debug) console.log("[codegen:debug]", ...args);
2282
+ }
2283
+ function findTypeInFile(name, file) {
2284
+ const cls = file.getClass(name);
2285
+ if (cls) return { kind: "class", decl: cls, file };
2286
+ const iface = file.getInterface(name);
2287
+ if (iface) return { kind: "interface", decl: iface, file };
2288
+ const alias = file.getTypeAlias(name);
2289
+ if (alias) {
2290
+ const typeNode = alias.getTypeNode();
2291
+ return {
2292
+ kind: "typeAlias",
2293
+ typeNode,
2294
+ file,
2295
+ text: typeNode ? typeNode.getText() : "unknown"
2296
+ };
2297
+ }
2298
+ const enumDecl = file.getEnum(name);
2299
+ if (enumDecl) {
2300
+ const members = enumDecl.getMembers().map((m) => {
2301
+ const val = m.getValue();
2302
+ if (typeof val === "string" || typeof val === "number") return JSON.stringify(val);
2303
+ return JSON.stringify(m.getName());
2285
2304
  });
2286
- } catch {
2287
- return unserializableMarker;
2305
+ return { kind: "enum", members };
2288
2306
  }
2307
+ return null;
2289
2308
  }
2290
- function computeConfigKeyHashes(config) {
2291
- const hashes = {};
2292
- for (const [key, value] of Object.entries(config)) {
2293
- if (value === void 0) continue;
2294
- hashes[key] = (0, import_node_crypto.createHash)("sha256").update(serializeConfigValue(value, `unserializable:${key}`)).digest("hex");
2309
+ function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
2310
+ if (moduleSpecifier.startsWith(".")) {
2311
+ const dir = (0, import_node_path13.dirname)(sourceFile.getFilePath());
2312
+ const noExt = moduleSpecifier.replace(/\.(js|ts)$/, "");
2313
+ return [
2314
+ (0, import_node_path13.resolve)(dir, `${noExt}.ts`),
2315
+ (0, import_node_path13.resolve)(dir, `${moduleSpecifier}.ts`),
2316
+ (0, import_node_path13.resolve)(dir, moduleSpecifier, "index.ts")
2317
+ ];
2295
2318
  }
2296
- return hashes;
2297
- }
2298
- function diffConfigKeyHashes(previous, current) {
2299
- const keys = /* @__PURE__ */ new Set([...Object.keys(previous), ...Object.keys(current)]);
2300
- return [...keys].filter((key) => previous[key] !== current[key]).sort();
2301
- }
2302
- async function discoverInputFiles(config) {
2303
- const globs = [config.contracts.glob, config.forms.watch];
2304
- if (config.pages) globs.push(config.pages.glob);
2305
- const cwd = config.codegen.cwd;
2306
- const matched = await (0, import_fast_glob2.default)(globs, { cwd, absolute: true, onlyFiles: true });
2307
- return [...new Set(matched)].sort();
2308
- }
2309
- async function computeInputsHash(config, extraInputs = []) {
2310
- const hash = (0, import_node_crypto.createHash)("sha256");
2311
- hash.update(`version:${VERSION}
2312
- `);
2313
- hash.update(`config:${serializeConfig(config)}
2314
- `);
2315
- const cwd = config.codegen.cwd;
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) {
2320
- const contents = await (0, import_promises11.readFile)(file, "utf8");
2321
- hash.update(`file:${(0, import_node_path13.relative)(cwd, file)}
2322
- `);
2323
- hash.update(contents);
2324
- hash.update("\n");
2319
+ const ctx = _ctxFor(project);
2320
+ const baseUrl = ctx.pathsBase ?? ctx.projectRoot;
2321
+ const tsconfigPaths = ctx.tsconfigPaths;
2322
+ dbg(
2323
+ "resolveModuleSpecifier",
2324
+ moduleSpecifier,
2325
+ "paths:",
2326
+ JSON.stringify(tsconfigPaths),
2327
+ "baseUrl:",
2328
+ baseUrl
2329
+ );
2330
+ if (tsconfigPaths) {
2331
+ for (const [pattern, mappings] of Object.entries(tsconfigPaths)) {
2332
+ const prefix = pattern.replace("*", "");
2333
+ if (moduleSpecifier.startsWith(prefix)) {
2334
+ const rest = moduleSpecifier.slice(prefix.length);
2335
+ const candidates = [];
2336
+ for (const mapping of mappings) {
2337
+ const resolved = (0, import_node_path13.resolve)(baseUrl, mapping.replace("*", rest));
2338
+ candidates.push(`${resolved}.ts`, (0, import_node_path13.resolve)(resolved, "index.ts"));
2339
+ }
2340
+ dbg(" resolved candidates:", candidates);
2341
+ return candidates;
2342
+ }
2343
+ }
2325
2344
  }
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");
2345
+ return [];
2346
+ }
2347
+ function resolveImportedType(name, sourceFile, project) {
2348
+ for (const importDecl of sourceFile.getImportDeclarations()) {
2349
+ const namedImport = importDecl.getNamedImports().find((n) => n.getName() === name);
2350
+ if (!namedImport) continue;
2351
+ const moduleSpecifier = importDecl.getModuleSpecifierValue();
2352
+ const candidates = resolveModuleSpecifier(moduleSpecifier, sourceFile, project);
2353
+ for (const candidate of candidates) {
2354
+ let importedFile = project.getSourceFile(candidate);
2355
+ if (!importedFile) {
2356
+ try {
2357
+ importedFile = project.addSourceFileAtPath(candidate);
2358
+ } catch {
2359
+ continue;
2360
+ }
2361
+ }
2362
+ const result = findTypeInFile(name, importedFile);
2363
+ if (result) return result;
2364
+ const viaReExport = resolveReExportedType(name, importedFile, project, /* @__PURE__ */ new Set());
2365
+ if (viaReExport) return viaReExport;
2366
+ }
2367
+ if (candidates.length === 0) {
2368
+ const viaCompiler = resolveBareSpecifierType(name, importDecl, project);
2369
+ if (viaCompiler) return viaCompiler;
2370
+ }
2332
2371
  }
2333
- return hash.digest("hex");
2372
+ return resolveReExportedType(name, sourceFile, project, /* @__PURE__ */ new Set());
2334
2373
  }
2335
- async function readManifest(outDir) {
2374
+ function resolveBareSpecifierType(name, importDecl, project) {
2375
+ let target;
2336
2376
  try {
2337
- const raw = await (0, import_promises11.readFile)((0, import_node_path13.join)(outDir, MANIFEST_FILE), "utf8");
2338
- const parsed = JSON.parse(raw);
2339
- if (!isManifestShape(parsed)) return null;
2340
- return {
2341
- version: parsed.version,
2342
- hash: parsed.hash,
2343
- ...parsed.entryPoint ? { entryPoint: parsed.entryPoint } : {},
2344
- ...parsed.configHash ? { configHash: parsed.configHash } : {},
2345
- ...parsed.configKeyHashes ? { configKeyHashes: parsed.configKeyHashes } : {},
2346
- ...parsed.extraInputs ? { extraInputs: parsed.extraInputs } : {},
2347
- files: parsed.files
2348
- };
2377
+ target = importDecl.getModuleSpecifierSourceFile();
2349
2378
  } catch {
2350
2379
  return null;
2351
2380
  }
2381
+ if (!target) return null;
2382
+ const direct = findTypeInFile(name, target);
2383
+ if (direct) return direct;
2384
+ return resolveReExportedType(name, target, project, /* @__PURE__ */ new Set());
2352
2385
  }
2353
- function computeConfigHash(config) {
2354
- return (0, import_node_crypto.createHash)("sha256").update(serializeConfig(config)).digest("hex");
2355
- }
2356
- async function writeManifest(outDir, manifest) {
2357
- await (0, import_promises11.writeFile)((0, import_node_path13.join)(outDir, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}
2358
- `, "utf8");
2359
- }
2360
- async function listOutputFiles(outDir) {
2361
- const found = [];
2362
- async function walk(dir) {
2363
- const entries = await (0, import_promises11.readdir)(dir, { withFileTypes: true }).catch(() => []);
2364
- for (const entry of entries) {
2365
- const abs = (0, import_node_path13.join)(dir, entry.name);
2366
- if (entry.isDirectory()) {
2367
- await walk(abs);
2368
- } else if (entry.isFile()) {
2369
- const rel = (0, import_node_path13.relative)(outDir, abs);
2370
- if (rel === MANIFEST_FILE || rel === LOCK_FILE) continue;
2371
- found.push(rel);
2372
- }
2386
+ function resolveReExportedType(name, file, project, seen) {
2387
+ const filePath = file.getFilePath();
2388
+ if (seen.has(filePath)) return null;
2389
+ seen.add(filePath);
2390
+ for (const exportDecl of file.getExportDeclarations()) {
2391
+ const moduleSpecifier = exportDecl.getModuleSpecifierValue();
2392
+ const namedExports = exportDecl.getNamedExports();
2393
+ if (moduleSpecifier) {
2394
+ const hasStar = namedExports.length === 0;
2395
+ const reExportsName2 = namedExports.some(
2396
+ (n) => (n.getAliasNode()?.getText() ?? n.getName()) === name
2397
+ );
2398
+ if (!hasStar && !reExportsName2) continue;
2399
+ const sourceName2 = hasStar ? name : namedExports.find((n) => (n.getAliasNode()?.getText() ?? n.getName()) === name)?.getName() ?? name;
2400
+ const target = followModuleForType(sourceName2, moduleSpecifier, file, project, seen);
2401
+ if (target) return target;
2402
+ continue;
2373
2403
  }
2404
+ const reExportsName = namedExports.some(
2405
+ (n) => (n.getAliasNode()?.getText() ?? n.getName()) === name
2406
+ );
2407
+ if (!reExportsName) continue;
2408
+ const sourceName = namedExports.find((n) => (n.getAliasNode()?.getText() ?? n.getName()) === name)?.getName() ?? name;
2409
+ const local = findTypeInFile(sourceName, file);
2410
+ if (local) return local;
2411
+ const imported = resolveImportedType(sourceName, file, project);
2412
+ if (imported) return imported;
2374
2413
  }
2375
- await walk(outDir);
2376
- return found.sort();
2377
- }
2378
- async function allOutputsExist(outDir, files) {
2379
- const present = new Set(await listOutputFiles(outDir));
2380
- return files.every((file) => present.has(file));
2381
- }
2382
- async function isManifestFresh(outDir, manifest, inputsHash) {
2383
- if (manifest === null) return false;
2384
- if (manifest.version !== VERSION) return false;
2385
- if (manifest.hash !== inputsHash) return false;
2386
- if (manifest.files.length === 0) return false;
2387
- return allOutputsExist(outDir, manifest.files);
2388
- }
2389
-
2390
- // src/util/debug-log.ts
2391
- var debugEnabled = false;
2392
- function setCodegenDebug(enabled) {
2393
- debugEnabled = enabled;
2394
- }
2395
- function debugWarn(message) {
2396
- if (debugEnabled) console.warn(`[nestjs-codegen] ${message}`);
2397
- }
2398
-
2399
- // src/generate.ts
2400
- function driftGuardMessage(outDir, previousEntryPoint, currentEntryPoint, differingKeys) {
2401
- const differ = differingKeys.length > 0 ? `their resolved configs differ at: ${differingKeys.map((key) => `\`${key}\``).join(", ")}` : "their resolved configs differ (re-run after this generate records per-key hashes to see which keys)";
2402
- return `[nestjs-codegen] Config drift detected in "${outDir}": the last generate ran from the "${previousEntryPoint}" entry point, this run is from the "${currentEntryPoint}" entry point, and ${differ}. Both entry points must read the SAME config \u2014 export a shared config object (e.g. codegen.config.ts) and import it from BOTH nestjs-codegen.config.ts (CLI) and NestjsCodegenModule.forRoot() (Nest module), or set \`driftGuard: false\` on either config to opt out of this check.`;
2403
- }
2404
- async function generate(config, inputRoutes = [], entryPoint = "cli") {
2405
- setCodegenDebug(config.debug);
2406
- const manifest = await readManifest(config.codegen.outDir);
2407
- const inputsHash = await computeInputsHash(config, manifest?.extraInputs ?? []);
2408
- if (await isManifestFresh(config.codegen.outDir, manifest, inputsHash)) {
2409
- console.log(`[nestjs-codegen] ${config.codegen.outDir} up to date, skipped`);
2410
- return;
2411
- }
2412
- const configHash = computeConfigHash(config);
2413
- const configKeyHashes = computeConfigKeyHashes(config);
2414
- if (config.driftGuard && manifest?.entryPoint && manifest.entryPoint !== entryPoint && manifest.configHash && manifest.configHash !== configHash) {
2415
- throw new DriftGuardError(
2416
- driftGuardMessage(
2417
- config.codegen.outDir,
2418
- manifest.entryPoint,
2419
- entryPoint,
2420
- // A pre-key-hash manifest can't tell us WHICH keys differ — pass none
2421
- // rather than diffing against {} (which would name every key).
2422
- manifest.configKeyHashes ? diffConfigKeyHashes(manifest.configKeyHashes, configKeyHashes) : []
2423
- )
2424
- );
2425
- }
2426
- const extensions = config.extensions ?? [];
2427
- let routes = inputRoutes;
2428
- const trackedInputs = /* @__PURE__ */ new Set();
2429
- const ctx = createExtensionContext(config, () => routes, trackedInputs);
2430
- if (extensions.length > 0) {
2431
- routes = await applyTransformRoutes(routes, extensions, ctx);
2432
- }
2433
- if (config.pages) {
2434
- const pagesConfig = config.pages;
2435
- const pages = await discoverPages({
2436
- glob: pagesConfig.glob,
2437
- cwd: config.codegen.cwd,
2438
- propsExport: pagesConfig.propsExport,
2439
- componentNameStrategy: pagesConfig.componentNameStrategy
2440
- });
2441
- const sharedProps = discoverSharedPropsFromConfig(config);
2442
- await emitPages(pages, config.codegen.outDir, {
2443
- propsExport: pagesConfig.propsExport,
2444
- sharedProps
2445
- });
2446
- await emitCache(pages, config.codegen.outDir);
2447
- }
2448
- const hasRoutes = routes.length > 0;
2449
- const hasContracts = routes.some((r) => r.contract);
2450
- if (hasRoutes) {
2451
- await emitRoutes(routes, config.codegen.outDir);
2452
- }
2453
- if (hasContracts) {
2454
- await emitApi(routes, config.codegen.outDir, {
2455
- ...config.fetcher?.importPath ? { fetcherImportPath: config.fetcher.importPath } : {},
2456
- serialization: config.serialization,
2457
- extensions,
2458
- ctx
2459
- });
2460
- }
2461
- const hasForms = await emitForms(routes, config.codegen.outDir, config.forms, config.validation);
2462
- if (hasContracts && config.openapi.enabled) {
2463
- await emitOpenApi(routes, config.codegen.outDir, {
2464
- fileName: config.openapi.fileName,
2465
- info: {
2466
- title: config.openapi.title,
2467
- version: config.openapi.version,
2468
- ...config.openapi.description ? { description: config.openapi.description } : {}
2469
- }
2470
- });
2471
- }
2472
- if (hasContracts && config.mocks.enabled) {
2473
- await emitMocks(routes, config.codegen.outDir, {
2474
- fileName: config.mocks.fileName,
2475
- seed: config.mocks.seed,
2476
- baseUrl: config.mocks.baseUrl
2477
- });
2478
- }
2479
- await emitIndex(config.codegen.outDir, hasContracts, hasForms);
2480
- if (extensions.length > 0) {
2481
- const extraFiles = await collectEmittedFiles(extensions, ctx);
2482
- for (const file of extraFiles) {
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 });
2485
- await (0, import_promises12.writeFile)(dest, file.contents, "utf8");
2486
- }
2487
- }
2488
- const outputFiles = await listOutputFiles(config.codegen.outDir);
2489
- const extraInputs = [...trackedInputs].sort();
2490
- const recordedHash = extraInputs.length > 0 ? await computeInputsHash(config, extraInputs) : inputsHash;
2491
- await writeManifest(config.codegen.outDir, {
2492
- version: VERSION,
2493
- hash: recordedHash,
2494
- entryPoint,
2495
- configHash,
2496
- configKeyHashes,
2497
- files: outputFiles,
2498
- ...extraInputs.length > 0 ? { extraInputs } : {}
2499
- });
2500
- }
2501
-
2502
- // src/watch/watcher.ts
2503
- var import_promises15 = require("fs/promises");
2504
- var import_node_path18 = require("path");
2505
- var import_chokidar = __toESM(require("chokidar"), 1);
2506
-
2507
- // src/discovery/contracts-fast.ts
2508
- var import_node_path16 = require("path");
2509
- var import_fast_glob3 = __toESM(require("fast-glob"), 1);
2510
- var import_ts_morph10 = require("ts-morph");
2511
-
2512
- // src/discovery/dto-type-resolver.ts
2513
- var import_ts_morph8 = require("ts-morph");
2514
-
2515
- // src/discovery/dto-to-ir.ts
2516
- var import_ts_morph4 = require("ts-morph");
2517
-
2518
- // src/discovery/type-ref-resolution.ts
2519
- var import_node_fs = require("fs");
2520
- var import_node_path15 = require("path");
2521
- var import_ts_morph3 = require("ts-morph");
2522
- var _EMPTY_CTX = { projectRoot: "", tsconfigPaths: null };
2523
- var _ctxByProject = /* @__PURE__ */ new WeakMap();
2524
- function setDiscoveryContext(project, ctx) {
2525
- _ctxByProject.set(project, ctx);
2526
- }
2527
- function _ctxFor(project) {
2528
- return _ctxByProject.get(project) ?? _EMPTY_CTX;
2529
- }
2530
- var _debug = process.env.NESTJS_INERTIA_DEBUG === "1";
2531
- function dbg(...args) {
2532
- if (_debug) console.log("[codegen:debug]", ...args);
2533
- }
2534
- function loadTsconfigPaths(tsconfigPath) {
2535
- try {
2536
- const raw = (0, import_node_fs.readFileSync)(tsconfigPath, "utf8");
2537
- const stripped = raw.replace(/\/\/.*$/gm, "");
2538
- const parsed = JSON.parse(stripped);
2539
- return parsed.compilerOptions?.paths ?? null;
2540
- } catch {
2541
- return null;
2542
- }
2543
- }
2544
- function findTypeInFile(name, file) {
2545
- const cls = file.getClass(name);
2546
- if (cls) return { kind: "class", decl: cls, file };
2547
- const iface = file.getInterface(name);
2548
- if (iface) return { kind: "interface", decl: iface, file };
2549
- const alias = file.getTypeAlias(name);
2550
- if (alias) {
2551
- const typeNode = alias.getTypeNode();
2552
- return {
2553
- kind: "typeAlias",
2554
- typeNode,
2555
- file,
2556
- text: typeNode ? typeNode.getText() : "unknown"
2557
- };
2558
- }
2559
- const enumDecl = file.getEnum(name);
2560
- if (enumDecl) {
2561
- const members = enumDecl.getMembers().map((m) => {
2562
- const val = m.getValue();
2563
- if (typeof val === "string" || typeof val === "number") return JSON.stringify(val);
2564
- return JSON.stringify(m.getName());
2565
- });
2566
- return { kind: "enum", members };
2567
- }
2568
- return null;
2569
- }
2570
- function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
2571
- if (moduleSpecifier.startsWith(".")) {
2572
- const dir = (0, import_node_path15.dirname)(sourceFile.getFilePath());
2573
- const noExt = moduleSpecifier.replace(/\.(js|ts)$/, "");
2574
- return [
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")
2578
- ];
2579
- }
2580
- const ctx = _ctxFor(project);
2581
- const baseUrl = ctx.projectRoot;
2582
- const tsconfigPaths = ctx.tsconfigPaths;
2583
- dbg(
2584
- "resolveModuleSpecifier",
2585
- moduleSpecifier,
2586
- "paths:",
2587
- JSON.stringify(tsconfigPaths),
2588
- "baseUrl:",
2589
- baseUrl
2590
- );
2591
- if (tsconfigPaths) {
2592
- for (const [pattern, mappings] of Object.entries(tsconfigPaths)) {
2593
- const prefix = pattern.replace("*", "");
2594
- if (moduleSpecifier.startsWith(prefix)) {
2595
- const rest = moduleSpecifier.slice(prefix.length);
2596
- const candidates = [];
2597
- for (const mapping of mappings) {
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"));
2600
- }
2601
- dbg(" resolved candidates:", candidates);
2602
- return candidates;
2603
- }
2604
- }
2605
- }
2606
- return [];
2607
- }
2608
- function resolveImportedType(name, sourceFile, project) {
2609
- for (const importDecl of sourceFile.getImportDeclarations()) {
2610
- const namedImport = importDecl.getNamedImports().find((n) => n.getName() === name);
2611
- if (!namedImport) continue;
2612
- const moduleSpecifier = importDecl.getModuleSpecifierValue();
2613
- const candidates = resolveModuleSpecifier(moduleSpecifier, sourceFile, project);
2614
- for (const candidate of candidates) {
2615
- let importedFile = project.getSourceFile(candidate);
2616
- if (!importedFile) {
2617
- try {
2618
- importedFile = project.addSourceFileAtPath(candidate);
2619
- } catch {
2620
- continue;
2621
- }
2622
- }
2623
- const result = findTypeInFile(name, importedFile);
2624
- if (result) return result;
2625
- const viaReExport = resolveReExportedType(name, importedFile, project, /* @__PURE__ */ new Set());
2626
- if (viaReExport) return viaReExport;
2627
- }
2628
- if (candidates.length === 0) {
2629
- const viaCompiler = resolveBareSpecifierType(name, importDecl, project);
2630
- if (viaCompiler) return viaCompiler;
2631
- }
2632
- }
2633
- return resolveReExportedType(name, sourceFile, project, /* @__PURE__ */ new Set());
2634
- }
2635
- function resolveBareSpecifierType(name, importDecl, project) {
2636
- let target;
2637
- try {
2638
- target = importDecl.getModuleSpecifierSourceFile();
2639
- } catch {
2640
- return null;
2641
- }
2642
- if (!target) return null;
2643
- const direct = findTypeInFile(name, target);
2644
- if (direct) return direct;
2645
- return resolveReExportedType(name, target, project, /* @__PURE__ */ new Set());
2646
- }
2647
- function resolveReExportedType(name, file, project, seen) {
2648
- const filePath = file.getFilePath();
2649
- if (seen.has(filePath)) return null;
2650
- seen.add(filePath);
2651
- for (const exportDecl of file.getExportDeclarations()) {
2652
- const moduleSpecifier = exportDecl.getModuleSpecifierValue();
2653
- const namedExports = exportDecl.getNamedExports();
2654
- if (moduleSpecifier) {
2655
- const hasStar = namedExports.length === 0;
2656
- const reExportsName2 = namedExports.some(
2657
- (n) => (n.getAliasNode()?.getText() ?? n.getName()) === name
2658
- );
2659
- if (!hasStar && !reExportsName2) continue;
2660
- const sourceName2 = hasStar ? name : namedExports.find((n) => (n.getAliasNode()?.getText() ?? n.getName()) === name)?.getName() ?? name;
2661
- const target = followModuleForType(sourceName2, moduleSpecifier, file, project, seen);
2662
- if (target) return target;
2663
- continue;
2664
- }
2665
- const reExportsName = namedExports.some(
2666
- (n) => (n.getAliasNode()?.getText() ?? n.getName()) === name
2667
- );
2668
- if (!reExportsName) continue;
2669
- const sourceName = namedExports.find((n) => (n.getAliasNode()?.getText() ?? n.getName()) === name)?.getName() ?? name;
2670
- const local = findTypeInFile(sourceName, file);
2671
- if (local) return local;
2672
- const imported = resolveImportedType(sourceName, file, project);
2673
- if (imported) return imported;
2674
- }
2675
- return null;
2414
+ return null;
2676
2415
  }
2677
2416
  function followModuleForType(name, moduleSpecifier, fromFile, project, seen) {
2678
2417
  const candidates = resolveModuleSpecifier(moduleSpecifier, fromFile, project);
@@ -3771,7 +3510,7 @@ function extractFilterForHints(classDecl, project) {
3771
3510
  }
3772
3511
  return hints;
3773
3512
  }
3774
- function extractApplyFilterInfo(method, sourceFile, project, mixin) {
3513
+ function extractApplyFilterInfo(method, sourceFile, project, mixin, controllerClass) {
3775
3514
  const declFile = method.getSourceFile();
3776
3515
  const mixinEntity = resolveMixinEntityClass(mixin, project);
3777
3516
  for (const param of method.getParameters()) {
@@ -3797,7 +3536,7 @@ function extractApplyFilterInfo(method, sourceFile, project, mixin) {
3797
3536
  const filterClassName = filterClassArg.getText();
3798
3537
  const resolved = factoryStaticClass ? void 0 : findType(filterClassName, declFile, project);
3799
3538
  const declaredClass = factoryStaticClass ?? (resolved?.kind === "class" ? resolved.decl : resolveLocalClassDeclaration(filterClassArg));
3800
- const ownRoute = !mixin || declFile.getFilePath() !== mixin.factoryFilePath;
3539
+ const ownRoute = !mixin || (controllerClass ? method.getParent() === controllerClass : declFile.getFilePath() !== mixin.factoryFilePath);
3801
3540
  const namedByRoute = ownRoute && import_ts_morph7.Node.isIdentifier(filterClassArg) ? declaredClass : void 0;
3802
3541
  for (const candidate of orderFilterCandidates({
3803
3542
  namedByRoute,
@@ -4388,9 +4127,9 @@ function unwrapNamedContainer(node, names) {
4388
4127
  }
4389
4128
  return node;
4390
4129
  }
4391
- function extractDtoContract(method, sourceFile, project, mixin) {
4130
+ function extractDtoContract(method, sourceFile, project, mixin, controllerClass) {
4392
4131
  let body = extractBodyType(method, sourceFile, project);
4393
- const filterInfo = extractApplyFilterInfo(method, sourceFile, project, mixin);
4132
+ const filterInfo = extractApplyFilterInfo(method, sourceFile, project, mixin, controllerClass);
4394
4133
  const query = extractQueryType(method, sourceFile, project);
4395
4134
  const uploads = extractUploadedFiles(method);
4396
4135
  const multipartBody = uploads.fields ? `{ ${uploads.fields} }` : null;
@@ -4627,43 +4366,80 @@ function parseDefineContractCall(callExpr) {
4627
4366
  async function discoverContractsFast(opts) {
4628
4367
  const { cwd, glob, tsconfig } = opts;
4629
4368
  const tsconfigPath = resolveTsconfigPath(cwd, tsconfig);
4630
- const project = createDiscoveryProject(tsconfigPath);
4631
- const files = await (0, import_fast_glob3.default)(glob, { cwd, absolute: true, onlyFiles: true });
4369
+ const loaded = loadDiscoveryTsconfig(tsconfigPath);
4370
+ const project = createDiscoveryProject(tsconfigPath, loaded);
4371
+ const files = await (0, import_fast_glob2.default)(glob, { cwd, absolute: true, onlyFiles: true });
4632
4372
  for (const f of files) {
4633
4373
  project.addSourceFileAtPath(f);
4634
4374
  }
4635
- bindDiscoveryContext(project, cwd, tsconfigPath);
4375
+ bindDiscoveryContext(project, cwd, tsconfigPath, loaded);
4636
4376
  clearMixinTypeProject();
4637
4377
  return extractAllRoutes(project);
4638
4378
  }
4639
- function resolveTsconfigPath(cwd, tsconfig) {
4640
- return tsconfig ? (0, import_node_path16.resolve)(tsconfig) : (0, import_node_path16.join)(cwd, "tsconfig.json");
4379
+ function resolveTsconfigPath(cwd, tsconfig) {
4380
+ return tsconfig ? (0, import_node_path14.resolve)(tsconfig) : (0, import_node_path14.join)(cwd, "tsconfig.json");
4381
+ }
4382
+ var NO_TSCONFIG_OPTIONS = {
4383
+ allowJs: true,
4384
+ resolveJsonModule: false,
4385
+ strict: false
4386
+ };
4387
+ function loadDiscoveryTsconfig(tsconfigPath) {
4388
+ if (!(0, import_node_fs.existsSync)(tsconfigPath)) return { options: null, files: [] };
4389
+ const read = import_ts_morph10.ts.readConfigFile(tsconfigPath, (path) => import_ts_morph10.ts.sys.readFile(path));
4390
+ if (read.error) {
4391
+ return {
4392
+ options: null,
4393
+ files: [tsconfigPath],
4394
+ error: import_ts_morph10.ts.flattenDiagnosticMessageText(read.error.messageText, " ")
4395
+ };
4396
+ }
4397
+ const host = {
4398
+ useCaseSensitiveFileNames: import_ts_morph10.ts.sys.useCaseSensitiveFileNames,
4399
+ // The one line that keeps a tsconfig from being a filesystem walk.
4400
+ readDirectory: () => [],
4401
+ fileExists: (path) => import_ts_morph10.ts.sys.fileExists(path),
4402
+ // `extends` is resolved through the host, so a consumer whose `paths` live
4403
+ // in a base tsconfig keeps working.
4404
+ readFile: (path) => import_ts_morph10.ts.sys.readFile(path)
4405
+ };
4406
+ const sourceFile = import_ts_morph10.ts.readJsonConfigFile(tsconfigPath, (path) => import_ts_morph10.ts.sys.readFile(path));
4407
+ const parsed = import_ts_morph10.ts.parseJsonSourceFileConfigFileContent(
4408
+ sourceFile,
4409
+ host,
4410
+ (0, import_node_path14.dirname)(tsconfigPath),
4411
+ void 0,
4412
+ tsconfigPath
4413
+ );
4414
+ return {
4415
+ options: parsed.options,
4416
+ files: [tsconfigPath, ...sourceFile.extendedSourceFiles ?? []]
4417
+ };
4418
+ }
4419
+ function pathsBaseDir(options, fallback) {
4420
+ if (typeof options.baseUrl === "string") return options.baseUrl;
4421
+ if (typeof options.pathsBasePath === "string") return options.pathsBasePath;
4422
+ return fallback;
4641
4423
  }
4642
- function createDiscoveryProject(tsconfigPath) {
4643
- try {
4644
- return new import_ts_morph10.Project({
4645
- tsConfigFilePath: tsconfigPath,
4646
- skipAddingFilesFromTsConfig: true,
4647
- skipLoadingLibFiles: true,
4648
- skipFileDependencyResolution: true
4649
- });
4650
- } catch {
4651
- return new import_ts_morph10.Project({
4652
- skipAddingFilesFromTsConfig: true,
4653
- skipLoadingLibFiles: true,
4654
- skipFileDependencyResolution: true,
4655
- compilerOptions: {
4656
- allowJs: true,
4657
- resolveJsonModule: false,
4658
- strict: false
4659
- }
4660
- });
4424
+ function createDiscoveryProject(tsconfigPath, tsconfig = loadDiscoveryTsconfig(tsconfigPath)) {
4425
+ if (tsconfig.error) {
4426
+ console.warn(
4427
+ `[nestjs-codegen/fast] Could not load ${tsconfigPath}: ${tsconfig.error} \u2014 continuing WITHOUT its compiler options, so path alias imports (e.g. '@/...') will not resolve and controllers extending an alias-imported factory will contribute NO routes to the generated client.`
4428
+ );
4661
4429
  }
4430
+ return new import_ts_morph10.Project({
4431
+ compilerOptions: tsconfig.options ?? { ...NO_TSCONFIG_OPTIONS },
4432
+ skipAddingFilesFromTsConfig: true,
4433
+ skipLoadingLibFiles: true,
4434
+ skipFileDependencyResolution: true
4435
+ });
4662
4436
  }
4663
- function bindDiscoveryContext(project, cwd, tsconfigPath) {
4437
+ function bindDiscoveryContext(project, cwd, tsconfigPath, tsconfig = loadDiscoveryTsconfig(tsconfigPath)) {
4438
+ const { options } = tsconfig;
4664
4439
  setDiscoveryContext(project, {
4665
4440
  projectRoot: cwd,
4666
- tsconfigPaths: loadTsconfigPaths(tsconfigPath)
4441
+ tsconfigPaths: options?.paths ?? null,
4442
+ pathsBase: options ? pathsBaseDir(options, cwd) : cwd
4667
4443
  });
4668
4444
  }
4669
4445
  function extractRoutesFrom(project, controllerPaths) {
@@ -4699,10 +4475,11 @@ var PersistentDiscovery = class _PersistentDiscovery {
4699
4475
  static async create(opts) {
4700
4476
  const { cwd, glob, tsconfig } = opts;
4701
4477
  const tsconfigPath = resolveTsconfigPath(cwd, tsconfig);
4702
- const project = createDiscoveryProject(tsconfigPath);
4703
- bindDiscoveryContext(project, cwd, tsconfigPath);
4478
+ const loaded = loadDiscoveryTsconfig(tsconfigPath);
4479
+ const project = createDiscoveryProject(tsconfigPath, loaded);
4480
+ bindDiscoveryContext(project, cwd, tsconfigPath, loaded);
4704
4481
  const instance = new _PersistentDiscovery(project, cwd, glob);
4705
- const files = await (0, import_fast_glob3.default)(glob, { cwd, absolute: true, onlyFiles: true });
4482
+ const files = await (0, import_fast_glob2.default)(glob, { cwd, absolute: true, onlyFiles: true });
4706
4483
  for (const f of files) {
4707
4484
  project.addSourceFileAtPath(f);
4708
4485
  instance.controllerPaths.add(f);
@@ -4723,7 +4500,7 @@ var PersistentDiscovery = class _PersistentDiscovery {
4723
4500
  async rediscover(changedPaths) {
4724
4501
  if (changedPaths) {
4725
4502
  for (const p of changedPaths) {
4726
- const abs = (0, import_node_path16.resolve)(p);
4503
+ const abs = (0, import_node_path14.resolve)(p);
4727
4504
  const sf = this.project.getSourceFile(abs);
4728
4505
  if (sf) {
4729
4506
  await sf.refreshFromFileSystem();
@@ -4731,7 +4508,7 @@ var PersistentDiscovery = class _PersistentDiscovery {
4731
4508
  }
4732
4509
  }
4733
4510
  const globbed = new Set(
4734
- await (0, import_fast_glob3.default)(this.glob, { cwd: this.cwd, absolute: true, onlyFiles: true })
4511
+ await (0, import_fast_glob2.default)(this.glob, { cwd: this.cwd, absolute: true, onlyFiles: true })
4735
4512
  );
4736
4513
  for (const f of globbed) {
4737
4514
  if (!this.controllerPaths.has(f)) {
@@ -4953,7 +4730,7 @@ function extractDtoRoute(args) {
4953
4730
  const methodName = method.getName();
4954
4731
  const classAs = readAsDecorator(cls, `class ${className}`);
4955
4732
  const methodAs = readAsDecorator(method, `${className}.${methodName}`);
4956
- const dtoContract = extractDtoContract(method, sourceFile, project, mixin);
4733
+ const dtoContract = extractDtoContract(method, sourceFile, project, mixin, cls);
4957
4734
  const mixinResponse = mixin ? resolveInstantiatedReturnType(cls, methodName) : void 0;
4958
4735
  return buildRoute({
4959
4736
  className,
@@ -5041,6 +4818,268 @@ function extractFromSourceFile(sourceFile, project) {
5041
4818
  return routes;
5042
4819
  }
5043
4820
 
4821
+ // src/generate-manifest.ts
4822
+ var MANIFEST_FILE = ".codegen-manifest.json";
4823
+ var LOCK_FILE = ".watcher.lock";
4824
+ var DriftGuardError = class extends Error {
4825
+ constructor(message) {
4826
+ super(message);
4827
+ this.name = "DriftGuardError";
4828
+ }
4829
+ };
4830
+ function isEntryPoint(value) {
4831
+ return value === "cli" || value === "module";
4832
+ }
4833
+ function isManifestShape(value) {
4834
+ if (typeof value !== "object" || value === null) return false;
4835
+ const candidate = value;
4836
+ if (typeof candidate.version !== "string") return false;
4837
+ if (typeof candidate.hash !== "string") return false;
4838
+ if (candidate.entryPoint !== void 0 && !isEntryPoint(candidate.entryPoint)) return false;
4839
+ if (candidate.configHash !== void 0 && typeof candidate.configHash !== "string") return false;
4840
+ if (candidate.configKeyHashes !== void 0 && !isStringRecord(candidate.configKeyHashes)) {
4841
+ return false;
4842
+ }
4843
+ if (candidate.extraInputs !== void 0 && (!Array.isArray(candidate.extraInputs) || !candidate.extraInputs.every((entry) => typeof entry === "string"))) {
4844
+ return false;
4845
+ }
4846
+ if (!Array.isArray(candidate.files)) return false;
4847
+ return candidate.files.every((entry) => typeof entry === "string");
4848
+ }
4849
+ function isStringRecord(value) {
4850
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
4851
+ return Object.values(value).every((entry) => typeof entry === "string");
4852
+ }
4853
+ function serializeConfig(config) {
4854
+ return serializeConfigValue(config, `unserializable:${config.codegen.outDir}`);
4855
+ }
4856
+ function serializeConfigValue(value, unserializableMarker) {
4857
+ try {
4858
+ return JSON.stringify(value, (_key, entry) => {
4859
+ if (typeof entry === "function") return `[fn:${entry.name}]`;
4860
+ return entry;
4861
+ });
4862
+ } catch {
4863
+ return unserializableMarker;
4864
+ }
4865
+ }
4866
+ function computeConfigKeyHashes(config) {
4867
+ const hashes = {};
4868
+ for (const [key, value] of Object.entries(config)) {
4869
+ if (value === void 0) continue;
4870
+ hashes[key] = (0, import_node_crypto.createHash)("sha256").update(serializeConfigValue(value, `unserializable:${key}`)).digest("hex");
4871
+ }
4872
+ return hashes;
4873
+ }
4874
+ function diffConfigKeyHashes(previous, current) {
4875
+ const keys = /* @__PURE__ */ new Set([...Object.keys(previous), ...Object.keys(current)]);
4876
+ return [...keys].filter((key) => previous[key] !== current[key]).sort();
4877
+ }
4878
+ async function discoverInputFiles(config) {
4879
+ const globs = [config.contracts.glob, config.forms.watch];
4880
+ if (config.pages) globs.push(config.pages.glob);
4881
+ const cwd = config.codegen.cwd;
4882
+ const matched = await (0, import_fast_glob3.default)(globs, { cwd, absolute: true, onlyFiles: true });
4883
+ return [...new Set(matched)].sort();
4884
+ }
4885
+ async function computeInputsHash(config, extraInputs = []) {
4886
+ const hash = (0, import_node_crypto.createHash)("sha256");
4887
+ hash.update(`version:${VERSION}
4888
+ `);
4889
+ hash.update(`config:${serializeConfig(config)}
4890
+ `);
4891
+ const cwd = config.codegen.cwd;
4892
+ const tsconfigPath = resolveTsconfigPath(cwd, config.app?.tsconfig ?? void 0);
4893
+ const tsconfigFiles = loadDiscoveryTsconfig(tsconfigPath).files;
4894
+ for (const file of tsconfigFiles.length > 0 ? tsconfigFiles : [tsconfigPath]) {
4895
+ const contents = await (0, import_promises11.readFile)(file, "utf8").catch(() => null);
4896
+ hash.update(`tsconfig:${(0, import_node_path15.relative)(cwd, file)}
4897
+ `);
4898
+ hash.update(contents ?? " missing");
4899
+ hash.update("\n");
4900
+ }
4901
+ const globbed = await discoverInputFiles(config);
4902
+ const globbedRelative = new Set(globbed.map((file) => (0, import_node_path15.relative)(cwd, file)));
4903
+ const extra = [...new Set(extraInputs)].filter((file) => !globbedRelative.has(file)).sort();
4904
+ for (const file of globbed) {
4905
+ const contents = await (0, import_promises11.readFile)(file, "utf8");
4906
+ hash.update(`file:${(0, import_node_path15.relative)(cwd, file)}
4907
+ `);
4908
+ hash.update(contents);
4909
+ hash.update("\n");
4910
+ }
4911
+ for (const file of extra) {
4912
+ const contents = await (0, import_promises11.readFile)((0, import_node_path15.join)(cwd, file), "utf8").catch(() => null);
4913
+ hash.update(`extra:${file}
4914
+ `);
4915
+ hash.update(contents ?? "\0missing");
4916
+ hash.update("\n");
4917
+ }
4918
+ return hash.digest("hex");
4919
+ }
4920
+ async function readManifest(outDir) {
4921
+ try {
4922
+ const raw = await (0, import_promises11.readFile)((0, import_node_path15.join)(outDir, MANIFEST_FILE), "utf8");
4923
+ const parsed = JSON.parse(raw);
4924
+ if (!isManifestShape(parsed)) return null;
4925
+ return {
4926
+ version: parsed.version,
4927
+ hash: parsed.hash,
4928
+ ...parsed.entryPoint ? { entryPoint: parsed.entryPoint } : {},
4929
+ ...parsed.configHash ? { configHash: parsed.configHash } : {},
4930
+ ...parsed.configKeyHashes ? { configKeyHashes: parsed.configKeyHashes } : {},
4931
+ ...parsed.extraInputs ? { extraInputs: parsed.extraInputs } : {},
4932
+ files: parsed.files
4933
+ };
4934
+ } catch {
4935
+ return null;
4936
+ }
4937
+ }
4938
+ function computeConfigHash(config) {
4939
+ return (0, import_node_crypto.createHash)("sha256").update(serializeConfig(config)).digest("hex");
4940
+ }
4941
+ async function writeManifest(outDir, manifest) {
4942
+ await (0, import_promises11.writeFile)((0, import_node_path15.join)(outDir, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}
4943
+ `, "utf8");
4944
+ }
4945
+ async function listOutputFiles(outDir) {
4946
+ const found = [];
4947
+ async function walk(dir) {
4948
+ const entries = await (0, import_promises11.readdir)(dir, { withFileTypes: true }).catch(() => []);
4949
+ for (const entry of entries) {
4950
+ const abs = (0, import_node_path15.join)(dir, entry.name);
4951
+ if (entry.isDirectory()) {
4952
+ await walk(abs);
4953
+ } else if (entry.isFile()) {
4954
+ const rel = (0, import_node_path15.relative)(outDir, abs);
4955
+ if (rel === MANIFEST_FILE || rel === LOCK_FILE) continue;
4956
+ found.push(rel);
4957
+ }
4958
+ }
4959
+ }
4960
+ await walk(outDir);
4961
+ return found.sort();
4962
+ }
4963
+ async function allOutputsExist(outDir, files) {
4964
+ const present = new Set(await listOutputFiles(outDir));
4965
+ return files.every((file) => present.has(file));
4966
+ }
4967
+ async function isManifestFresh(outDir, manifest, inputsHash) {
4968
+ if (manifest === null) return false;
4969
+ if (manifest.version !== VERSION) return false;
4970
+ if (manifest.hash !== inputsHash) return false;
4971
+ if (manifest.files.length === 0) return false;
4972
+ return allOutputsExist(outDir, manifest.files);
4973
+ }
4974
+
4975
+ // src/generate.ts
4976
+ function driftGuardMessage(outDir, previousEntryPoint, currentEntryPoint, differingKeys) {
4977
+ const differ = differingKeys.length > 0 ? `their resolved configs differ at: ${differingKeys.map((key) => `\`${key}\``).join(", ")}` : "their resolved configs differ (re-run after this generate records per-key hashes to see which keys)";
4978
+ return `[nestjs-codegen] Config drift detected in "${outDir}": the last generate ran from the "${previousEntryPoint}" entry point, this run is from the "${currentEntryPoint}" entry point, and ${differ}. Both entry points must read the SAME config \u2014 export a shared config object (e.g. codegen.config.ts) and import it from BOTH nestjs-codegen.config.ts (CLI) and NestjsCodegenModule.forRoot() (Nest module), or set \`driftGuard: false\` on either config to opt out of this check.`;
4979
+ }
4980
+ async function generate(config, inputRoutes = [], entryPoint = "cli") {
4981
+ setCodegenDebug(config.debug);
4982
+ const manifest = await readManifest(config.codegen.outDir);
4983
+ const inputsHash = await computeInputsHash(config, manifest?.extraInputs ?? []);
4984
+ if (await isManifestFresh(config.codegen.outDir, manifest, inputsHash)) {
4985
+ console.log(`[nestjs-codegen] ${config.codegen.outDir} up to date, skipped`);
4986
+ return;
4987
+ }
4988
+ const configHash = computeConfigHash(config);
4989
+ const configKeyHashes = computeConfigKeyHashes(config);
4990
+ if (config.driftGuard && manifest?.entryPoint && manifest.entryPoint !== entryPoint && manifest.configHash && manifest.configHash !== configHash) {
4991
+ throw new DriftGuardError(
4992
+ driftGuardMessage(
4993
+ config.codegen.outDir,
4994
+ manifest.entryPoint,
4995
+ entryPoint,
4996
+ // A pre-key-hash manifest can't tell us WHICH keys differ — pass none
4997
+ // rather than diffing against {} (which would name every key).
4998
+ manifest.configKeyHashes ? diffConfigKeyHashes(manifest.configKeyHashes, configKeyHashes) : []
4999
+ )
5000
+ );
5001
+ }
5002
+ const extensions = config.extensions ?? [];
5003
+ let routes = inputRoutes;
5004
+ const trackedInputs = /* @__PURE__ */ new Set();
5005
+ const ctx = createExtensionContext(config, () => routes, trackedInputs);
5006
+ if (extensions.length > 0) {
5007
+ routes = await applyTransformRoutes(routes, extensions, ctx);
5008
+ }
5009
+ if (config.pages) {
5010
+ const pagesConfig = config.pages;
5011
+ const pages = await discoverPages({
5012
+ glob: pagesConfig.glob,
5013
+ cwd: config.codegen.cwd,
5014
+ propsExport: pagesConfig.propsExport,
5015
+ componentNameStrategy: pagesConfig.componentNameStrategy
5016
+ });
5017
+ const sharedProps = discoverSharedPropsFromConfig(config);
5018
+ await emitPages(pages, config.codegen.outDir, {
5019
+ propsExport: pagesConfig.propsExport,
5020
+ sharedProps
5021
+ });
5022
+ await emitCache(pages, config.codegen.outDir);
5023
+ }
5024
+ const hasRoutes = routes.length > 0;
5025
+ const hasContracts = routes.some((r) => r.contract);
5026
+ if (hasRoutes) {
5027
+ await emitRoutes(routes, config.codegen.outDir);
5028
+ }
5029
+ if (hasContracts) {
5030
+ await emitApi(routes, config.codegen.outDir, {
5031
+ ...config.fetcher?.importPath ? { fetcherImportPath: config.fetcher.importPath } : {},
5032
+ serialization: config.serialization,
5033
+ extensions,
5034
+ ctx
5035
+ });
5036
+ }
5037
+ const hasForms = await emitForms(routes, config.codegen.outDir, config.forms, config.validation);
5038
+ if (hasContracts && config.openapi.enabled) {
5039
+ await emitOpenApi(routes, config.codegen.outDir, {
5040
+ fileName: config.openapi.fileName,
5041
+ info: {
5042
+ title: config.openapi.title,
5043
+ version: config.openapi.version,
5044
+ ...config.openapi.description ? { description: config.openapi.description } : {}
5045
+ }
5046
+ });
5047
+ }
5048
+ if (hasContracts && config.mocks.enabled) {
5049
+ await emitMocks(routes, config.codegen.outDir, {
5050
+ fileName: config.mocks.fileName,
5051
+ seed: config.mocks.seed,
5052
+ baseUrl: config.mocks.baseUrl
5053
+ });
5054
+ }
5055
+ await emitIndex(config.codegen.outDir, hasContracts, hasForms);
5056
+ if (extensions.length > 0) {
5057
+ const extraFiles = await collectEmittedFiles(extensions, ctx);
5058
+ for (const file of extraFiles) {
5059
+ const dest = (0, import_node_path16.join)(config.codegen.outDir, file.path);
5060
+ await (0, import_promises12.mkdir)((0, import_node_path16.dirname)(dest), { recursive: true });
5061
+ await (0, import_promises12.writeFile)(dest, file.contents, "utf8");
5062
+ }
5063
+ }
5064
+ const outputFiles = await listOutputFiles(config.codegen.outDir);
5065
+ const extraInputs = [...trackedInputs].sort();
5066
+ const recordedHash = extraInputs.length > 0 ? await computeInputsHash(config, extraInputs) : inputsHash;
5067
+ await writeManifest(config.codegen.outDir, {
5068
+ version: VERSION,
5069
+ hash: recordedHash,
5070
+ entryPoint,
5071
+ configHash,
5072
+ configKeyHashes,
5073
+ files: outputFiles,
5074
+ ...extraInputs.length > 0 ? { extraInputs } : {}
5075
+ });
5076
+ }
5077
+
5078
+ // src/watch/watcher.ts
5079
+ var import_promises15 = require("fs/promises");
5080
+ var import_node_path18 = require("path");
5081
+ var import_chokidar = __toESM(require("chokidar"), 1);
5082
+
5044
5083
  // src/watch/lock-file.ts
5045
5084
  var import_promises13 = require("fs/promises");
5046
5085
  var import_promises14 = require("fs/promises");
@@ -5317,7 +5356,7 @@ function createChainModuleRenderer(opts) {
5317
5356
  }
5318
5357
 
5319
5358
  // src/index.ts
5320
- var VERSION = "0.20.0";
5359
+ var VERSION = "0.21.1";
5321
5360
  // Annotate the CommonJS export names for ESM import in node:
5322
5361
  0 && (module.exports = {
5323
5362
  CodegenError,