@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/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
|
|
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
|
|
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
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
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
|
|
2223
|
-
if (
|
|
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
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
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
|
|
2243
|
-
return
|
|
2241
|
+
function _ctxFor(project) {
|
|
2242
|
+
return _ctxByProject.get(project) ?? _EMPTY_CTX;
|
|
2244
2243
|
}
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
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
|
-
|
|
2252
|
-
return unserializableMarker;
|
|
2270
|
+
return { kind: "enum", members };
|
|
2253
2271
|
}
|
|
2272
|
+
return null;
|
|
2254
2273
|
}
|
|
2255
|
-
function
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
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
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
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
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
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
|
|
2337
|
+
return resolveReExportedType(name, sourceFile, project, /* @__PURE__ */ new Set());
|
|
2299
2338
|
}
|
|
2300
|
-
|
|
2339
|
+
function resolveBareSpecifierType(name, importDecl, project) {
|
|
2340
|
+
let target;
|
|
2301
2341
|
try {
|
|
2302
|
-
|
|
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
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
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
|
-
|
|
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);
|
|
@@ -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
|
|
4596
|
-
const
|
|
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,
|
|
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
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
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:
|
|
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
|
|
4668
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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)) {
|
|
@@ -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.21.
|
|
5241
|
+
var VERSION = "0.21.1";
|
|
5203
5242
|
|
|
5204
5243
|
// src/cli/codegen.ts
|
|
5205
5244
|
async function runCodegen(opts = {}) {
|