@dudousxd/nestjs-codegen 0.21.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/CHANGELOG.md +64 -0
- package/dist/cli/main.cjs +485 -446
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.js +364 -324
- package/dist/cli/main.js.map +1 -1
- package/dist/index.cjs +485 -446
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +350 -310
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +4205 -4169
- package/dist/nest/index.cjs.map +1 -1
- package/dist/nest/index.js +4233 -4196
- package/dist/nest/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
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
|
|
2258
|
-
if (
|
|
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
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
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
|
|
2278
|
-
return
|
|
2276
|
+
function _ctxFor(project) {
|
|
2277
|
+
return _ctxByProject.get(project) ?? _EMPTY_CTX;
|
|
2279
2278
|
}
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
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
|
-
|
|
2287
|
-
return unserializableMarker;
|
|
2305
|
+
return { kind: "enum", members };
|
|
2288
2306
|
}
|
|
2307
|
+
return null;
|
|
2289
2308
|
}
|
|
2290
|
-
function
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
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
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
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
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
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
|
|
2372
|
+
return resolveReExportedType(name, sourceFile, project, /* @__PURE__ */ new Set());
|
|
2334
2373
|
}
|
|
2335
|
-
|
|
2374
|
+
function resolveBareSpecifierType(name, importDecl, project) {
|
|
2375
|
+
let target;
|
|
2336
2376
|
try {
|
|
2337
|
-
|
|
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
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
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
|
-
|
|
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);
|
|
@@ -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
|
|
4631
|
-
const
|
|
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,
|
|
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
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
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:
|
|
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
|
|
4703
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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)) {
|
|
@@ -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.21.
|
|
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,
|