@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/cli/main.cjs CHANGED
@@ -219,7 +219,7 @@ Run \`nestjs-codegen init\` to create a starter config.`
219
219
 
220
220
  // src/generate.ts
221
221
  var import_promises12 = require("fs/promises");
222
- var import_node_path14 = require("path");
222
+ var import_node_path16 = require("path");
223
223
 
224
224
  // src/discovery/pages.ts
225
225
  var import_promises2 = require("fs/promises");
@@ -2206,438 +2206,177 @@ function buildEmpty() {
2206
2206
  // src/generate-manifest.ts
2207
2207
  var import_node_crypto = require("crypto");
2208
2208
  var import_promises11 = require("fs/promises");
2209
- var import_node_path13 = require("path");
2209
+ var import_node_path15 = require("path");
2210
+ var import_fast_glob3 = __toESM(require("fast-glob"), 1);
2211
+
2212
+ // src/discovery/contracts-fast.ts
2213
+ var import_node_fs = require("fs");
2214
+ var import_node_path14 = require("path");
2210
2215
  var import_fast_glob2 = __toESM(require("fast-glob"), 1);
2211
- var MANIFEST_FILE = ".codegen-manifest.json";
2212
- var LOCK_FILE = ".watcher.lock";
2213
- var DriftGuardError = class extends Error {
2214
- constructor(message) {
2215
- super(message);
2216
- this.name = "DriftGuardError";
2217
- }
2218
- };
2219
- function isEntryPoint(value) {
2220
- return value === "cli" || value === "module";
2216
+ var import_ts_morph10 = require("ts-morph");
2217
+
2218
+ // src/discovery/dto-type-resolver.ts
2219
+ var import_ts_morph8 = require("ts-morph");
2220
+
2221
+ // src/discovery/dto-to-ir.ts
2222
+ var import_ts_morph4 = require("ts-morph");
2223
+
2224
+ // src/util/debug-log.ts
2225
+ var debugEnabled = false;
2226
+ function setCodegenDebug(enabled) {
2227
+ debugEnabled = enabled;
2221
2228
  }
2222
- function isManifestShape(value) {
2223
- if (typeof value !== "object" || value === null) return false;
2224
- const candidate = value;
2225
- if (typeof candidate.version !== "string") return false;
2226
- if (typeof candidate.hash !== "string") return false;
2227
- if (candidate.entryPoint !== void 0 && !isEntryPoint(candidate.entryPoint)) return false;
2228
- if (candidate.configHash !== void 0 && typeof candidate.configHash !== "string") return false;
2229
- if (candidate.configKeyHashes !== void 0 && !isStringRecord(candidate.configKeyHashes)) {
2230
- return false;
2231
- }
2232
- if (candidate.extraInputs !== void 0 && (!Array.isArray(candidate.extraInputs) || !candidate.extraInputs.every((entry) => typeof entry === "string"))) {
2233
- return false;
2234
- }
2235
- if (!Array.isArray(candidate.files)) return false;
2236
- return candidate.files.every((entry) => typeof entry === "string");
2229
+ function debugWarn(message) {
2230
+ if (debugEnabled) console.warn(`[nestjs-codegen] ${message}`);
2237
2231
  }
2238
- function isStringRecord(value) {
2239
- if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
2240
- return Object.values(value).every((entry) => typeof entry === "string");
2232
+
2233
+ // src/discovery/type-ref-resolution.ts
2234
+ var import_node_path13 = require("path");
2235
+ var import_ts_morph3 = require("ts-morph");
2236
+ var _EMPTY_CTX = { projectRoot: "", tsconfigPaths: null };
2237
+ var _ctxByProject = /* @__PURE__ */ new WeakMap();
2238
+ function setDiscoveryContext(project, ctx) {
2239
+ _ctxByProject.set(project, ctx);
2241
2240
  }
2242
- function serializeConfig(config) {
2243
- return serializeConfigValue(config, `unserializable:${config.codegen.outDir}`);
2241
+ function _ctxFor(project) {
2242
+ return _ctxByProject.get(project) ?? _EMPTY_CTX;
2244
2243
  }
2245
- function serializeConfigValue(value, unserializableMarker) {
2246
- try {
2247
- return JSON.stringify(value, (_key, entry) => {
2248
- if (typeof entry === "function") return `[fn:${entry.name}]`;
2249
- return entry;
2244
+ var _debug = process.env.NESTJS_INERTIA_DEBUG === "1";
2245
+ function dbg(...args) {
2246
+ if (_debug) console.log("[codegen:debug]", ...args);
2247
+ }
2248
+ function findTypeInFile(name, file) {
2249
+ const cls = file.getClass(name);
2250
+ if (cls) return { kind: "class", decl: cls, file };
2251
+ const iface = file.getInterface(name);
2252
+ if (iface) return { kind: "interface", decl: iface, file };
2253
+ const alias = file.getTypeAlias(name);
2254
+ if (alias) {
2255
+ const typeNode = alias.getTypeNode();
2256
+ return {
2257
+ kind: "typeAlias",
2258
+ typeNode,
2259
+ file,
2260
+ text: typeNode ? typeNode.getText() : "unknown"
2261
+ };
2262
+ }
2263
+ const enumDecl = file.getEnum(name);
2264
+ if (enumDecl) {
2265
+ const members = enumDecl.getMembers().map((m) => {
2266
+ const val = m.getValue();
2267
+ if (typeof val === "string" || typeof val === "number") return JSON.stringify(val);
2268
+ return JSON.stringify(m.getName());
2250
2269
  });
2251
- } catch {
2252
- return unserializableMarker;
2270
+ return { kind: "enum", members };
2253
2271
  }
2272
+ return null;
2254
2273
  }
2255
- function computeConfigKeyHashes(config) {
2256
- const hashes = {};
2257
- for (const [key, value] of Object.entries(config)) {
2258
- if (value === void 0) continue;
2259
- hashes[key] = (0, import_node_crypto.createHash)("sha256").update(serializeConfigValue(value, `unserializable:${key}`)).digest("hex");
2274
+ function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
2275
+ if (moduleSpecifier.startsWith(".")) {
2276
+ const dir = (0, import_node_path13.dirname)(sourceFile.getFilePath());
2277
+ const noExt = moduleSpecifier.replace(/\.(js|ts)$/, "");
2278
+ return [
2279
+ (0, import_node_path13.resolve)(dir, `${noExt}.ts`),
2280
+ (0, import_node_path13.resolve)(dir, `${moduleSpecifier}.ts`),
2281
+ (0, import_node_path13.resolve)(dir, moduleSpecifier, "index.ts")
2282
+ ];
2260
2283
  }
2261
- return hashes;
2262
- }
2263
- function diffConfigKeyHashes(previous, current) {
2264
- const keys = /* @__PURE__ */ new Set([...Object.keys(previous), ...Object.keys(current)]);
2265
- return [...keys].filter((key) => previous[key] !== current[key]).sort();
2266
- }
2267
- async function discoverInputFiles(config) {
2268
- const globs = [config.contracts.glob, config.forms.watch];
2269
- if (config.pages) globs.push(config.pages.glob);
2270
- const cwd = config.codegen.cwd;
2271
- const matched = await (0, import_fast_glob2.default)(globs, { cwd, absolute: true, onlyFiles: true });
2272
- return [...new Set(matched)].sort();
2273
- }
2274
- async function computeInputsHash(config, extraInputs = []) {
2275
- const hash = (0, import_node_crypto.createHash)("sha256");
2276
- hash.update(`version:${VERSION}
2277
- `);
2278
- hash.update(`config:${serializeConfig(config)}
2279
- `);
2280
- const cwd = config.codegen.cwd;
2281
- const globbed = await discoverInputFiles(config);
2282
- const globbedRelative = new Set(globbed.map((file) => (0, import_node_path13.relative)(cwd, file)));
2283
- const extra = [...new Set(extraInputs)].filter((file) => !globbedRelative.has(file)).sort();
2284
- for (const file of globbed) {
2285
- const contents = await (0, import_promises11.readFile)(file, "utf8");
2286
- hash.update(`file:${(0, import_node_path13.relative)(cwd, file)}
2287
- `);
2288
- hash.update(contents);
2289
- hash.update("\n");
2284
+ const ctx = _ctxFor(project);
2285
+ const baseUrl = ctx.pathsBase ?? ctx.projectRoot;
2286
+ const tsconfigPaths = ctx.tsconfigPaths;
2287
+ dbg(
2288
+ "resolveModuleSpecifier",
2289
+ moduleSpecifier,
2290
+ "paths:",
2291
+ JSON.stringify(tsconfigPaths),
2292
+ "baseUrl:",
2293
+ baseUrl
2294
+ );
2295
+ if (tsconfigPaths) {
2296
+ for (const [pattern, mappings] of Object.entries(tsconfigPaths)) {
2297
+ const prefix = pattern.replace("*", "");
2298
+ if (moduleSpecifier.startsWith(prefix)) {
2299
+ const rest = moduleSpecifier.slice(prefix.length);
2300
+ const candidates = [];
2301
+ for (const mapping of mappings) {
2302
+ const resolved = (0, import_node_path13.resolve)(baseUrl, mapping.replace("*", rest));
2303
+ candidates.push(`${resolved}.ts`, (0, import_node_path13.resolve)(resolved, "index.ts"));
2304
+ }
2305
+ dbg(" resolved candidates:", candidates);
2306
+ return candidates;
2307
+ }
2308
+ }
2290
2309
  }
2291
- for (const file of extra) {
2292
- const contents = await (0, import_promises11.readFile)((0, import_node_path13.join)(cwd, file), "utf8").catch(() => null);
2293
- hash.update(`extra:${file}
2294
- `);
2295
- hash.update(contents ?? "\0missing");
2296
- hash.update("\n");
2310
+ return [];
2311
+ }
2312
+ function resolveImportedType(name, sourceFile, project) {
2313
+ for (const importDecl of sourceFile.getImportDeclarations()) {
2314
+ const namedImport = importDecl.getNamedImports().find((n) => n.getName() === name);
2315
+ if (!namedImport) continue;
2316
+ const moduleSpecifier = importDecl.getModuleSpecifierValue();
2317
+ const candidates = resolveModuleSpecifier(moduleSpecifier, sourceFile, project);
2318
+ for (const candidate of candidates) {
2319
+ let importedFile = project.getSourceFile(candidate);
2320
+ if (!importedFile) {
2321
+ try {
2322
+ importedFile = project.addSourceFileAtPath(candidate);
2323
+ } catch {
2324
+ continue;
2325
+ }
2326
+ }
2327
+ const result = findTypeInFile(name, importedFile);
2328
+ if (result) return result;
2329
+ const viaReExport = resolveReExportedType(name, importedFile, project, /* @__PURE__ */ new Set());
2330
+ if (viaReExport) return viaReExport;
2331
+ }
2332
+ if (candidates.length === 0) {
2333
+ const viaCompiler = resolveBareSpecifierType(name, importDecl, project);
2334
+ if (viaCompiler) return viaCompiler;
2335
+ }
2297
2336
  }
2298
- return hash.digest("hex");
2337
+ return resolveReExportedType(name, sourceFile, project, /* @__PURE__ */ new Set());
2299
2338
  }
2300
- async function readManifest(outDir) {
2339
+ function resolveBareSpecifierType(name, importDecl, project) {
2340
+ let target;
2301
2341
  try {
2302
- const raw = await (0, import_promises11.readFile)((0, import_node_path13.join)(outDir, MANIFEST_FILE), "utf8");
2303
- const parsed = JSON.parse(raw);
2304
- if (!isManifestShape(parsed)) return null;
2305
- return {
2306
- version: parsed.version,
2307
- hash: parsed.hash,
2308
- ...parsed.entryPoint ? { entryPoint: parsed.entryPoint } : {},
2309
- ...parsed.configHash ? { configHash: parsed.configHash } : {},
2310
- ...parsed.configKeyHashes ? { configKeyHashes: parsed.configKeyHashes } : {},
2311
- ...parsed.extraInputs ? { extraInputs: parsed.extraInputs } : {},
2312
- files: parsed.files
2313
- };
2342
+ target = importDecl.getModuleSpecifierSourceFile();
2314
2343
  } catch {
2315
2344
  return null;
2316
2345
  }
2346
+ if (!target) return null;
2347
+ const direct = findTypeInFile(name, target);
2348
+ if (direct) return direct;
2349
+ return resolveReExportedType(name, target, project, /* @__PURE__ */ new Set());
2317
2350
  }
2318
- function computeConfigHash(config) {
2319
- return (0, import_node_crypto.createHash)("sha256").update(serializeConfig(config)).digest("hex");
2320
- }
2321
- async function writeManifest(outDir, manifest) {
2322
- await (0, import_promises11.writeFile)((0, import_node_path13.join)(outDir, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}
2323
- `, "utf8");
2324
- }
2325
- async function listOutputFiles(outDir) {
2326
- const found = [];
2327
- async function walk(dir) {
2328
- const entries = await (0, import_promises11.readdir)(dir, { withFileTypes: true }).catch(() => []);
2329
- for (const entry of entries) {
2330
- const abs = (0, import_node_path13.join)(dir, entry.name);
2331
- if (entry.isDirectory()) {
2332
- await walk(abs);
2333
- } else if (entry.isFile()) {
2334
- const rel = (0, import_node_path13.relative)(outDir, abs);
2335
- if (rel === MANIFEST_FILE || rel === LOCK_FILE) continue;
2336
- found.push(rel);
2337
- }
2351
+ function resolveReExportedType(name, file, project, seen) {
2352
+ const filePath = file.getFilePath();
2353
+ if (seen.has(filePath)) return null;
2354
+ seen.add(filePath);
2355
+ for (const exportDecl of file.getExportDeclarations()) {
2356
+ const moduleSpecifier = exportDecl.getModuleSpecifierValue();
2357
+ const namedExports = exportDecl.getNamedExports();
2358
+ if (moduleSpecifier) {
2359
+ const hasStar = namedExports.length === 0;
2360
+ const reExportsName2 = namedExports.some(
2361
+ (n) => (n.getAliasNode()?.getText() ?? n.getName()) === name
2362
+ );
2363
+ if (!hasStar && !reExportsName2) continue;
2364
+ const sourceName2 = hasStar ? name : namedExports.find((n) => (n.getAliasNode()?.getText() ?? n.getName()) === name)?.getName() ?? name;
2365
+ const target = followModuleForType(sourceName2, moduleSpecifier, file, project, seen);
2366
+ if (target) return target;
2367
+ continue;
2338
2368
  }
2369
+ const reExportsName = namedExports.some(
2370
+ (n) => (n.getAliasNode()?.getText() ?? n.getName()) === name
2371
+ );
2372
+ if (!reExportsName) continue;
2373
+ const sourceName = namedExports.find((n) => (n.getAliasNode()?.getText() ?? n.getName()) === name)?.getName() ?? name;
2374
+ const local = findTypeInFile(sourceName, file);
2375
+ if (local) return local;
2376
+ const imported = resolveImportedType(sourceName, file, project);
2377
+ if (imported) return imported;
2339
2378
  }
2340
- await walk(outDir);
2341
- return found.sort();
2342
- }
2343
- async function allOutputsExist(outDir, files) {
2344
- const present = new Set(await listOutputFiles(outDir));
2345
- return files.every((file) => present.has(file));
2346
- }
2347
- async function isManifestFresh(outDir, manifest, inputsHash) {
2348
- if (manifest === null) return false;
2349
- if (manifest.version !== VERSION) return false;
2350
- if (manifest.hash !== inputsHash) return false;
2351
- if (manifest.files.length === 0) return false;
2352
- return allOutputsExist(outDir, manifest.files);
2353
- }
2354
-
2355
- // src/util/debug-log.ts
2356
- var debugEnabled = false;
2357
- function setCodegenDebug(enabled) {
2358
- debugEnabled = enabled;
2359
- }
2360
- function debugWarn(message) {
2361
- if (debugEnabled) console.warn(`[nestjs-codegen] ${message}`);
2362
- }
2363
-
2364
- // src/generate.ts
2365
- function driftGuardMessage(outDir, previousEntryPoint, currentEntryPoint, differingKeys) {
2366
- 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)";
2367
- 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.`;
2368
- }
2369
- async function generate(config, inputRoutes = [], entryPoint = "cli") {
2370
- setCodegenDebug(config.debug);
2371
- const manifest = await readManifest(config.codegen.outDir);
2372
- const inputsHash = await computeInputsHash(config, manifest?.extraInputs ?? []);
2373
- if (await isManifestFresh(config.codegen.outDir, manifest, inputsHash)) {
2374
- console.log(`[nestjs-codegen] ${config.codegen.outDir} up to date, skipped`);
2375
- return;
2376
- }
2377
- const configHash = computeConfigHash(config);
2378
- const configKeyHashes = computeConfigKeyHashes(config);
2379
- if (config.driftGuard && manifest?.entryPoint && manifest.entryPoint !== entryPoint && manifest.configHash && manifest.configHash !== configHash) {
2380
- throw new DriftGuardError(
2381
- driftGuardMessage(
2382
- config.codegen.outDir,
2383
- manifest.entryPoint,
2384
- entryPoint,
2385
- // A pre-key-hash manifest can't tell us WHICH keys differ — pass none
2386
- // rather than diffing against {} (which would name every key).
2387
- manifest.configKeyHashes ? diffConfigKeyHashes(manifest.configKeyHashes, configKeyHashes) : []
2388
- )
2389
- );
2390
- }
2391
- const extensions = config.extensions ?? [];
2392
- let routes = inputRoutes;
2393
- const trackedInputs = /* @__PURE__ */ new Set();
2394
- const ctx = createExtensionContext(config, () => routes, trackedInputs);
2395
- if (extensions.length > 0) {
2396
- routes = await applyTransformRoutes(routes, extensions, ctx);
2397
- }
2398
- if (config.pages) {
2399
- const pagesConfig = config.pages;
2400
- const pages = await discoverPages({
2401
- glob: pagesConfig.glob,
2402
- cwd: config.codegen.cwd,
2403
- propsExport: pagesConfig.propsExport,
2404
- componentNameStrategy: pagesConfig.componentNameStrategy
2405
- });
2406
- const sharedProps = discoverSharedPropsFromConfig(config);
2407
- await emitPages(pages, config.codegen.outDir, {
2408
- propsExport: pagesConfig.propsExport,
2409
- sharedProps
2410
- });
2411
- await emitCache(pages, config.codegen.outDir);
2412
- }
2413
- const hasRoutes = routes.length > 0;
2414
- const hasContracts = routes.some((r) => r.contract);
2415
- if (hasRoutes) {
2416
- await emitRoutes(routes, config.codegen.outDir);
2417
- }
2418
- if (hasContracts) {
2419
- await emitApi(routes, config.codegen.outDir, {
2420
- ...config.fetcher?.importPath ? { fetcherImportPath: config.fetcher.importPath } : {},
2421
- serialization: config.serialization,
2422
- extensions,
2423
- ctx
2424
- });
2425
- }
2426
- const hasForms = await emitForms(routes, config.codegen.outDir, config.forms, config.validation);
2427
- if (hasContracts && config.openapi.enabled) {
2428
- await emitOpenApi(routes, config.codegen.outDir, {
2429
- fileName: config.openapi.fileName,
2430
- info: {
2431
- title: config.openapi.title,
2432
- version: config.openapi.version,
2433
- ...config.openapi.description ? { description: config.openapi.description } : {}
2434
- }
2435
- });
2436
- }
2437
- if (hasContracts && config.mocks.enabled) {
2438
- await emitMocks(routes, config.codegen.outDir, {
2439
- fileName: config.mocks.fileName,
2440
- seed: config.mocks.seed,
2441
- baseUrl: config.mocks.baseUrl
2442
- });
2443
- }
2444
- await emitIndex(config.codegen.outDir, hasContracts, hasForms);
2445
- if (extensions.length > 0) {
2446
- const extraFiles = await collectEmittedFiles(extensions, ctx);
2447
- for (const file of extraFiles) {
2448
- const dest = (0, import_node_path14.join)(config.codegen.outDir, file.path);
2449
- await (0, import_promises12.mkdir)((0, import_node_path14.dirname)(dest), { recursive: true });
2450
- await (0, import_promises12.writeFile)(dest, file.contents, "utf8");
2451
- }
2452
- }
2453
- const outputFiles = await listOutputFiles(config.codegen.outDir);
2454
- const extraInputs = [...trackedInputs].sort();
2455
- const recordedHash = extraInputs.length > 0 ? await computeInputsHash(config, extraInputs) : inputsHash;
2456
- await writeManifest(config.codegen.outDir, {
2457
- version: VERSION,
2458
- hash: recordedHash,
2459
- entryPoint,
2460
- configHash,
2461
- configKeyHashes,
2462
- files: outputFiles,
2463
- ...extraInputs.length > 0 ? { extraInputs } : {}
2464
- });
2465
- }
2466
-
2467
- // src/watch/watcher.ts
2468
- var import_promises15 = require("fs/promises");
2469
- var import_node_path18 = require("path");
2470
- var import_chokidar = __toESM(require("chokidar"), 1);
2471
-
2472
- // src/discovery/contracts-fast.ts
2473
- var import_node_path16 = require("path");
2474
- var import_fast_glob3 = __toESM(require("fast-glob"), 1);
2475
- var import_ts_morph10 = require("ts-morph");
2476
-
2477
- // src/discovery/dto-type-resolver.ts
2478
- var import_ts_morph8 = require("ts-morph");
2479
-
2480
- // src/discovery/dto-to-ir.ts
2481
- var import_ts_morph4 = require("ts-morph");
2482
-
2483
- // src/discovery/type-ref-resolution.ts
2484
- var import_node_fs = require("fs");
2485
- var import_node_path15 = require("path");
2486
- var import_ts_morph3 = require("ts-morph");
2487
- var _EMPTY_CTX = { projectRoot: "", tsconfigPaths: null };
2488
- var _ctxByProject = /* @__PURE__ */ new WeakMap();
2489
- function setDiscoveryContext(project, ctx) {
2490
- _ctxByProject.set(project, ctx);
2491
- }
2492
- function _ctxFor(project) {
2493
- return _ctxByProject.get(project) ?? _EMPTY_CTX;
2494
- }
2495
- var _debug = process.env.NESTJS_INERTIA_DEBUG === "1";
2496
- function dbg(...args) {
2497
- if (_debug) console.log("[codegen:debug]", ...args);
2498
- }
2499
- function loadTsconfigPaths(tsconfigPath) {
2500
- try {
2501
- const raw = (0, import_node_fs.readFileSync)(tsconfigPath, "utf8");
2502
- const stripped = raw.replace(/\/\/.*$/gm, "");
2503
- const parsed = JSON.parse(stripped);
2504
- return parsed.compilerOptions?.paths ?? null;
2505
- } catch {
2506
- return null;
2507
- }
2508
- }
2509
- function findTypeInFile(name, file) {
2510
- const cls = file.getClass(name);
2511
- if (cls) return { kind: "class", decl: cls, file };
2512
- const iface = file.getInterface(name);
2513
- if (iface) return { kind: "interface", decl: iface, file };
2514
- const alias = file.getTypeAlias(name);
2515
- if (alias) {
2516
- const typeNode = alias.getTypeNode();
2517
- return {
2518
- kind: "typeAlias",
2519
- typeNode,
2520
- file,
2521
- text: typeNode ? typeNode.getText() : "unknown"
2522
- };
2523
- }
2524
- const enumDecl = file.getEnum(name);
2525
- if (enumDecl) {
2526
- const members = enumDecl.getMembers().map((m) => {
2527
- const val = m.getValue();
2528
- if (typeof val === "string" || typeof val === "number") return JSON.stringify(val);
2529
- return JSON.stringify(m.getName());
2530
- });
2531
- return { kind: "enum", members };
2532
- }
2533
- return null;
2534
- }
2535
- function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
2536
- if (moduleSpecifier.startsWith(".")) {
2537
- const dir = (0, import_node_path15.dirname)(sourceFile.getFilePath());
2538
- const noExt = moduleSpecifier.replace(/\.(js|ts)$/, "");
2539
- return [
2540
- (0, import_node_path15.resolve)(dir, `${noExt}.ts`),
2541
- (0, import_node_path15.resolve)(dir, `${moduleSpecifier}.ts`),
2542
- (0, import_node_path15.resolve)(dir, moduleSpecifier, "index.ts")
2543
- ];
2544
- }
2545
- const ctx = _ctxFor(project);
2546
- const baseUrl = ctx.projectRoot;
2547
- const tsconfigPaths = ctx.tsconfigPaths;
2548
- dbg(
2549
- "resolveModuleSpecifier",
2550
- moduleSpecifier,
2551
- "paths:",
2552
- JSON.stringify(tsconfigPaths),
2553
- "baseUrl:",
2554
- baseUrl
2555
- );
2556
- if (tsconfigPaths) {
2557
- for (const [pattern, mappings] of Object.entries(tsconfigPaths)) {
2558
- const prefix = pattern.replace("*", "");
2559
- if (moduleSpecifier.startsWith(prefix)) {
2560
- const rest = moduleSpecifier.slice(prefix.length);
2561
- const candidates = [];
2562
- for (const mapping of mappings) {
2563
- const resolved = (0, import_node_path15.resolve)(baseUrl, mapping.replace("*", rest));
2564
- candidates.push(`${resolved}.ts`, (0, import_node_path15.resolve)(resolved, "index.ts"));
2565
- }
2566
- dbg(" resolved candidates:", candidates);
2567
- return candidates;
2568
- }
2569
- }
2570
- }
2571
- return [];
2572
- }
2573
- function resolveImportedType(name, sourceFile, project) {
2574
- for (const importDecl of sourceFile.getImportDeclarations()) {
2575
- const namedImport = importDecl.getNamedImports().find((n) => n.getName() === name);
2576
- if (!namedImport) continue;
2577
- const moduleSpecifier = importDecl.getModuleSpecifierValue();
2578
- const candidates = resolveModuleSpecifier(moduleSpecifier, sourceFile, project);
2579
- for (const candidate of candidates) {
2580
- let importedFile = project.getSourceFile(candidate);
2581
- if (!importedFile) {
2582
- try {
2583
- importedFile = project.addSourceFileAtPath(candidate);
2584
- } catch {
2585
- continue;
2586
- }
2587
- }
2588
- const result = findTypeInFile(name, importedFile);
2589
- if (result) return result;
2590
- const viaReExport = resolveReExportedType(name, importedFile, project, /* @__PURE__ */ new Set());
2591
- if (viaReExport) return viaReExport;
2592
- }
2593
- if (candidates.length === 0) {
2594
- const viaCompiler = resolveBareSpecifierType(name, importDecl, project);
2595
- if (viaCompiler) return viaCompiler;
2596
- }
2597
- }
2598
- return resolveReExportedType(name, sourceFile, project, /* @__PURE__ */ new Set());
2599
- }
2600
- function resolveBareSpecifierType(name, importDecl, project) {
2601
- let target;
2602
- try {
2603
- target = importDecl.getModuleSpecifierSourceFile();
2604
- } catch {
2605
- return null;
2606
- }
2607
- if (!target) return null;
2608
- const direct = findTypeInFile(name, target);
2609
- if (direct) return direct;
2610
- return resolveReExportedType(name, target, project, /* @__PURE__ */ new Set());
2611
- }
2612
- function resolveReExportedType(name, file, project, seen) {
2613
- const filePath = file.getFilePath();
2614
- if (seen.has(filePath)) return null;
2615
- seen.add(filePath);
2616
- for (const exportDecl of file.getExportDeclarations()) {
2617
- const moduleSpecifier = exportDecl.getModuleSpecifierValue();
2618
- const namedExports = exportDecl.getNamedExports();
2619
- if (moduleSpecifier) {
2620
- const hasStar = namedExports.length === 0;
2621
- const reExportsName2 = namedExports.some(
2622
- (n) => (n.getAliasNode()?.getText() ?? n.getName()) === name
2623
- );
2624
- if (!hasStar && !reExportsName2) continue;
2625
- const sourceName2 = hasStar ? name : namedExports.find((n) => (n.getAliasNode()?.getText() ?? n.getName()) === name)?.getName() ?? name;
2626
- const target = followModuleForType(sourceName2, moduleSpecifier, file, project, seen);
2627
- if (target) return target;
2628
- continue;
2629
- }
2630
- const reExportsName = namedExports.some(
2631
- (n) => (n.getAliasNode()?.getText() ?? n.getName()) === name
2632
- );
2633
- if (!reExportsName) continue;
2634
- const sourceName = namedExports.find((n) => (n.getAliasNode()?.getText() ?? n.getName()) === name)?.getName() ?? name;
2635
- const local = findTypeInFile(sourceName, file);
2636
- if (local) return local;
2637
- const imported = resolveImportedType(sourceName, file, project);
2638
- if (imported) return imported;
2639
- }
2640
- return null;
2379
+ return null;
2641
2380
  }
2642
2381
  function followModuleForType(name, moduleSpecifier, fromFile, project, seen) {
2643
2382
  const candidates = resolveModuleSpecifier(moduleSpecifier, fromFile, project);
@@ -3736,7 +3475,7 @@ function extractFilterForHints(classDecl, project) {
3736
3475
  }
3737
3476
  return hints;
3738
3477
  }
3739
- function extractApplyFilterInfo(method, sourceFile, project, mixin) {
3478
+ function extractApplyFilterInfo(method, sourceFile, project, mixin, controllerClass) {
3740
3479
  const declFile = method.getSourceFile();
3741
3480
  const mixinEntity = resolveMixinEntityClass(mixin, project);
3742
3481
  for (const param of method.getParameters()) {
@@ -3762,7 +3501,7 @@ function extractApplyFilterInfo(method, sourceFile, project, mixin) {
3762
3501
  const filterClassName = filterClassArg.getText();
3763
3502
  const resolved = factoryStaticClass ? void 0 : findType(filterClassName, declFile, project);
3764
3503
  const declaredClass = factoryStaticClass ?? (resolved?.kind === "class" ? resolved.decl : resolveLocalClassDeclaration(filterClassArg));
3765
- const ownRoute = !mixin || declFile.getFilePath() !== mixin.factoryFilePath;
3504
+ const ownRoute = !mixin || (controllerClass ? method.getParent() === controllerClass : declFile.getFilePath() !== mixin.factoryFilePath);
3766
3505
  const namedByRoute = ownRoute && import_ts_morph7.Node.isIdentifier(filterClassArg) ? declaredClass : void 0;
3767
3506
  for (const candidate of orderFilterCandidates({
3768
3507
  namedByRoute,
@@ -4353,9 +4092,9 @@ function unwrapNamedContainer(node, names) {
4353
4092
  }
4354
4093
  return node;
4355
4094
  }
4356
- function extractDtoContract(method, sourceFile, project, mixin) {
4095
+ function extractDtoContract(method, sourceFile, project, mixin, controllerClass) {
4357
4096
  let body = extractBodyType(method, sourceFile, project);
4358
- const filterInfo = extractApplyFilterInfo(method, sourceFile, project, mixin);
4097
+ const filterInfo = extractApplyFilterInfo(method, sourceFile, project, mixin, controllerClass);
4359
4098
  const query = extractQueryType(method, sourceFile, project);
4360
4099
  const uploads = extractUploadedFiles(method);
4361
4100
  const multipartBody = uploads.fields ? `{ ${uploads.fields} }` : null;
@@ -4592,43 +4331,80 @@ function parseDefineContractCall(callExpr) {
4592
4331
  async function discoverContractsFast(opts) {
4593
4332
  const { cwd, glob, tsconfig } = opts;
4594
4333
  const tsconfigPath = resolveTsconfigPath(cwd, tsconfig);
4595
- const project = createDiscoveryProject(tsconfigPath);
4596
- const files = await (0, import_fast_glob3.default)(glob, { cwd, absolute: true, onlyFiles: true });
4334
+ const loaded = loadDiscoveryTsconfig(tsconfigPath);
4335
+ const project = createDiscoveryProject(tsconfigPath, loaded);
4336
+ const files = await (0, import_fast_glob2.default)(glob, { cwd, absolute: true, onlyFiles: true });
4597
4337
  for (const f of files) {
4598
4338
  project.addSourceFileAtPath(f);
4599
4339
  }
4600
- bindDiscoveryContext(project, cwd, tsconfigPath);
4340
+ bindDiscoveryContext(project, cwd, tsconfigPath, loaded);
4601
4341
  clearMixinTypeProject();
4602
4342
  return extractAllRoutes(project);
4603
4343
  }
4604
- function resolveTsconfigPath(cwd, tsconfig) {
4605
- return tsconfig ? (0, import_node_path16.resolve)(tsconfig) : (0, import_node_path16.join)(cwd, "tsconfig.json");
4344
+ function resolveTsconfigPath(cwd, tsconfig) {
4345
+ return tsconfig ? (0, import_node_path14.resolve)(tsconfig) : (0, import_node_path14.join)(cwd, "tsconfig.json");
4346
+ }
4347
+ var NO_TSCONFIG_OPTIONS = {
4348
+ allowJs: true,
4349
+ resolveJsonModule: false,
4350
+ strict: false
4351
+ };
4352
+ function loadDiscoveryTsconfig(tsconfigPath) {
4353
+ if (!(0, import_node_fs.existsSync)(tsconfigPath)) return { options: null, files: [] };
4354
+ const read = import_ts_morph10.ts.readConfigFile(tsconfigPath, (path) => import_ts_morph10.ts.sys.readFile(path));
4355
+ if (read.error) {
4356
+ return {
4357
+ options: null,
4358
+ files: [tsconfigPath],
4359
+ error: import_ts_morph10.ts.flattenDiagnosticMessageText(read.error.messageText, " ")
4360
+ };
4361
+ }
4362
+ const host = {
4363
+ useCaseSensitiveFileNames: import_ts_morph10.ts.sys.useCaseSensitiveFileNames,
4364
+ // The one line that keeps a tsconfig from being a filesystem walk.
4365
+ readDirectory: () => [],
4366
+ fileExists: (path) => import_ts_morph10.ts.sys.fileExists(path),
4367
+ // `extends` is resolved through the host, so a consumer whose `paths` live
4368
+ // in a base tsconfig keeps working.
4369
+ readFile: (path) => import_ts_morph10.ts.sys.readFile(path)
4370
+ };
4371
+ const sourceFile = import_ts_morph10.ts.readJsonConfigFile(tsconfigPath, (path) => import_ts_morph10.ts.sys.readFile(path));
4372
+ const parsed = import_ts_morph10.ts.parseJsonSourceFileConfigFileContent(
4373
+ sourceFile,
4374
+ host,
4375
+ (0, import_node_path14.dirname)(tsconfigPath),
4376
+ void 0,
4377
+ tsconfigPath
4378
+ );
4379
+ return {
4380
+ options: parsed.options,
4381
+ files: [tsconfigPath, ...sourceFile.extendedSourceFiles ?? []]
4382
+ };
4383
+ }
4384
+ function pathsBaseDir(options, fallback) {
4385
+ if (typeof options.baseUrl === "string") return options.baseUrl;
4386
+ if (typeof options.pathsBasePath === "string") return options.pathsBasePath;
4387
+ return fallback;
4606
4388
  }
4607
- function createDiscoveryProject(tsconfigPath) {
4608
- try {
4609
- return new import_ts_morph10.Project({
4610
- tsConfigFilePath: tsconfigPath,
4611
- skipAddingFilesFromTsConfig: true,
4612
- skipLoadingLibFiles: true,
4613
- skipFileDependencyResolution: true
4614
- });
4615
- } catch {
4616
- return new import_ts_morph10.Project({
4617
- skipAddingFilesFromTsConfig: true,
4618
- skipLoadingLibFiles: true,
4619
- skipFileDependencyResolution: true,
4620
- compilerOptions: {
4621
- allowJs: true,
4622
- resolveJsonModule: false,
4623
- strict: false
4624
- }
4625
- });
4389
+ function createDiscoveryProject(tsconfigPath, tsconfig = loadDiscoveryTsconfig(tsconfigPath)) {
4390
+ if (tsconfig.error) {
4391
+ console.warn(
4392
+ `[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.`
4393
+ );
4626
4394
  }
4395
+ return new import_ts_morph10.Project({
4396
+ compilerOptions: tsconfig.options ?? { ...NO_TSCONFIG_OPTIONS },
4397
+ skipAddingFilesFromTsConfig: true,
4398
+ skipLoadingLibFiles: true,
4399
+ skipFileDependencyResolution: true
4400
+ });
4627
4401
  }
4628
- function bindDiscoveryContext(project, cwd, tsconfigPath) {
4402
+ function bindDiscoveryContext(project, cwd, tsconfigPath, tsconfig = loadDiscoveryTsconfig(tsconfigPath)) {
4403
+ const { options } = tsconfig;
4629
4404
  setDiscoveryContext(project, {
4630
4405
  projectRoot: cwd,
4631
- tsconfigPaths: loadTsconfigPaths(tsconfigPath)
4406
+ tsconfigPaths: options?.paths ?? null,
4407
+ pathsBase: options ? pathsBaseDir(options, cwd) : cwd
4632
4408
  });
4633
4409
  }
4634
4410
  function extractRoutesFrom(project, controllerPaths) {
@@ -4664,10 +4440,11 @@ var PersistentDiscovery = class _PersistentDiscovery {
4664
4440
  static async create(opts) {
4665
4441
  const { cwd, glob, tsconfig } = opts;
4666
4442
  const tsconfigPath = resolveTsconfigPath(cwd, tsconfig);
4667
- const project = createDiscoveryProject(tsconfigPath);
4668
- bindDiscoveryContext(project, cwd, tsconfigPath);
4443
+ const loaded = loadDiscoveryTsconfig(tsconfigPath);
4444
+ const project = createDiscoveryProject(tsconfigPath, loaded);
4445
+ bindDiscoveryContext(project, cwd, tsconfigPath, loaded);
4669
4446
  const instance = new _PersistentDiscovery(project, cwd, glob);
4670
- const files = await (0, import_fast_glob3.default)(glob, { cwd, absolute: true, onlyFiles: true });
4447
+ const files = await (0, import_fast_glob2.default)(glob, { cwd, absolute: true, onlyFiles: true });
4671
4448
  for (const f of files) {
4672
4449
  project.addSourceFileAtPath(f);
4673
4450
  instance.controllerPaths.add(f);
@@ -4688,7 +4465,7 @@ var PersistentDiscovery = class _PersistentDiscovery {
4688
4465
  async rediscover(changedPaths) {
4689
4466
  if (changedPaths) {
4690
4467
  for (const p of changedPaths) {
4691
- const abs = (0, import_node_path16.resolve)(p);
4468
+ const abs = (0, import_node_path14.resolve)(p);
4692
4469
  const sf = this.project.getSourceFile(abs);
4693
4470
  if (sf) {
4694
4471
  await sf.refreshFromFileSystem();
@@ -4696,7 +4473,7 @@ var PersistentDiscovery = class _PersistentDiscovery {
4696
4473
  }
4697
4474
  }
4698
4475
  const globbed = new Set(
4699
- await (0, import_fast_glob3.default)(this.glob, { cwd: this.cwd, absolute: true, onlyFiles: true })
4476
+ await (0, import_fast_glob2.default)(this.glob, { cwd: this.cwd, absolute: true, onlyFiles: true })
4700
4477
  );
4701
4478
  for (const f of globbed) {
4702
4479
  if (!this.controllerPaths.has(f)) {
@@ -4918,7 +4695,7 @@ function extractDtoRoute(args) {
4918
4695
  const methodName = method.getName();
4919
4696
  const classAs = readAsDecorator(cls, `class ${className}`);
4920
4697
  const methodAs = readAsDecorator(method, `${className}.${methodName}`);
4921
- const dtoContract = extractDtoContract(method, sourceFile, project, mixin);
4698
+ const dtoContract = extractDtoContract(method, sourceFile, project, mixin, cls);
4922
4699
  const mixinResponse = mixin ? resolveInstantiatedReturnType(cls, methodName) : void 0;
4923
4700
  return buildRoute({
4924
4701
  className,
@@ -5006,6 +4783,268 @@ function extractFromSourceFile(sourceFile, project) {
5006
4783
  return routes;
5007
4784
  }
5008
4785
 
4786
+ // src/generate-manifest.ts
4787
+ var MANIFEST_FILE = ".codegen-manifest.json";
4788
+ var LOCK_FILE = ".watcher.lock";
4789
+ var DriftGuardError = class extends Error {
4790
+ constructor(message) {
4791
+ super(message);
4792
+ this.name = "DriftGuardError";
4793
+ }
4794
+ };
4795
+ function isEntryPoint(value) {
4796
+ return value === "cli" || value === "module";
4797
+ }
4798
+ function isManifestShape(value) {
4799
+ if (typeof value !== "object" || value === null) return false;
4800
+ const candidate = value;
4801
+ if (typeof candidate.version !== "string") return false;
4802
+ if (typeof candidate.hash !== "string") return false;
4803
+ if (candidate.entryPoint !== void 0 && !isEntryPoint(candidate.entryPoint)) return false;
4804
+ if (candidate.configHash !== void 0 && typeof candidate.configHash !== "string") return false;
4805
+ if (candidate.configKeyHashes !== void 0 && !isStringRecord(candidate.configKeyHashes)) {
4806
+ return false;
4807
+ }
4808
+ if (candidate.extraInputs !== void 0 && (!Array.isArray(candidate.extraInputs) || !candidate.extraInputs.every((entry) => typeof entry === "string"))) {
4809
+ return false;
4810
+ }
4811
+ if (!Array.isArray(candidate.files)) return false;
4812
+ return candidate.files.every((entry) => typeof entry === "string");
4813
+ }
4814
+ function isStringRecord(value) {
4815
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
4816
+ return Object.values(value).every((entry) => typeof entry === "string");
4817
+ }
4818
+ function serializeConfig(config) {
4819
+ return serializeConfigValue(config, `unserializable:${config.codegen.outDir}`);
4820
+ }
4821
+ function serializeConfigValue(value, unserializableMarker) {
4822
+ try {
4823
+ return JSON.stringify(value, (_key, entry) => {
4824
+ if (typeof entry === "function") return `[fn:${entry.name}]`;
4825
+ return entry;
4826
+ });
4827
+ } catch {
4828
+ return unserializableMarker;
4829
+ }
4830
+ }
4831
+ function computeConfigKeyHashes(config) {
4832
+ const hashes = {};
4833
+ for (const [key, value] of Object.entries(config)) {
4834
+ if (value === void 0) continue;
4835
+ hashes[key] = (0, import_node_crypto.createHash)("sha256").update(serializeConfigValue(value, `unserializable:${key}`)).digest("hex");
4836
+ }
4837
+ return hashes;
4838
+ }
4839
+ function diffConfigKeyHashes(previous, current) {
4840
+ const keys = /* @__PURE__ */ new Set([...Object.keys(previous), ...Object.keys(current)]);
4841
+ return [...keys].filter((key) => previous[key] !== current[key]).sort();
4842
+ }
4843
+ async function discoverInputFiles(config) {
4844
+ const globs = [config.contracts.glob, config.forms.watch];
4845
+ if (config.pages) globs.push(config.pages.glob);
4846
+ const cwd = config.codegen.cwd;
4847
+ const matched = await (0, import_fast_glob3.default)(globs, { cwd, absolute: true, onlyFiles: true });
4848
+ return [...new Set(matched)].sort();
4849
+ }
4850
+ async function computeInputsHash(config, extraInputs = []) {
4851
+ const hash = (0, import_node_crypto.createHash)("sha256");
4852
+ hash.update(`version:${VERSION}
4853
+ `);
4854
+ hash.update(`config:${serializeConfig(config)}
4855
+ `);
4856
+ const cwd = config.codegen.cwd;
4857
+ const tsconfigPath = resolveTsconfigPath(cwd, config.app?.tsconfig ?? void 0);
4858
+ const tsconfigFiles = loadDiscoveryTsconfig(tsconfigPath).files;
4859
+ for (const file of tsconfigFiles.length > 0 ? tsconfigFiles : [tsconfigPath]) {
4860
+ const contents = await (0, import_promises11.readFile)(file, "utf8").catch(() => null);
4861
+ hash.update(`tsconfig:${(0, import_node_path15.relative)(cwd, file)}
4862
+ `);
4863
+ hash.update(contents ?? " missing");
4864
+ hash.update("\n");
4865
+ }
4866
+ const globbed = await discoverInputFiles(config);
4867
+ const globbedRelative = new Set(globbed.map((file) => (0, import_node_path15.relative)(cwd, file)));
4868
+ const extra = [...new Set(extraInputs)].filter((file) => !globbedRelative.has(file)).sort();
4869
+ for (const file of globbed) {
4870
+ const contents = await (0, import_promises11.readFile)(file, "utf8");
4871
+ hash.update(`file:${(0, import_node_path15.relative)(cwd, file)}
4872
+ `);
4873
+ hash.update(contents);
4874
+ hash.update("\n");
4875
+ }
4876
+ for (const file of extra) {
4877
+ const contents = await (0, import_promises11.readFile)((0, import_node_path15.join)(cwd, file), "utf8").catch(() => null);
4878
+ hash.update(`extra:${file}
4879
+ `);
4880
+ hash.update(contents ?? "\0missing");
4881
+ hash.update("\n");
4882
+ }
4883
+ return hash.digest("hex");
4884
+ }
4885
+ async function readManifest(outDir) {
4886
+ try {
4887
+ const raw = await (0, import_promises11.readFile)((0, import_node_path15.join)(outDir, MANIFEST_FILE), "utf8");
4888
+ const parsed = JSON.parse(raw);
4889
+ if (!isManifestShape(parsed)) return null;
4890
+ return {
4891
+ version: parsed.version,
4892
+ hash: parsed.hash,
4893
+ ...parsed.entryPoint ? { entryPoint: parsed.entryPoint } : {},
4894
+ ...parsed.configHash ? { configHash: parsed.configHash } : {},
4895
+ ...parsed.configKeyHashes ? { configKeyHashes: parsed.configKeyHashes } : {},
4896
+ ...parsed.extraInputs ? { extraInputs: parsed.extraInputs } : {},
4897
+ files: parsed.files
4898
+ };
4899
+ } catch {
4900
+ return null;
4901
+ }
4902
+ }
4903
+ function computeConfigHash(config) {
4904
+ return (0, import_node_crypto.createHash)("sha256").update(serializeConfig(config)).digest("hex");
4905
+ }
4906
+ async function writeManifest(outDir, manifest) {
4907
+ await (0, import_promises11.writeFile)((0, import_node_path15.join)(outDir, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}
4908
+ `, "utf8");
4909
+ }
4910
+ async function listOutputFiles(outDir) {
4911
+ const found = [];
4912
+ async function walk(dir) {
4913
+ const entries = await (0, import_promises11.readdir)(dir, { withFileTypes: true }).catch(() => []);
4914
+ for (const entry of entries) {
4915
+ const abs = (0, import_node_path15.join)(dir, entry.name);
4916
+ if (entry.isDirectory()) {
4917
+ await walk(abs);
4918
+ } else if (entry.isFile()) {
4919
+ const rel = (0, import_node_path15.relative)(outDir, abs);
4920
+ if (rel === MANIFEST_FILE || rel === LOCK_FILE) continue;
4921
+ found.push(rel);
4922
+ }
4923
+ }
4924
+ }
4925
+ await walk(outDir);
4926
+ return found.sort();
4927
+ }
4928
+ async function allOutputsExist(outDir, files) {
4929
+ const present = new Set(await listOutputFiles(outDir));
4930
+ return files.every((file) => present.has(file));
4931
+ }
4932
+ async function isManifestFresh(outDir, manifest, inputsHash) {
4933
+ if (manifest === null) return false;
4934
+ if (manifest.version !== VERSION) return false;
4935
+ if (manifest.hash !== inputsHash) return false;
4936
+ if (manifest.files.length === 0) return false;
4937
+ return allOutputsExist(outDir, manifest.files);
4938
+ }
4939
+
4940
+ // src/generate.ts
4941
+ function driftGuardMessage(outDir, previousEntryPoint, currentEntryPoint, differingKeys) {
4942
+ 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)";
4943
+ 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.`;
4944
+ }
4945
+ async function generate(config, inputRoutes = [], entryPoint = "cli") {
4946
+ setCodegenDebug(config.debug);
4947
+ const manifest = await readManifest(config.codegen.outDir);
4948
+ const inputsHash = await computeInputsHash(config, manifest?.extraInputs ?? []);
4949
+ if (await isManifestFresh(config.codegen.outDir, manifest, inputsHash)) {
4950
+ console.log(`[nestjs-codegen] ${config.codegen.outDir} up to date, skipped`);
4951
+ return;
4952
+ }
4953
+ const configHash = computeConfigHash(config);
4954
+ const configKeyHashes = computeConfigKeyHashes(config);
4955
+ if (config.driftGuard && manifest?.entryPoint && manifest.entryPoint !== entryPoint && manifest.configHash && manifest.configHash !== configHash) {
4956
+ throw new DriftGuardError(
4957
+ driftGuardMessage(
4958
+ config.codegen.outDir,
4959
+ manifest.entryPoint,
4960
+ entryPoint,
4961
+ // A pre-key-hash manifest can't tell us WHICH keys differ — pass none
4962
+ // rather than diffing against {} (which would name every key).
4963
+ manifest.configKeyHashes ? diffConfigKeyHashes(manifest.configKeyHashes, configKeyHashes) : []
4964
+ )
4965
+ );
4966
+ }
4967
+ const extensions = config.extensions ?? [];
4968
+ let routes = inputRoutes;
4969
+ const trackedInputs = /* @__PURE__ */ new Set();
4970
+ const ctx = createExtensionContext(config, () => routes, trackedInputs);
4971
+ if (extensions.length > 0) {
4972
+ routes = await applyTransformRoutes(routes, extensions, ctx);
4973
+ }
4974
+ if (config.pages) {
4975
+ const pagesConfig = config.pages;
4976
+ const pages = await discoverPages({
4977
+ glob: pagesConfig.glob,
4978
+ cwd: config.codegen.cwd,
4979
+ propsExport: pagesConfig.propsExport,
4980
+ componentNameStrategy: pagesConfig.componentNameStrategy
4981
+ });
4982
+ const sharedProps = discoverSharedPropsFromConfig(config);
4983
+ await emitPages(pages, config.codegen.outDir, {
4984
+ propsExport: pagesConfig.propsExport,
4985
+ sharedProps
4986
+ });
4987
+ await emitCache(pages, config.codegen.outDir);
4988
+ }
4989
+ const hasRoutes = routes.length > 0;
4990
+ const hasContracts = routes.some((r) => r.contract);
4991
+ if (hasRoutes) {
4992
+ await emitRoutes(routes, config.codegen.outDir);
4993
+ }
4994
+ if (hasContracts) {
4995
+ await emitApi(routes, config.codegen.outDir, {
4996
+ ...config.fetcher?.importPath ? { fetcherImportPath: config.fetcher.importPath } : {},
4997
+ serialization: config.serialization,
4998
+ extensions,
4999
+ ctx
5000
+ });
5001
+ }
5002
+ const hasForms = await emitForms(routes, config.codegen.outDir, config.forms, config.validation);
5003
+ if (hasContracts && config.openapi.enabled) {
5004
+ await emitOpenApi(routes, config.codegen.outDir, {
5005
+ fileName: config.openapi.fileName,
5006
+ info: {
5007
+ title: config.openapi.title,
5008
+ version: config.openapi.version,
5009
+ ...config.openapi.description ? { description: config.openapi.description } : {}
5010
+ }
5011
+ });
5012
+ }
5013
+ if (hasContracts && config.mocks.enabled) {
5014
+ await emitMocks(routes, config.codegen.outDir, {
5015
+ fileName: config.mocks.fileName,
5016
+ seed: config.mocks.seed,
5017
+ baseUrl: config.mocks.baseUrl
5018
+ });
5019
+ }
5020
+ await emitIndex(config.codegen.outDir, hasContracts, hasForms);
5021
+ if (extensions.length > 0) {
5022
+ const extraFiles = await collectEmittedFiles(extensions, ctx);
5023
+ for (const file of extraFiles) {
5024
+ const dest = (0, import_node_path16.join)(config.codegen.outDir, file.path);
5025
+ await (0, import_promises12.mkdir)((0, import_node_path16.dirname)(dest), { recursive: true });
5026
+ await (0, import_promises12.writeFile)(dest, file.contents, "utf8");
5027
+ }
5028
+ }
5029
+ const outputFiles = await listOutputFiles(config.codegen.outDir);
5030
+ const extraInputs = [...trackedInputs].sort();
5031
+ const recordedHash = extraInputs.length > 0 ? await computeInputsHash(config, extraInputs) : inputsHash;
5032
+ await writeManifest(config.codegen.outDir, {
5033
+ version: VERSION,
5034
+ hash: recordedHash,
5035
+ entryPoint,
5036
+ configHash,
5037
+ configKeyHashes,
5038
+ files: outputFiles,
5039
+ ...extraInputs.length > 0 ? { extraInputs } : {}
5040
+ });
5041
+ }
5042
+
5043
+ // src/watch/watcher.ts
5044
+ var import_promises15 = require("fs/promises");
5045
+ var import_node_path18 = require("path");
5046
+ var import_chokidar = __toESM(require("chokidar"), 1);
5047
+
5009
5048
  // src/watch/lock-file.ts
5010
5049
  var import_promises13 = require("fs/promises");
5011
5050
  var import_promises14 = require("fs/promises");
@@ -5199,7 +5238,7 @@ async function watch(config, onChange, options = {}) {
5199
5238
  }
5200
5239
 
5201
5240
  // src/index.ts
5202
- var VERSION = "0.20.0";
5241
+ var VERSION = "0.21.1";
5203
5242
 
5204
5243
  // src/cli/codegen.ts
5205
5244
  async function runCodegen(opts = {}) {