@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.js
CHANGED
|
@@ -190,7 +190,7 @@ Run \`nestjs-codegen init\` to create a starter config.`
|
|
|
190
190
|
|
|
191
191
|
// src/generate.ts
|
|
192
192
|
import { mkdir as mkdir9, writeFile as writeFile10 } from "fs/promises";
|
|
193
|
-
import { dirname as
|
|
193
|
+
import { dirname as dirname4, join as join14 } from "path";
|
|
194
194
|
|
|
195
195
|
// src/discovery/pages.ts
|
|
196
196
|
import { readFile } from "fs/promises";
|
|
@@ -2180,275 +2180,17 @@ function buildEmpty() {
|
|
|
2180
2180
|
// src/generate-manifest.ts
|
|
2181
2181
|
import { createHash } from "crypto";
|
|
2182
2182
|
import { readFile as readFile2, readdir, writeFile as writeFile9 } from "fs/promises";
|
|
2183
|
-
import { join as
|
|
2184
|
-
import
|
|
2185
|
-
var MANIFEST_FILE = ".codegen-manifest.json";
|
|
2186
|
-
var LOCK_FILE = ".watcher.lock";
|
|
2187
|
-
var DriftGuardError = class extends Error {
|
|
2188
|
-
constructor(message) {
|
|
2189
|
-
super(message);
|
|
2190
|
-
this.name = "DriftGuardError";
|
|
2191
|
-
}
|
|
2192
|
-
};
|
|
2193
|
-
function isEntryPoint(value) {
|
|
2194
|
-
return value === "cli" || value === "module";
|
|
2195
|
-
}
|
|
2196
|
-
function isManifestShape(value) {
|
|
2197
|
-
if (typeof value !== "object" || value === null) return false;
|
|
2198
|
-
const candidate = value;
|
|
2199
|
-
if (typeof candidate.version !== "string") return false;
|
|
2200
|
-
if (typeof candidate.hash !== "string") return false;
|
|
2201
|
-
if (candidate.entryPoint !== void 0 && !isEntryPoint(candidate.entryPoint)) return false;
|
|
2202
|
-
if (candidate.configHash !== void 0 && typeof candidate.configHash !== "string") return false;
|
|
2203
|
-
if (candidate.configKeyHashes !== void 0 && !isStringRecord(candidate.configKeyHashes)) {
|
|
2204
|
-
return false;
|
|
2205
|
-
}
|
|
2206
|
-
if (candidate.extraInputs !== void 0 && (!Array.isArray(candidate.extraInputs) || !candidate.extraInputs.every((entry) => typeof entry === "string"))) {
|
|
2207
|
-
return false;
|
|
2208
|
-
}
|
|
2209
|
-
if (!Array.isArray(candidate.files)) return false;
|
|
2210
|
-
return candidate.files.every((entry) => typeof entry === "string");
|
|
2211
|
-
}
|
|
2212
|
-
function isStringRecord(value) {
|
|
2213
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
2214
|
-
return Object.values(value).every((entry) => typeof entry === "string");
|
|
2215
|
-
}
|
|
2216
|
-
function serializeConfig(config) {
|
|
2217
|
-
return serializeConfigValue(config, `unserializable:${config.codegen.outDir}`);
|
|
2218
|
-
}
|
|
2219
|
-
function serializeConfigValue(value, unserializableMarker) {
|
|
2220
|
-
try {
|
|
2221
|
-
return JSON.stringify(value, (_key, entry) => {
|
|
2222
|
-
if (typeof entry === "function") return `[fn:${entry.name}]`;
|
|
2223
|
-
return entry;
|
|
2224
|
-
});
|
|
2225
|
-
} catch {
|
|
2226
|
-
return unserializableMarker;
|
|
2227
|
-
}
|
|
2228
|
-
}
|
|
2229
|
-
function computeConfigKeyHashes(config) {
|
|
2230
|
-
const hashes = {};
|
|
2231
|
-
for (const [key, value] of Object.entries(config)) {
|
|
2232
|
-
if (value === void 0) continue;
|
|
2233
|
-
hashes[key] = createHash("sha256").update(serializeConfigValue(value, `unserializable:${key}`)).digest("hex");
|
|
2234
|
-
}
|
|
2235
|
-
return hashes;
|
|
2236
|
-
}
|
|
2237
|
-
function diffConfigKeyHashes(previous, current) {
|
|
2238
|
-
const keys = /* @__PURE__ */ new Set([...Object.keys(previous), ...Object.keys(current)]);
|
|
2239
|
-
return [...keys].filter((key) => previous[key] !== current[key]).sort();
|
|
2240
|
-
}
|
|
2241
|
-
async function discoverInputFiles(config) {
|
|
2242
|
-
const globs = [config.contracts.glob, config.forms.watch];
|
|
2243
|
-
if (config.pages) globs.push(config.pages.glob);
|
|
2244
|
-
const cwd = config.codegen.cwd;
|
|
2245
|
-
const matched = await fg2(globs, { cwd, absolute: true, onlyFiles: true });
|
|
2246
|
-
return [...new Set(matched)].sort();
|
|
2247
|
-
}
|
|
2248
|
-
async function computeInputsHash(config, extraInputs = []) {
|
|
2249
|
-
const hash = createHash("sha256");
|
|
2250
|
-
hash.update(`version:${VERSION}
|
|
2251
|
-
`);
|
|
2252
|
-
hash.update(`config:${serializeConfig(config)}
|
|
2253
|
-
`);
|
|
2254
|
-
const cwd = config.codegen.cwd;
|
|
2255
|
-
const globbed = await discoverInputFiles(config);
|
|
2256
|
-
const globbedRelative = new Set(globbed.map((file) => relative7(cwd, file)));
|
|
2257
|
-
const extra = [...new Set(extraInputs)].filter((file) => !globbedRelative.has(file)).sort();
|
|
2258
|
-
for (const file of globbed) {
|
|
2259
|
-
const contents = await readFile2(file, "utf8");
|
|
2260
|
-
hash.update(`file:${relative7(cwd, file)}
|
|
2261
|
-
`);
|
|
2262
|
-
hash.update(contents);
|
|
2263
|
-
hash.update("\n");
|
|
2264
|
-
}
|
|
2265
|
-
for (const file of extra) {
|
|
2266
|
-
const contents = await readFile2(join12(cwd, file), "utf8").catch(() => null);
|
|
2267
|
-
hash.update(`extra:${file}
|
|
2268
|
-
`);
|
|
2269
|
-
hash.update(contents ?? "\0missing");
|
|
2270
|
-
hash.update("\n");
|
|
2271
|
-
}
|
|
2272
|
-
return hash.digest("hex");
|
|
2273
|
-
}
|
|
2274
|
-
async function readManifest(outDir) {
|
|
2275
|
-
try {
|
|
2276
|
-
const raw = await readFile2(join12(outDir, MANIFEST_FILE), "utf8");
|
|
2277
|
-
const parsed = JSON.parse(raw);
|
|
2278
|
-
if (!isManifestShape(parsed)) return null;
|
|
2279
|
-
return {
|
|
2280
|
-
version: parsed.version,
|
|
2281
|
-
hash: parsed.hash,
|
|
2282
|
-
...parsed.entryPoint ? { entryPoint: parsed.entryPoint } : {},
|
|
2283
|
-
...parsed.configHash ? { configHash: parsed.configHash } : {},
|
|
2284
|
-
...parsed.configKeyHashes ? { configKeyHashes: parsed.configKeyHashes } : {},
|
|
2285
|
-
...parsed.extraInputs ? { extraInputs: parsed.extraInputs } : {},
|
|
2286
|
-
files: parsed.files
|
|
2287
|
-
};
|
|
2288
|
-
} catch {
|
|
2289
|
-
return null;
|
|
2290
|
-
}
|
|
2291
|
-
}
|
|
2292
|
-
function computeConfigHash(config) {
|
|
2293
|
-
return createHash("sha256").update(serializeConfig(config)).digest("hex");
|
|
2294
|
-
}
|
|
2295
|
-
async function writeManifest(outDir, manifest) {
|
|
2296
|
-
await writeFile9(join12(outDir, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}
|
|
2297
|
-
`, "utf8");
|
|
2298
|
-
}
|
|
2299
|
-
async function listOutputFiles(outDir) {
|
|
2300
|
-
const found = [];
|
|
2301
|
-
async function walk(dir) {
|
|
2302
|
-
const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
2303
|
-
for (const entry of entries) {
|
|
2304
|
-
const abs = join12(dir, entry.name);
|
|
2305
|
-
if (entry.isDirectory()) {
|
|
2306
|
-
await walk(abs);
|
|
2307
|
-
} else if (entry.isFile()) {
|
|
2308
|
-
const rel = relative7(outDir, abs);
|
|
2309
|
-
if (rel === MANIFEST_FILE || rel === LOCK_FILE) continue;
|
|
2310
|
-
found.push(rel);
|
|
2311
|
-
}
|
|
2312
|
-
}
|
|
2313
|
-
}
|
|
2314
|
-
await walk(outDir);
|
|
2315
|
-
return found.sort();
|
|
2316
|
-
}
|
|
2317
|
-
async function allOutputsExist(outDir, files) {
|
|
2318
|
-
const present = new Set(await listOutputFiles(outDir));
|
|
2319
|
-
return files.every((file) => present.has(file));
|
|
2320
|
-
}
|
|
2321
|
-
async function isManifestFresh(outDir, manifest, inputsHash) {
|
|
2322
|
-
if (manifest === null) return false;
|
|
2323
|
-
if (manifest.version !== VERSION) return false;
|
|
2324
|
-
if (manifest.hash !== inputsHash) return false;
|
|
2325
|
-
if (manifest.files.length === 0) return false;
|
|
2326
|
-
return allOutputsExist(outDir, manifest.files);
|
|
2327
|
-
}
|
|
2328
|
-
|
|
2329
|
-
// src/util/debug-log.ts
|
|
2330
|
-
var debugEnabled = false;
|
|
2331
|
-
function setCodegenDebug(enabled) {
|
|
2332
|
-
debugEnabled = enabled;
|
|
2333
|
-
}
|
|
2334
|
-
function debugWarn(message) {
|
|
2335
|
-
if (debugEnabled) console.warn(`[nestjs-codegen] ${message}`);
|
|
2336
|
-
}
|
|
2337
|
-
|
|
2338
|
-
// src/generate.ts
|
|
2339
|
-
function driftGuardMessage(outDir, previousEntryPoint, currentEntryPoint, differingKeys) {
|
|
2340
|
-
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)";
|
|
2341
|
-
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.`;
|
|
2342
|
-
}
|
|
2343
|
-
async function generate(config, inputRoutes = [], entryPoint = "cli") {
|
|
2344
|
-
setCodegenDebug(config.debug);
|
|
2345
|
-
const manifest = await readManifest(config.codegen.outDir);
|
|
2346
|
-
const inputsHash = await computeInputsHash(config, manifest?.extraInputs ?? []);
|
|
2347
|
-
if (await isManifestFresh(config.codegen.outDir, manifest, inputsHash)) {
|
|
2348
|
-
console.log(`[nestjs-codegen] ${config.codegen.outDir} up to date, skipped`);
|
|
2349
|
-
return;
|
|
2350
|
-
}
|
|
2351
|
-
const configHash = computeConfigHash(config);
|
|
2352
|
-
const configKeyHashes = computeConfigKeyHashes(config);
|
|
2353
|
-
if (config.driftGuard && manifest?.entryPoint && manifest.entryPoint !== entryPoint && manifest.configHash && manifest.configHash !== configHash) {
|
|
2354
|
-
throw new DriftGuardError(
|
|
2355
|
-
driftGuardMessage(
|
|
2356
|
-
config.codegen.outDir,
|
|
2357
|
-
manifest.entryPoint,
|
|
2358
|
-
entryPoint,
|
|
2359
|
-
// A pre-key-hash manifest can't tell us WHICH keys differ — pass none
|
|
2360
|
-
// rather than diffing against {} (which would name every key).
|
|
2361
|
-
manifest.configKeyHashes ? diffConfigKeyHashes(manifest.configKeyHashes, configKeyHashes) : []
|
|
2362
|
-
)
|
|
2363
|
-
);
|
|
2364
|
-
}
|
|
2365
|
-
const extensions = config.extensions ?? [];
|
|
2366
|
-
let routes = inputRoutes;
|
|
2367
|
-
const trackedInputs = /* @__PURE__ */ new Set();
|
|
2368
|
-
const ctx = createExtensionContext(config, () => routes, trackedInputs);
|
|
2369
|
-
if (extensions.length > 0) {
|
|
2370
|
-
routes = await applyTransformRoutes(routes, extensions, ctx);
|
|
2371
|
-
}
|
|
2372
|
-
if (config.pages) {
|
|
2373
|
-
const pagesConfig = config.pages;
|
|
2374
|
-
const pages = await discoverPages({
|
|
2375
|
-
glob: pagesConfig.glob,
|
|
2376
|
-
cwd: config.codegen.cwd,
|
|
2377
|
-
propsExport: pagesConfig.propsExport,
|
|
2378
|
-
componentNameStrategy: pagesConfig.componentNameStrategy
|
|
2379
|
-
});
|
|
2380
|
-
const sharedProps = discoverSharedPropsFromConfig(config);
|
|
2381
|
-
await emitPages(pages, config.codegen.outDir, {
|
|
2382
|
-
propsExport: pagesConfig.propsExport,
|
|
2383
|
-
sharedProps
|
|
2384
|
-
});
|
|
2385
|
-
await emitCache(pages, config.codegen.outDir);
|
|
2386
|
-
}
|
|
2387
|
-
const hasRoutes = routes.length > 0;
|
|
2388
|
-
const hasContracts = routes.some((r) => r.contract);
|
|
2389
|
-
if (hasRoutes) {
|
|
2390
|
-
await emitRoutes(routes, config.codegen.outDir);
|
|
2391
|
-
}
|
|
2392
|
-
if (hasContracts) {
|
|
2393
|
-
await emitApi(routes, config.codegen.outDir, {
|
|
2394
|
-
...config.fetcher?.importPath ? { fetcherImportPath: config.fetcher.importPath } : {},
|
|
2395
|
-
serialization: config.serialization,
|
|
2396
|
-
extensions,
|
|
2397
|
-
ctx
|
|
2398
|
-
});
|
|
2399
|
-
}
|
|
2400
|
-
const hasForms = await emitForms(routes, config.codegen.outDir, config.forms, config.validation);
|
|
2401
|
-
if (hasContracts && config.openapi.enabled) {
|
|
2402
|
-
await emitOpenApi(routes, config.codegen.outDir, {
|
|
2403
|
-
fileName: config.openapi.fileName,
|
|
2404
|
-
info: {
|
|
2405
|
-
title: config.openapi.title,
|
|
2406
|
-
version: config.openapi.version,
|
|
2407
|
-
...config.openapi.description ? { description: config.openapi.description } : {}
|
|
2408
|
-
}
|
|
2409
|
-
});
|
|
2410
|
-
}
|
|
2411
|
-
if (hasContracts && config.mocks.enabled) {
|
|
2412
|
-
await emitMocks(routes, config.codegen.outDir, {
|
|
2413
|
-
fileName: config.mocks.fileName,
|
|
2414
|
-
seed: config.mocks.seed,
|
|
2415
|
-
baseUrl: config.mocks.baseUrl
|
|
2416
|
-
});
|
|
2417
|
-
}
|
|
2418
|
-
await emitIndex(config.codegen.outDir, hasContracts, hasForms);
|
|
2419
|
-
if (extensions.length > 0) {
|
|
2420
|
-
const extraFiles = await collectEmittedFiles(extensions, ctx);
|
|
2421
|
-
for (const file of extraFiles) {
|
|
2422
|
-
const dest = join13(config.codegen.outDir, file.path);
|
|
2423
|
-
await mkdir9(dirname2(dest), { recursive: true });
|
|
2424
|
-
await writeFile10(dest, file.contents, "utf8");
|
|
2425
|
-
}
|
|
2426
|
-
}
|
|
2427
|
-
const outputFiles = await listOutputFiles(config.codegen.outDir);
|
|
2428
|
-
const extraInputs = [...trackedInputs].sort();
|
|
2429
|
-
const recordedHash = extraInputs.length > 0 ? await computeInputsHash(config, extraInputs) : inputsHash;
|
|
2430
|
-
await writeManifest(config.codegen.outDir, {
|
|
2431
|
-
version: VERSION,
|
|
2432
|
-
hash: recordedHash,
|
|
2433
|
-
entryPoint,
|
|
2434
|
-
configHash,
|
|
2435
|
-
configKeyHashes,
|
|
2436
|
-
files: outputFiles,
|
|
2437
|
-
...extraInputs.length > 0 ? { extraInputs } : {}
|
|
2438
|
-
});
|
|
2439
|
-
}
|
|
2440
|
-
|
|
2441
|
-
// src/watch/watcher.ts
|
|
2442
|
-
import { readFile as readFile4 } from "fs/promises";
|
|
2443
|
-
import { join as join16 } from "path";
|
|
2444
|
-
import chokidar from "chokidar";
|
|
2183
|
+
import { join as join13, relative as relative7 } from "path";
|
|
2184
|
+
import fg3 from "fast-glob";
|
|
2445
2185
|
|
|
2446
2186
|
// src/discovery/contracts-fast.ts
|
|
2447
|
-
import {
|
|
2448
|
-
import
|
|
2187
|
+
import { existsSync } from "fs";
|
|
2188
|
+
import { dirname as dirname3, join as join12, resolve as resolve4 } from "path";
|
|
2189
|
+
import fg2 from "fast-glob";
|
|
2449
2190
|
import {
|
|
2450
2191
|
Node as Node9,
|
|
2451
|
-
Project as Project4
|
|
2192
|
+
Project as Project4,
|
|
2193
|
+
ts
|
|
2452
2194
|
} from "ts-morph";
|
|
2453
2195
|
|
|
2454
2196
|
// src/discovery/dto-type-resolver.ts
|
|
@@ -2462,9 +2204,17 @@ import {
|
|
|
2462
2204
|
Node as Node3
|
|
2463
2205
|
} from "ts-morph";
|
|
2464
2206
|
|
|
2207
|
+
// src/util/debug-log.ts
|
|
2208
|
+
var debugEnabled = false;
|
|
2209
|
+
function setCodegenDebug(enabled) {
|
|
2210
|
+
debugEnabled = enabled;
|
|
2211
|
+
}
|
|
2212
|
+
function debugWarn(message) {
|
|
2213
|
+
if (debugEnabled) console.warn(`[nestjs-codegen] ${message}`);
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2465
2216
|
// src/discovery/type-ref-resolution.ts
|
|
2466
|
-
import {
|
|
2467
|
-
import { dirname as dirname3, resolve as resolve3 } from "path";
|
|
2217
|
+
import { dirname as dirname2, resolve as resolve3 } from "path";
|
|
2468
2218
|
import {
|
|
2469
2219
|
Node as Node2
|
|
2470
2220
|
} from "ts-morph";
|
|
@@ -2480,16 +2230,6 @@ var _debug = process.env.NESTJS_INERTIA_DEBUG === "1";
|
|
|
2480
2230
|
function dbg(...args) {
|
|
2481
2231
|
if (_debug) console.log("[codegen:debug]", ...args);
|
|
2482
2232
|
}
|
|
2483
|
-
function loadTsconfigPaths(tsconfigPath) {
|
|
2484
|
-
try {
|
|
2485
|
-
const raw = readFileSync(tsconfigPath, "utf8");
|
|
2486
|
-
const stripped = raw.replace(/\/\/.*$/gm, "");
|
|
2487
|
-
const parsed = JSON.parse(stripped);
|
|
2488
|
-
return parsed.compilerOptions?.paths ?? null;
|
|
2489
|
-
} catch {
|
|
2490
|
-
return null;
|
|
2491
|
-
}
|
|
2492
|
-
}
|
|
2493
2233
|
function findTypeInFile(name, file) {
|
|
2494
2234
|
const cls = file.getClass(name);
|
|
2495
2235
|
if (cls) return { kind: "class", decl: cls, file };
|
|
@@ -2518,7 +2258,7 @@ function findTypeInFile(name, file) {
|
|
|
2518
2258
|
}
|
|
2519
2259
|
function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
|
|
2520
2260
|
if (moduleSpecifier.startsWith(".")) {
|
|
2521
|
-
const dir =
|
|
2261
|
+
const dir = dirname2(sourceFile.getFilePath());
|
|
2522
2262
|
const noExt = moduleSpecifier.replace(/\.(js|ts)$/, "");
|
|
2523
2263
|
return [
|
|
2524
2264
|
resolve3(dir, `${noExt}.ts`),
|
|
@@ -2527,7 +2267,7 @@ function resolveModuleSpecifier(moduleSpecifier, sourceFile, project) {
|
|
|
2527
2267
|
];
|
|
2528
2268
|
}
|
|
2529
2269
|
const ctx = _ctxFor(project);
|
|
2530
|
-
const baseUrl = ctx.projectRoot;
|
|
2270
|
+
const baseUrl = ctx.pathsBase ?? ctx.projectRoot;
|
|
2531
2271
|
const tsconfigPaths = ctx.tsconfigPaths;
|
|
2532
2272
|
dbg(
|
|
2533
2273
|
"resolveModuleSpecifier",
|
|
@@ -4581,43 +4321,80 @@ function parseDefineContractCall(callExpr) {
|
|
|
4581
4321
|
async function discoverContractsFast(opts) {
|
|
4582
4322
|
const { cwd, glob, tsconfig } = opts;
|
|
4583
4323
|
const tsconfigPath = resolveTsconfigPath(cwd, tsconfig);
|
|
4584
|
-
const
|
|
4585
|
-
const
|
|
4324
|
+
const loaded = loadDiscoveryTsconfig(tsconfigPath);
|
|
4325
|
+
const project = createDiscoveryProject(tsconfigPath, loaded);
|
|
4326
|
+
const files = await fg2(glob, { cwd, absolute: true, onlyFiles: true });
|
|
4586
4327
|
for (const f of files) {
|
|
4587
4328
|
project.addSourceFileAtPath(f);
|
|
4588
4329
|
}
|
|
4589
|
-
bindDiscoveryContext(project, cwd, tsconfigPath);
|
|
4330
|
+
bindDiscoveryContext(project, cwd, tsconfigPath, loaded);
|
|
4590
4331
|
clearMixinTypeProject();
|
|
4591
4332
|
return extractAllRoutes(project);
|
|
4592
4333
|
}
|
|
4593
4334
|
function resolveTsconfigPath(cwd, tsconfig) {
|
|
4594
|
-
return tsconfig ? resolve4(tsconfig) :
|
|
4335
|
+
return tsconfig ? resolve4(tsconfig) : join12(cwd, "tsconfig.json");
|
|
4595
4336
|
}
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
return
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4337
|
+
var NO_TSCONFIG_OPTIONS = {
|
|
4338
|
+
allowJs: true,
|
|
4339
|
+
resolveJsonModule: false,
|
|
4340
|
+
strict: false
|
|
4341
|
+
};
|
|
4342
|
+
function loadDiscoveryTsconfig(tsconfigPath) {
|
|
4343
|
+
if (!existsSync(tsconfigPath)) return { options: null, files: [] };
|
|
4344
|
+
const read = ts.readConfigFile(tsconfigPath, (path) => ts.sys.readFile(path));
|
|
4345
|
+
if (read.error) {
|
|
4346
|
+
return {
|
|
4347
|
+
options: null,
|
|
4348
|
+
files: [tsconfigPath],
|
|
4349
|
+
error: ts.flattenDiagnosticMessageText(read.error.messageText, " ")
|
|
4350
|
+
};
|
|
4351
|
+
}
|
|
4352
|
+
const host = {
|
|
4353
|
+
useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
|
|
4354
|
+
// The one line that keeps a tsconfig from being a filesystem walk.
|
|
4355
|
+
readDirectory: () => [],
|
|
4356
|
+
fileExists: (path) => ts.sys.fileExists(path),
|
|
4357
|
+
// `extends` is resolved through the host, so a consumer whose `paths` live
|
|
4358
|
+
// in a base tsconfig keeps working.
|
|
4359
|
+
readFile: (path) => ts.sys.readFile(path)
|
|
4360
|
+
};
|
|
4361
|
+
const sourceFile = ts.readJsonConfigFile(tsconfigPath, (path) => ts.sys.readFile(path));
|
|
4362
|
+
const parsed = ts.parseJsonSourceFileConfigFileContent(
|
|
4363
|
+
sourceFile,
|
|
4364
|
+
host,
|
|
4365
|
+
dirname3(tsconfigPath),
|
|
4366
|
+
void 0,
|
|
4367
|
+
tsconfigPath
|
|
4368
|
+
);
|
|
4369
|
+
return {
|
|
4370
|
+
options: parsed.options,
|
|
4371
|
+
files: [tsconfigPath, ...sourceFile.extendedSourceFiles ?? []]
|
|
4372
|
+
};
|
|
4373
|
+
}
|
|
4374
|
+
function pathsBaseDir(options, fallback) {
|
|
4375
|
+
if (typeof options.baseUrl === "string") return options.baseUrl;
|
|
4376
|
+
if (typeof options.pathsBasePath === "string") return options.pathsBasePath;
|
|
4377
|
+
return fallback;
|
|
4378
|
+
}
|
|
4379
|
+
function createDiscoveryProject(tsconfigPath, tsconfig = loadDiscoveryTsconfig(tsconfigPath)) {
|
|
4380
|
+
if (tsconfig.error) {
|
|
4381
|
+
console.warn(
|
|
4382
|
+
`[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.`
|
|
4383
|
+
);
|
|
4615
4384
|
}
|
|
4385
|
+
return new Project4({
|
|
4386
|
+
compilerOptions: tsconfig.options ?? { ...NO_TSCONFIG_OPTIONS },
|
|
4387
|
+
skipAddingFilesFromTsConfig: true,
|
|
4388
|
+
skipLoadingLibFiles: true,
|
|
4389
|
+
skipFileDependencyResolution: true
|
|
4390
|
+
});
|
|
4616
4391
|
}
|
|
4617
|
-
function bindDiscoveryContext(project, cwd, tsconfigPath) {
|
|
4392
|
+
function bindDiscoveryContext(project, cwd, tsconfigPath, tsconfig = loadDiscoveryTsconfig(tsconfigPath)) {
|
|
4393
|
+
const { options } = tsconfig;
|
|
4618
4394
|
setDiscoveryContext(project, {
|
|
4619
4395
|
projectRoot: cwd,
|
|
4620
|
-
tsconfigPaths:
|
|
4396
|
+
tsconfigPaths: options?.paths ?? null,
|
|
4397
|
+
pathsBase: options ? pathsBaseDir(options, cwd) : cwd
|
|
4621
4398
|
});
|
|
4622
4399
|
}
|
|
4623
4400
|
function extractRoutesFrom(project, controllerPaths) {
|
|
@@ -4653,10 +4430,11 @@ var PersistentDiscovery = class _PersistentDiscovery {
|
|
|
4653
4430
|
static async create(opts) {
|
|
4654
4431
|
const { cwd, glob, tsconfig } = opts;
|
|
4655
4432
|
const tsconfigPath = resolveTsconfigPath(cwd, tsconfig);
|
|
4656
|
-
const
|
|
4657
|
-
|
|
4433
|
+
const loaded = loadDiscoveryTsconfig(tsconfigPath);
|
|
4434
|
+
const project = createDiscoveryProject(tsconfigPath, loaded);
|
|
4435
|
+
bindDiscoveryContext(project, cwd, tsconfigPath, loaded);
|
|
4658
4436
|
const instance = new _PersistentDiscovery(project, cwd, glob);
|
|
4659
|
-
const files = await
|
|
4437
|
+
const files = await fg2(glob, { cwd, absolute: true, onlyFiles: true });
|
|
4660
4438
|
for (const f of files) {
|
|
4661
4439
|
project.addSourceFileAtPath(f);
|
|
4662
4440
|
instance.controllerPaths.add(f);
|
|
@@ -4685,7 +4463,7 @@ var PersistentDiscovery = class _PersistentDiscovery {
|
|
|
4685
4463
|
}
|
|
4686
4464
|
}
|
|
4687
4465
|
const globbed = new Set(
|
|
4688
|
-
await
|
|
4466
|
+
await fg2(this.glob, { cwd: this.cwd, absolute: true, onlyFiles: true })
|
|
4689
4467
|
);
|
|
4690
4468
|
for (const f of globbed) {
|
|
4691
4469
|
if (!this.controllerPaths.has(f)) {
|
|
@@ -4995,6 +4773,268 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
4995
4773
|
return routes;
|
|
4996
4774
|
}
|
|
4997
4775
|
|
|
4776
|
+
// src/generate-manifest.ts
|
|
4777
|
+
var MANIFEST_FILE = ".codegen-manifest.json";
|
|
4778
|
+
var LOCK_FILE = ".watcher.lock";
|
|
4779
|
+
var DriftGuardError = class extends Error {
|
|
4780
|
+
constructor(message) {
|
|
4781
|
+
super(message);
|
|
4782
|
+
this.name = "DriftGuardError";
|
|
4783
|
+
}
|
|
4784
|
+
};
|
|
4785
|
+
function isEntryPoint(value) {
|
|
4786
|
+
return value === "cli" || value === "module";
|
|
4787
|
+
}
|
|
4788
|
+
function isManifestShape(value) {
|
|
4789
|
+
if (typeof value !== "object" || value === null) return false;
|
|
4790
|
+
const candidate = value;
|
|
4791
|
+
if (typeof candidate.version !== "string") return false;
|
|
4792
|
+
if (typeof candidate.hash !== "string") return false;
|
|
4793
|
+
if (candidate.entryPoint !== void 0 && !isEntryPoint(candidate.entryPoint)) return false;
|
|
4794
|
+
if (candidate.configHash !== void 0 && typeof candidate.configHash !== "string") return false;
|
|
4795
|
+
if (candidate.configKeyHashes !== void 0 && !isStringRecord(candidate.configKeyHashes)) {
|
|
4796
|
+
return false;
|
|
4797
|
+
}
|
|
4798
|
+
if (candidate.extraInputs !== void 0 && (!Array.isArray(candidate.extraInputs) || !candidate.extraInputs.every((entry) => typeof entry === "string"))) {
|
|
4799
|
+
return false;
|
|
4800
|
+
}
|
|
4801
|
+
if (!Array.isArray(candidate.files)) return false;
|
|
4802
|
+
return candidate.files.every((entry) => typeof entry === "string");
|
|
4803
|
+
}
|
|
4804
|
+
function isStringRecord(value) {
|
|
4805
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
4806
|
+
return Object.values(value).every((entry) => typeof entry === "string");
|
|
4807
|
+
}
|
|
4808
|
+
function serializeConfig(config) {
|
|
4809
|
+
return serializeConfigValue(config, `unserializable:${config.codegen.outDir}`);
|
|
4810
|
+
}
|
|
4811
|
+
function serializeConfigValue(value, unserializableMarker) {
|
|
4812
|
+
try {
|
|
4813
|
+
return JSON.stringify(value, (_key, entry) => {
|
|
4814
|
+
if (typeof entry === "function") return `[fn:${entry.name}]`;
|
|
4815
|
+
return entry;
|
|
4816
|
+
});
|
|
4817
|
+
} catch {
|
|
4818
|
+
return unserializableMarker;
|
|
4819
|
+
}
|
|
4820
|
+
}
|
|
4821
|
+
function computeConfigKeyHashes(config) {
|
|
4822
|
+
const hashes = {};
|
|
4823
|
+
for (const [key, value] of Object.entries(config)) {
|
|
4824
|
+
if (value === void 0) continue;
|
|
4825
|
+
hashes[key] = createHash("sha256").update(serializeConfigValue(value, `unserializable:${key}`)).digest("hex");
|
|
4826
|
+
}
|
|
4827
|
+
return hashes;
|
|
4828
|
+
}
|
|
4829
|
+
function diffConfigKeyHashes(previous, current) {
|
|
4830
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(previous), ...Object.keys(current)]);
|
|
4831
|
+
return [...keys].filter((key) => previous[key] !== current[key]).sort();
|
|
4832
|
+
}
|
|
4833
|
+
async function discoverInputFiles(config) {
|
|
4834
|
+
const globs = [config.contracts.glob, config.forms.watch];
|
|
4835
|
+
if (config.pages) globs.push(config.pages.glob);
|
|
4836
|
+
const cwd = config.codegen.cwd;
|
|
4837
|
+
const matched = await fg3(globs, { cwd, absolute: true, onlyFiles: true });
|
|
4838
|
+
return [...new Set(matched)].sort();
|
|
4839
|
+
}
|
|
4840
|
+
async function computeInputsHash(config, extraInputs = []) {
|
|
4841
|
+
const hash = createHash("sha256");
|
|
4842
|
+
hash.update(`version:${VERSION}
|
|
4843
|
+
`);
|
|
4844
|
+
hash.update(`config:${serializeConfig(config)}
|
|
4845
|
+
`);
|
|
4846
|
+
const cwd = config.codegen.cwd;
|
|
4847
|
+
const tsconfigPath = resolveTsconfigPath(cwd, config.app?.tsconfig ?? void 0);
|
|
4848
|
+
const tsconfigFiles = loadDiscoveryTsconfig(tsconfigPath).files;
|
|
4849
|
+
for (const file of tsconfigFiles.length > 0 ? tsconfigFiles : [tsconfigPath]) {
|
|
4850
|
+
const contents = await readFile2(file, "utf8").catch(() => null);
|
|
4851
|
+
hash.update(`tsconfig:${relative7(cwd, file)}
|
|
4852
|
+
`);
|
|
4853
|
+
hash.update(contents ?? " missing");
|
|
4854
|
+
hash.update("\n");
|
|
4855
|
+
}
|
|
4856
|
+
const globbed = await discoverInputFiles(config);
|
|
4857
|
+
const globbedRelative = new Set(globbed.map((file) => relative7(cwd, file)));
|
|
4858
|
+
const extra = [...new Set(extraInputs)].filter((file) => !globbedRelative.has(file)).sort();
|
|
4859
|
+
for (const file of globbed) {
|
|
4860
|
+
const contents = await readFile2(file, "utf8");
|
|
4861
|
+
hash.update(`file:${relative7(cwd, file)}
|
|
4862
|
+
`);
|
|
4863
|
+
hash.update(contents);
|
|
4864
|
+
hash.update("\n");
|
|
4865
|
+
}
|
|
4866
|
+
for (const file of extra) {
|
|
4867
|
+
const contents = await readFile2(join13(cwd, file), "utf8").catch(() => null);
|
|
4868
|
+
hash.update(`extra:${file}
|
|
4869
|
+
`);
|
|
4870
|
+
hash.update(contents ?? "\0missing");
|
|
4871
|
+
hash.update("\n");
|
|
4872
|
+
}
|
|
4873
|
+
return hash.digest("hex");
|
|
4874
|
+
}
|
|
4875
|
+
async function readManifest(outDir) {
|
|
4876
|
+
try {
|
|
4877
|
+
const raw = await readFile2(join13(outDir, MANIFEST_FILE), "utf8");
|
|
4878
|
+
const parsed = JSON.parse(raw);
|
|
4879
|
+
if (!isManifestShape(parsed)) return null;
|
|
4880
|
+
return {
|
|
4881
|
+
version: parsed.version,
|
|
4882
|
+
hash: parsed.hash,
|
|
4883
|
+
...parsed.entryPoint ? { entryPoint: parsed.entryPoint } : {},
|
|
4884
|
+
...parsed.configHash ? { configHash: parsed.configHash } : {},
|
|
4885
|
+
...parsed.configKeyHashes ? { configKeyHashes: parsed.configKeyHashes } : {},
|
|
4886
|
+
...parsed.extraInputs ? { extraInputs: parsed.extraInputs } : {},
|
|
4887
|
+
files: parsed.files
|
|
4888
|
+
};
|
|
4889
|
+
} catch {
|
|
4890
|
+
return null;
|
|
4891
|
+
}
|
|
4892
|
+
}
|
|
4893
|
+
function computeConfigHash(config) {
|
|
4894
|
+
return createHash("sha256").update(serializeConfig(config)).digest("hex");
|
|
4895
|
+
}
|
|
4896
|
+
async function writeManifest(outDir, manifest) {
|
|
4897
|
+
await writeFile9(join13(outDir, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}
|
|
4898
|
+
`, "utf8");
|
|
4899
|
+
}
|
|
4900
|
+
async function listOutputFiles(outDir) {
|
|
4901
|
+
const found = [];
|
|
4902
|
+
async function walk(dir) {
|
|
4903
|
+
const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
4904
|
+
for (const entry of entries) {
|
|
4905
|
+
const abs = join13(dir, entry.name);
|
|
4906
|
+
if (entry.isDirectory()) {
|
|
4907
|
+
await walk(abs);
|
|
4908
|
+
} else if (entry.isFile()) {
|
|
4909
|
+
const rel = relative7(outDir, abs);
|
|
4910
|
+
if (rel === MANIFEST_FILE || rel === LOCK_FILE) continue;
|
|
4911
|
+
found.push(rel);
|
|
4912
|
+
}
|
|
4913
|
+
}
|
|
4914
|
+
}
|
|
4915
|
+
await walk(outDir);
|
|
4916
|
+
return found.sort();
|
|
4917
|
+
}
|
|
4918
|
+
async function allOutputsExist(outDir, files) {
|
|
4919
|
+
const present = new Set(await listOutputFiles(outDir));
|
|
4920
|
+
return files.every((file) => present.has(file));
|
|
4921
|
+
}
|
|
4922
|
+
async function isManifestFresh(outDir, manifest, inputsHash) {
|
|
4923
|
+
if (manifest === null) return false;
|
|
4924
|
+
if (manifest.version !== VERSION) return false;
|
|
4925
|
+
if (manifest.hash !== inputsHash) return false;
|
|
4926
|
+
if (manifest.files.length === 0) return false;
|
|
4927
|
+
return allOutputsExist(outDir, manifest.files);
|
|
4928
|
+
}
|
|
4929
|
+
|
|
4930
|
+
// src/generate.ts
|
|
4931
|
+
function driftGuardMessage(outDir, previousEntryPoint, currentEntryPoint, differingKeys) {
|
|
4932
|
+
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)";
|
|
4933
|
+
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.`;
|
|
4934
|
+
}
|
|
4935
|
+
async function generate(config, inputRoutes = [], entryPoint = "cli") {
|
|
4936
|
+
setCodegenDebug(config.debug);
|
|
4937
|
+
const manifest = await readManifest(config.codegen.outDir);
|
|
4938
|
+
const inputsHash = await computeInputsHash(config, manifest?.extraInputs ?? []);
|
|
4939
|
+
if (await isManifestFresh(config.codegen.outDir, manifest, inputsHash)) {
|
|
4940
|
+
console.log(`[nestjs-codegen] ${config.codegen.outDir} up to date, skipped`);
|
|
4941
|
+
return;
|
|
4942
|
+
}
|
|
4943
|
+
const configHash = computeConfigHash(config);
|
|
4944
|
+
const configKeyHashes = computeConfigKeyHashes(config);
|
|
4945
|
+
if (config.driftGuard && manifest?.entryPoint && manifest.entryPoint !== entryPoint && manifest.configHash && manifest.configHash !== configHash) {
|
|
4946
|
+
throw new DriftGuardError(
|
|
4947
|
+
driftGuardMessage(
|
|
4948
|
+
config.codegen.outDir,
|
|
4949
|
+
manifest.entryPoint,
|
|
4950
|
+
entryPoint,
|
|
4951
|
+
// A pre-key-hash manifest can't tell us WHICH keys differ — pass none
|
|
4952
|
+
// rather than diffing against {} (which would name every key).
|
|
4953
|
+
manifest.configKeyHashes ? diffConfigKeyHashes(manifest.configKeyHashes, configKeyHashes) : []
|
|
4954
|
+
)
|
|
4955
|
+
);
|
|
4956
|
+
}
|
|
4957
|
+
const extensions = config.extensions ?? [];
|
|
4958
|
+
let routes = inputRoutes;
|
|
4959
|
+
const trackedInputs = /* @__PURE__ */ new Set();
|
|
4960
|
+
const ctx = createExtensionContext(config, () => routes, trackedInputs);
|
|
4961
|
+
if (extensions.length > 0) {
|
|
4962
|
+
routes = await applyTransformRoutes(routes, extensions, ctx);
|
|
4963
|
+
}
|
|
4964
|
+
if (config.pages) {
|
|
4965
|
+
const pagesConfig = config.pages;
|
|
4966
|
+
const pages = await discoverPages({
|
|
4967
|
+
glob: pagesConfig.glob,
|
|
4968
|
+
cwd: config.codegen.cwd,
|
|
4969
|
+
propsExport: pagesConfig.propsExport,
|
|
4970
|
+
componentNameStrategy: pagesConfig.componentNameStrategy
|
|
4971
|
+
});
|
|
4972
|
+
const sharedProps = discoverSharedPropsFromConfig(config);
|
|
4973
|
+
await emitPages(pages, config.codegen.outDir, {
|
|
4974
|
+
propsExport: pagesConfig.propsExport,
|
|
4975
|
+
sharedProps
|
|
4976
|
+
});
|
|
4977
|
+
await emitCache(pages, config.codegen.outDir);
|
|
4978
|
+
}
|
|
4979
|
+
const hasRoutes = routes.length > 0;
|
|
4980
|
+
const hasContracts = routes.some((r) => r.contract);
|
|
4981
|
+
if (hasRoutes) {
|
|
4982
|
+
await emitRoutes(routes, config.codegen.outDir);
|
|
4983
|
+
}
|
|
4984
|
+
if (hasContracts) {
|
|
4985
|
+
await emitApi(routes, config.codegen.outDir, {
|
|
4986
|
+
...config.fetcher?.importPath ? { fetcherImportPath: config.fetcher.importPath } : {},
|
|
4987
|
+
serialization: config.serialization,
|
|
4988
|
+
extensions,
|
|
4989
|
+
ctx
|
|
4990
|
+
});
|
|
4991
|
+
}
|
|
4992
|
+
const hasForms = await emitForms(routes, config.codegen.outDir, config.forms, config.validation);
|
|
4993
|
+
if (hasContracts && config.openapi.enabled) {
|
|
4994
|
+
await emitOpenApi(routes, config.codegen.outDir, {
|
|
4995
|
+
fileName: config.openapi.fileName,
|
|
4996
|
+
info: {
|
|
4997
|
+
title: config.openapi.title,
|
|
4998
|
+
version: config.openapi.version,
|
|
4999
|
+
...config.openapi.description ? { description: config.openapi.description } : {}
|
|
5000
|
+
}
|
|
5001
|
+
});
|
|
5002
|
+
}
|
|
5003
|
+
if (hasContracts && config.mocks.enabled) {
|
|
5004
|
+
await emitMocks(routes, config.codegen.outDir, {
|
|
5005
|
+
fileName: config.mocks.fileName,
|
|
5006
|
+
seed: config.mocks.seed,
|
|
5007
|
+
baseUrl: config.mocks.baseUrl
|
|
5008
|
+
});
|
|
5009
|
+
}
|
|
5010
|
+
await emitIndex(config.codegen.outDir, hasContracts, hasForms);
|
|
5011
|
+
if (extensions.length > 0) {
|
|
5012
|
+
const extraFiles = await collectEmittedFiles(extensions, ctx);
|
|
5013
|
+
for (const file of extraFiles) {
|
|
5014
|
+
const dest = join14(config.codegen.outDir, file.path);
|
|
5015
|
+
await mkdir9(dirname4(dest), { recursive: true });
|
|
5016
|
+
await writeFile10(dest, file.contents, "utf8");
|
|
5017
|
+
}
|
|
5018
|
+
}
|
|
5019
|
+
const outputFiles = await listOutputFiles(config.codegen.outDir);
|
|
5020
|
+
const extraInputs = [...trackedInputs].sort();
|
|
5021
|
+
const recordedHash = extraInputs.length > 0 ? await computeInputsHash(config, extraInputs) : inputsHash;
|
|
5022
|
+
await writeManifest(config.codegen.outDir, {
|
|
5023
|
+
version: VERSION,
|
|
5024
|
+
hash: recordedHash,
|
|
5025
|
+
entryPoint,
|
|
5026
|
+
configHash,
|
|
5027
|
+
configKeyHashes,
|
|
5028
|
+
files: outputFiles,
|
|
5029
|
+
...extraInputs.length > 0 ? { extraInputs } : {}
|
|
5030
|
+
});
|
|
5031
|
+
}
|
|
5032
|
+
|
|
5033
|
+
// src/watch/watcher.ts
|
|
5034
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
5035
|
+
import { join as join16 } from "path";
|
|
5036
|
+
import chokidar from "chokidar";
|
|
5037
|
+
|
|
4998
5038
|
// src/watch/lock-file.ts
|
|
4999
5039
|
import { open } from "fs/promises";
|
|
5000
5040
|
import { mkdir as mkdir10, readFile as readFile3, unlink } from "fs/promises";
|
|
@@ -5271,7 +5311,7 @@ function createChainModuleRenderer(opts) {
|
|
|
5271
5311
|
}
|
|
5272
5312
|
|
|
5273
5313
|
// src/index.ts
|
|
5274
|
-
var VERSION = "0.21.
|
|
5314
|
+
var VERSION = "0.21.1";
|
|
5275
5315
|
export {
|
|
5276
5316
|
CodegenError,
|
|
5277
5317
|
ConfigError,
|