@dudousxd/nestjs-codegen 0.11.0 → 0.13.0
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 +59 -0
- package/dist/cli/main.cjs +264 -91
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.js +248 -75
- package/dist/cli/main.js.map +1 -1
- package/dist/extension/index.d.cts +1 -1
- package/dist/extension/index.d.ts +1 -1
- package/dist/{index-CxkGbILp.d.cts → index-DvUzPXdh.d.cts} +7 -0
- package/dist/{index-CxkGbILp.d.ts → index-DvUzPXdh.d.ts} +7 -0
- package/dist/index.cjs +222 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -5
- package/dist/index.d.ts +31 -5
- package/dist/index.js +209 -36
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +267 -75
- package/dist/nest/index.cjs.map +1 -1
- package/dist/nest/index.d.cts +16 -5
- package/dist/nest/index.d.ts +16 -5
- package/dist/nest/index.js +267 -75
- package/dist/nest/index.js.map +1 -1
- package/package.json +1 -1
package/dist/nest/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DynamicModule, OnApplicationBootstrap, OnModuleDestroy } from '@nestjs/common';
|
|
2
|
-
import { U as UserConfig } from '../index-
|
|
2
|
+
import { U as UserConfig } from '../index-DvUzPXdh.cjs';
|
|
3
3
|
import 'ts-morph';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -33,11 +33,21 @@ interface CodegenModuleOptions extends Omit<UserConfig, 'validation'> {
|
|
|
33
33
|
validation?: UserConfig['validation'];
|
|
34
34
|
/**
|
|
35
35
|
* Master switch for the boot-time watcher. When omitted, the watcher runs in every
|
|
36
|
-
* environment EXCEPT production
|
|
37
|
-
*
|
|
38
|
-
*
|
|
36
|
+
* environment EXCEPT production — codegen is a dev/CI build step, not a
|
|
37
|
+
* production-runtime concern. Set `false` to disable entirely, or `true` to force it
|
|
38
|
+
* on even in production (overrides {@link runInProduction}).
|
|
39
39
|
*/
|
|
40
40
|
enabled?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Run the boot-time watcher even when `NODE_ENV` is `production`. Codegen is a
|
|
43
|
+
* dev-time concern — in production the artifacts are already built by the CLI in the
|
|
44
|
+
* build pipeline — so this defaults to `false` (skip in production). Set `true` only
|
|
45
|
+
* if you genuinely need to regenerate at production runtime. Ignored when `enabled`
|
|
46
|
+
* is set explicitly.
|
|
47
|
+
*
|
|
48
|
+
* @default false
|
|
49
|
+
*/
|
|
50
|
+
runInProduction?: boolean;
|
|
41
51
|
/** Project root used to resolve globs / `outDir`. Defaults to `process.cwd()`. */
|
|
42
52
|
cwd?: string;
|
|
43
53
|
}
|
|
@@ -45,7 +55,8 @@ interface CodegenModuleOptions extends Omit<UserConfig, 'validation'> {
|
|
|
45
55
|
declare const CODEGEN_MODULE_OPTIONS: unique symbol;
|
|
46
56
|
/**
|
|
47
57
|
* Decide whether the boot-time watcher should start, given the module options and the
|
|
48
|
-
* current `NODE_ENV`. Explicit `enabled` always wins; otherwise default on
|
|
58
|
+
* current `NODE_ENV`. Explicit `enabled` always wins; otherwise default on everywhere
|
|
59
|
+
* except production, where it stays off unless `runInProduction` is set.
|
|
49
60
|
*/
|
|
50
61
|
declare function shouldRun(options: CodegenModuleOptions, env: string | undefined): boolean;
|
|
51
62
|
/**
|
package/dist/nest/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DynamicModule, OnApplicationBootstrap, OnModuleDestroy } from '@nestjs/common';
|
|
2
|
-
import { U as UserConfig } from '../index-
|
|
2
|
+
import { U as UserConfig } from '../index-DvUzPXdh.js';
|
|
3
3
|
import 'ts-morph';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -33,11 +33,21 @@ interface CodegenModuleOptions extends Omit<UserConfig, 'validation'> {
|
|
|
33
33
|
validation?: UserConfig['validation'];
|
|
34
34
|
/**
|
|
35
35
|
* Master switch for the boot-time watcher. When omitted, the watcher runs in every
|
|
36
|
-
* environment EXCEPT production
|
|
37
|
-
*
|
|
38
|
-
*
|
|
36
|
+
* environment EXCEPT production — codegen is a dev/CI build step, not a
|
|
37
|
+
* production-runtime concern. Set `false` to disable entirely, or `true` to force it
|
|
38
|
+
* on even in production (overrides {@link runInProduction}).
|
|
39
39
|
*/
|
|
40
40
|
enabled?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Run the boot-time watcher even when `NODE_ENV` is `production`. Codegen is a
|
|
43
|
+
* dev-time concern — in production the artifacts are already built by the CLI in the
|
|
44
|
+
* build pipeline — so this defaults to `false` (skip in production). Set `true` only
|
|
45
|
+
* if you genuinely need to regenerate at production runtime. Ignored when `enabled`
|
|
46
|
+
* is set explicitly.
|
|
47
|
+
*
|
|
48
|
+
* @default false
|
|
49
|
+
*/
|
|
50
|
+
runInProduction?: boolean;
|
|
41
51
|
/** Project root used to resolve globs / `outDir`. Defaults to `process.cwd()`. */
|
|
42
52
|
cwd?: string;
|
|
43
53
|
}
|
|
@@ -45,7 +55,8 @@ interface CodegenModuleOptions extends Omit<UserConfig, 'validation'> {
|
|
|
45
55
|
declare const CODEGEN_MODULE_OPTIONS: unique symbol;
|
|
46
56
|
/**
|
|
47
57
|
* Decide whether the boot-time watcher should start, given the module options and the
|
|
48
|
-
* current `NODE_ENV`. Explicit `enabled` always wins; otherwise default on
|
|
58
|
+
* current `NODE_ENV`. Explicit `enabled` always wins; otherwise default on everywhere
|
|
59
|
+
* except production, where it stays off unless `runInProduction` is set.
|
|
49
60
|
*/
|
|
50
61
|
declare function shouldRun(options: CodegenModuleOptions, env: string | undefined): boolean;
|
|
51
62
|
/**
|
package/dist/nest/index.js
CHANGED
|
@@ -138,8 +138,8 @@ function applyDefaults(userConfig, cwd) {
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
// src/watch/watcher.ts
|
|
141
|
-
import { readFile as
|
|
142
|
-
import { join as
|
|
141
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
142
|
+
import { join as join16 } from "path";
|
|
143
143
|
import chokidar from "chokidar";
|
|
144
144
|
|
|
145
145
|
// src/discovery/contracts-fast.ts
|
|
@@ -1568,6 +1568,55 @@ function extractParamsType(method, sourceFile, project) {
|
|
|
1568
1568
|
}
|
|
1569
1569
|
return entries.length > 0 ? `{ ${entries.join("; ")} }` : null;
|
|
1570
1570
|
}
|
|
1571
|
+
function extractUploadedFiles(method) {
|
|
1572
|
+
const FILE = "File | Blob";
|
|
1573
|
+
const entries = [];
|
|
1574
|
+
let multipart = false;
|
|
1575
|
+
const hasUploadedFileParam = method.getParameters().some(
|
|
1576
|
+
(p) => p.getDecorators().some((d) => {
|
|
1577
|
+
const name = d.getName();
|
|
1578
|
+
return name === "UploadedFile" || name === "UploadedFiles";
|
|
1579
|
+
})
|
|
1580
|
+
);
|
|
1581
|
+
for (const decorator of method.getDecorators()) {
|
|
1582
|
+
if (decorator.getName() !== "UseInterceptors") continue;
|
|
1583
|
+
for (const arg of decorator.getArguments()) {
|
|
1584
|
+
if (!Node5.isCallExpression(arg)) continue;
|
|
1585
|
+
const interceptor = arg.getExpression().getText();
|
|
1586
|
+
const callArgs = arg.getArguments();
|
|
1587
|
+
const firstArg2 = callArgs[0];
|
|
1588
|
+
if (interceptor === "FileInterceptor") {
|
|
1589
|
+
if (firstArg2 && Node5.isStringLiteral(firstArg2)) {
|
|
1590
|
+
entries.push(`${firstArg2.getLiteralValue()}: ${FILE}`);
|
|
1591
|
+
multipart = true;
|
|
1592
|
+
}
|
|
1593
|
+
} else if (interceptor === "FilesInterceptor") {
|
|
1594
|
+
if (firstArg2 && Node5.isStringLiteral(firstArg2)) {
|
|
1595
|
+
entries.push(`${firstArg2.getLiteralValue()}: Array<${FILE}>`);
|
|
1596
|
+
multipart = true;
|
|
1597
|
+
}
|
|
1598
|
+
} else if (interceptor === "FileFieldsInterceptor") {
|
|
1599
|
+
if (firstArg2 && Node5.isArrayLiteralExpression(firstArg2)) {
|
|
1600
|
+
for (const el of firstArg2.getElements()) {
|
|
1601
|
+
if (!Node5.isObjectLiteralExpression(el)) continue;
|
|
1602
|
+
const nameProp = el.getProperty("name");
|
|
1603
|
+
if (nameProp && Node5.isPropertyAssignment(nameProp)) {
|
|
1604
|
+
const init = nameProp.getInitializer();
|
|
1605
|
+
if (init && Node5.isStringLiteral(init)) {
|
|
1606
|
+
entries.push(`${init.getLiteralValue()}: Array<${FILE}>`);
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
multipart = true;
|
|
1611
|
+
}
|
|
1612
|
+
} else if (interceptor === "AnyFilesInterceptor") {
|
|
1613
|
+
multipart = true;
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
if (hasUploadedFileParam) multipart = true;
|
|
1618
|
+
return { fields: entries.length > 0 ? entries.join("; ") : null, multipart };
|
|
1619
|
+
}
|
|
1571
1620
|
function extractResponseType(method, sourceFile, project) {
|
|
1572
1621
|
const apiResponseDecorator = method.getDecorators().find((d) => d.getName() === "ApiResponse" && (apiResponseStatus(d) ?? 0) < 400);
|
|
1573
1622
|
if (apiResponseDecorator) {
|
|
@@ -1702,6 +1751,11 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
1702
1751
|
let body = extractBodyType(method, sourceFile, project);
|
|
1703
1752
|
const filterInfo = extractApplyFilterInfo(method, sourceFile, project);
|
|
1704
1753
|
const query = extractQueryType(method, sourceFile, project);
|
|
1754
|
+
const uploads = extractUploadedFiles(method);
|
|
1755
|
+
if (uploads.fields) {
|
|
1756
|
+
const fileObject = `{ ${uploads.fields} }`;
|
|
1757
|
+
body = body ? `(${body}) & ${fileObject}` : fileObject;
|
|
1758
|
+
}
|
|
1705
1759
|
const streamElement = detectStreamElement(method);
|
|
1706
1760
|
const isStream = streamElement !== null;
|
|
1707
1761
|
if (filterInfo && filterInfo.source === "body") {
|
|
@@ -1711,7 +1765,7 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
1711
1765
|
const paramsType = extractParamsType(method, sourceFile, project);
|
|
1712
1766
|
const response = isStream ? resolveTypeNodeToString(streamElement, sourceFile, project, 3) : extractResponseType(method, sourceFile, project);
|
|
1713
1767
|
const errorInfo = extractErrorType(method, sourceFile, project);
|
|
1714
|
-
if (body === null && query === null && paramsType === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream) {
|
|
1768
|
+
if (body === null && query === null && paramsType === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream && !uploads.multipart) {
|
|
1715
1769
|
return null;
|
|
1716
1770
|
}
|
|
1717
1771
|
let bodyRef = null;
|
|
@@ -1784,7 +1838,8 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
1784
1838
|
formWarnings,
|
|
1785
1839
|
bodySchema,
|
|
1786
1840
|
querySchema,
|
|
1787
|
-
stream: isStream
|
|
1841
|
+
stream: isStream,
|
|
1842
|
+
multipart: uploads.multipart
|
|
1788
1843
|
};
|
|
1789
1844
|
}
|
|
1790
1845
|
function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
@@ -2251,7 +2306,8 @@ function extractDtoRoute(args) {
|
|
|
2251
2306
|
formWarnings: dtoContract?.formWarnings ?? [],
|
|
2252
2307
|
bodySchema: dtoContract?.bodySchema ?? null,
|
|
2253
2308
|
querySchema: dtoContract?.querySchema ?? null,
|
|
2254
|
-
stream: dtoContract?.stream ?? false
|
|
2309
|
+
stream: dtoContract?.stream ?? false,
|
|
2310
|
+
multipart: dtoContract?.multipart ?? false
|
|
2255
2311
|
}
|
|
2256
2312
|
});
|
|
2257
2313
|
}
|
|
@@ -2294,8 +2350,8 @@ function extractFromSourceFile(sourceFile, project) {
|
|
|
2294
2350
|
}
|
|
2295
2351
|
|
|
2296
2352
|
// src/generate.ts
|
|
2297
|
-
import { mkdir as
|
|
2298
|
-
import { dirname as dirname3, join as
|
|
2353
|
+
import { mkdir as mkdir10, writeFile as writeFile10 } from "fs/promises";
|
|
2354
|
+
import { dirname as dirname3, join as join15 } from "path";
|
|
2299
2355
|
|
|
2300
2356
|
// src/discovery/pages.ts
|
|
2301
2357
|
import { readFile } from "fs/promises";
|
|
@@ -2897,6 +2953,7 @@ function buildRequestModel(c) {
|
|
|
2897
2953
|
const optsParts = [];
|
|
2898
2954
|
if (hasQuery) optsParts.push("query: input?.query as Record<string, unknown> | undefined");
|
|
2899
2955
|
if (hasBody) optsParts.push("body: input?.body");
|
|
2956
|
+
if (hasBody && c.contractSource.multipart) optsParts.push("multipart: true");
|
|
2900
2957
|
const optsExpr = optsParts.length ? `{ ${optsParts.join(", ")} }` : "{}";
|
|
2901
2958
|
return {
|
|
2902
2959
|
routeName: c.name,
|
|
@@ -4190,9 +4247,159 @@ function buildEmpty() {
|
|
|
4190
4247
|
].join("\n");
|
|
4191
4248
|
}
|
|
4192
4249
|
|
|
4250
|
+
// src/generate-manifest.ts
|
|
4251
|
+
import { createHash } from "crypto";
|
|
4252
|
+
import { readFile as readFile3, readdir, writeFile as writeFile9 } from "fs/promises";
|
|
4253
|
+
import { join as join14, relative as relative6 } from "path";
|
|
4254
|
+
import fg3 from "fast-glob";
|
|
4255
|
+
|
|
4256
|
+
// src/watch/lock-file.ts
|
|
4257
|
+
import { open } from "fs/promises";
|
|
4258
|
+
import { mkdir as mkdir9, readFile as readFile2, unlink } from "fs/promises";
|
|
4259
|
+
import { join as join13 } from "path";
|
|
4260
|
+
var LOCK_FILE = ".watcher.lock";
|
|
4261
|
+
function isProcessAlive(pid) {
|
|
4262
|
+
try {
|
|
4263
|
+
process.kill(pid, 0);
|
|
4264
|
+
return true;
|
|
4265
|
+
} catch {
|
|
4266
|
+
return false;
|
|
4267
|
+
}
|
|
4268
|
+
}
|
|
4269
|
+
async function acquireLock(outDir) {
|
|
4270
|
+
await mkdir9(outDir, { recursive: true });
|
|
4271
|
+
const lockPath = join13(outDir, LOCK_FILE);
|
|
4272
|
+
const lockData = { pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4273
|
+
try {
|
|
4274
|
+
const fd = await open(lockPath, "wx");
|
|
4275
|
+
await fd.writeFile(`${JSON.stringify(lockData, null, 2)}
|
|
4276
|
+
`, "utf8");
|
|
4277
|
+
await fd.close();
|
|
4278
|
+
} catch (err) {
|
|
4279
|
+
if (err.code === "EEXIST") {
|
|
4280
|
+
try {
|
|
4281
|
+
const raw = await readFile2(lockPath, "utf8");
|
|
4282
|
+
const existing = JSON.parse(raw);
|
|
4283
|
+
if (isProcessAlive(existing.pid)) return null;
|
|
4284
|
+
await unlink(lockPath);
|
|
4285
|
+
return acquireLock(outDir);
|
|
4286
|
+
} catch {
|
|
4287
|
+
return null;
|
|
4288
|
+
}
|
|
4289
|
+
}
|
|
4290
|
+
return null;
|
|
4291
|
+
}
|
|
4292
|
+
return {
|
|
4293
|
+
release: async () => {
|
|
4294
|
+
try {
|
|
4295
|
+
await unlink(lockPath);
|
|
4296
|
+
} catch {
|
|
4297
|
+
}
|
|
4298
|
+
}
|
|
4299
|
+
};
|
|
4300
|
+
}
|
|
4301
|
+
|
|
4302
|
+
// src/index.ts
|
|
4303
|
+
var VERSION = "0.13.0";
|
|
4304
|
+
|
|
4305
|
+
// src/generate-manifest.ts
|
|
4306
|
+
var MANIFEST_FILE = ".codegen-manifest.json";
|
|
4307
|
+
var LOCK_FILE2 = ".watcher.lock";
|
|
4308
|
+
function isManifestShape(value) {
|
|
4309
|
+
if (typeof value !== "object" || value === null) return false;
|
|
4310
|
+
const candidate = value;
|
|
4311
|
+
if (typeof candidate.version !== "string") return false;
|
|
4312
|
+
if (typeof candidate.hash !== "string") return false;
|
|
4313
|
+
if (!Array.isArray(candidate.files)) return false;
|
|
4314
|
+
return candidate.files.every((entry) => typeof entry === "string");
|
|
4315
|
+
}
|
|
4316
|
+
function serializeConfig(config) {
|
|
4317
|
+
try {
|
|
4318
|
+
return JSON.stringify(config, (_key, value) => {
|
|
4319
|
+
if (typeof value === "function") return `[fn:${value.name}]${value.toString()}`;
|
|
4320
|
+
return value;
|
|
4321
|
+
});
|
|
4322
|
+
} catch {
|
|
4323
|
+
return `unserializable:${config.codegen.outDir}:${config.contracts.glob}`;
|
|
4324
|
+
}
|
|
4325
|
+
}
|
|
4326
|
+
async function discoverInputFiles(config) {
|
|
4327
|
+
const globs = [config.contracts.glob, config.forms.watch];
|
|
4328
|
+
if (config.pages) globs.push(config.pages.glob);
|
|
4329
|
+
const cwd = config.codegen.cwd;
|
|
4330
|
+
const matched = await fg3(globs, { cwd, absolute: true, onlyFiles: true });
|
|
4331
|
+
return [...new Set(matched)].sort();
|
|
4332
|
+
}
|
|
4333
|
+
async function computeInputsHash(config) {
|
|
4334
|
+
const hash = createHash("sha256");
|
|
4335
|
+
hash.update(`version:${VERSION}
|
|
4336
|
+
`);
|
|
4337
|
+
hash.update(`config:${serializeConfig(config)}
|
|
4338
|
+
`);
|
|
4339
|
+
const inputFiles = await discoverInputFiles(config);
|
|
4340
|
+
const cwd = config.codegen.cwd;
|
|
4341
|
+
for (const file of inputFiles) {
|
|
4342
|
+
const contents = await readFile3(file, "utf8");
|
|
4343
|
+
hash.update(`file:${relative6(cwd, file)}
|
|
4344
|
+
`);
|
|
4345
|
+
hash.update(contents);
|
|
4346
|
+
hash.update("\n");
|
|
4347
|
+
}
|
|
4348
|
+
return hash.digest("hex");
|
|
4349
|
+
}
|
|
4350
|
+
async function readManifest(outDir) {
|
|
4351
|
+
try {
|
|
4352
|
+
const raw = await readFile3(join14(outDir, MANIFEST_FILE), "utf8");
|
|
4353
|
+
const parsed = JSON.parse(raw);
|
|
4354
|
+
if (!isManifestShape(parsed)) return null;
|
|
4355
|
+
return { version: parsed.version, hash: parsed.hash, files: parsed.files };
|
|
4356
|
+
} catch {
|
|
4357
|
+
return null;
|
|
4358
|
+
}
|
|
4359
|
+
}
|
|
4360
|
+
async function writeManifest(outDir, manifest) {
|
|
4361
|
+
await writeFile9(join14(outDir, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}
|
|
4362
|
+
`, "utf8");
|
|
4363
|
+
}
|
|
4364
|
+
async function listOutputFiles(outDir) {
|
|
4365
|
+
const found = [];
|
|
4366
|
+
async function walk(dir) {
|
|
4367
|
+
const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
4368
|
+
for (const entry of entries) {
|
|
4369
|
+
const abs = join14(dir, entry.name);
|
|
4370
|
+
if (entry.isDirectory()) {
|
|
4371
|
+
await walk(abs);
|
|
4372
|
+
} else if (entry.isFile()) {
|
|
4373
|
+
const rel = relative6(outDir, abs);
|
|
4374
|
+
if (rel === MANIFEST_FILE || rel === LOCK_FILE2) continue;
|
|
4375
|
+
found.push(rel);
|
|
4376
|
+
}
|
|
4377
|
+
}
|
|
4378
|
+
}
|
|
4379
|
+
await walk(outDir);
|
|
4380
|
+
return found.sort();
|
|
4381
|
+
}
|
|
4382
|
+
async function allOutputsExist(outDir, files) {
|
|
4383
|
+
const present = new Set(await listOutputFiles(outDir));
|
|
4384
|
+
return files.every((file) => present.has(file));
|
|
4385
|
+
}
|
|
4386
|
+
async function isManifestFresh(outDir, manifest, inputsHash) {
|
|
4387
|
+
if (manifest === null) return false;
|
|
4388
|
+
if (manifest.version !== VERSION) return false;
|
|
4389
|
+
if (manifest.hash !== inputsHash) return false;
|
|
4390
|
+
if (manifest.files.length === 0) return false;
|
|
4391
|
+
return allOutputsExist(outDir, manifest.files);
|
|
4392
|
+
}
|
|
4393
|
+
|
|
4193
4394
|
// src/generate.ts
|
|
4194
4395
|
async function generate(config, inputRoutes = []) {
|
|
4195
4396
|
setCodegenDebug(config.debug);
|
|
4397
|
+
const inputsHash = await computeInputsHash(config);
|
|
4398
|
+
const manifest = await readManifest(config.codegen.outDir);
|
|
4399
|
+
if (await isManifestFresh(config.codegen.outDir, manifest, inputsHash)) {
|
|
4400
|
+
console.log(`[nestjs-codegen] ${config.codegen.outDir} up to date, skipped`);
|
|
4401
|
+
return;
|
|
4402
|
+
}
|
|
4196
4403
|
const extensions = config.extensions ?? [];
|
|
4197
4404
|
let routes = inputRoutes;
|
|
4198
4405
|
const ctx = createExtensionContext(config, () => routes);
|
|
@@ -4249,69 +4456,29 @@ async function generate(config, inputRoutes = []) {
|
|
|
4249
4456
|
if (extensions.length > 0) {
|
|
4250
4457
|
const extraFiles = await collectEmittedFiles(extensions, ctx);
|
|
4251
4458
|
for (const file of extraFiles) {
|
|
4252
|
-
const dest =
|
|
4253
|
-
await
|
|
4254
|
-
await
|
|
4255
|
-
}
|
|
4256
|
-
}
|
|
4257
|
-
}
|
|
4258
|
-
|
|
4259
|
-
// src/watch/lock-file.ts
|
|
4260
|
-
import { open } from "fs/promises";
|
|
4261
|
-
import { mkdir as mkdir10, readFile as readFile2, unlink } from "fs/promises";
|
|
4262
|
-
import { join as join14 } from "path";
|
|
4263
|
-
var LOCK_FILE = ".watcher.lock";
|
|
4264
|
-
function isProcessAlive(pid) {
|
|
4265
|
-
try {
|
|
4266
|
-
process.kill(pid, 0);
|
|
4267
|
-
return true;
|
|
4268
|
-
} catch {
|
|
4269
|
-
return false;
|
|
4270
|
-
}
|
|
4271
|
-
}
|
|
4272
|
-
async function acquireLock(outDir) {
|
|
4273
|
-
await mkdir10(outDir, { recursive: true });
|
|
4274
|
-
const lockPath = join14(outDir, LOCK_FILE);
|
|
4275
|
-
const lockData = { pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4276
|
-
try {
|
|
4277
|
-
const fd = await open(lockPath, "wx");
|
|
4278
|
-
await fd.writeFile(`${JSON.stringify(lockData, null, 2)}
|
|
4279
|
-
`, "utf8");
|
|
4280
|
-
await fd.close();
|
|
4281
|
-
} catch (err) {
|
|
4282
|
-
if (err.code === "EEXIST") {
|
|
4283
|
-
try {
|
|
4284
|
-
const raw = await readFile2(lockPath, "utf8");
|
|
4285
|
-
const existing = JSON.parse(raw);
|
|
4286
|
-
if (isProcessAlive(existing.pid)) return null;
|
|
4287
|
-
await unlink(lockPath);
|
|
4288
|
-
return acquireLock(outDir);
|
|
4289
|
-
} catch {
|
|
4290
|
-
return null;
|
|
4291
|
-
}
|
|
4459
|
+
const dest = join15(config.codegen.outDir, file.path);
|
|
4460
|
+
await mkdir10(dirname3(dest), { recursive: true });
|
|
4461
|
+
await writeFile10(dest, file.contents, "utf8");
|
|
4292
4462
|
}
|
|
4293
|
-
return null;
|
|
4294
4463
|
}
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
}
|
|
4302
|
-
};
|
|
4464
|
+
const outputFiles = await listOutputFiles(config.codegen.outDir);
|
|
4465
|
+
await writeManifest(config.codegen.outDir, {
|
|
4466
|
+
version: VERSION,
|
|
4467
|
+
hash: inputsHash,
|
|
4468
|
+
files: outputFiles
|
|
4469
|
+
});
|
|
4303
4470
|
}
|
|
4304
4471
|
|
|
4305
4472
|
// src/watch/watcher.ts
|
|
4306
4473
|
var PAGES_DEBOUNCE_MS = 150;
|
|
4307
4474
|
var NO_OP_WATCHER = { close: async () => {
|
|
4308
4475
|
} };
|
|
4309
|
-
async function watch(config, onChange) {
|
|
4476
|
+
async function watch(config, onChange, options = {}) {
|
|
4310
4477
|
const lock = await acquireLock(config.codegen.outDir);
|
|
4311
4478
|
if (lock === null) {
|
|
4312
4479
|
let holderPid = "unknown";
|
|
4313
4480
|
try {
|
|
4314
|
-
const raw = await
|
|
4481
|
+
const raw = await readFile4(join16(config.codegen.outDir, ".watcher.lock"), "utf8");
|
|
4315
4482
|
const data = JSON.parse(raw);
|
|
4316
4483
|
if (data.pid !== void 0) holderPid = String(data.pid);
|
|
4317
4484
|
} catch {
|
|
@@ -4334,22 +4501,33 @@ async function watch(config, onChange) {
|
|
|
4334
4501
|
}
|
|
4335
4502
|
return discovery;
|
|
4336
4503
|
}
|
|
4337
|
-
|
|
4338
|
-
const initialRoutes = (await getDiscovery()).discover();
|
|
4339
|
-
lastRoutes = initialRoutes;
|
|
4340
|
-
await generate(config, initialRoutes);
|
|
4341
|
-
} catch (err) {
|
|
4342
|
-
console.warn(
|
|
4343
|
-
`[nestjs-codegen] Initial route discovery failed, falling back to pages-only: ${err instanceof Error ? err.message : String(err)}`
|
|
4344
|
-
);
|
|
4504
|
+
async function runInitialPass() {
|
|
4345
4505
|
try {
|
|
4346
|
-
await
|
|
4347
|
-
|
|
4506
|
+
const initialRoutes = (await getDiscovery()).discover();
|
|
4507
|
+
lastRoutes = initialRoutes;
|
|
4508
|
+
await generate(config, initialRoutes);
|
|
4509
|
+
} catch (err) {
|
|
4510
|
+
console.warn(
|
|
4511
|
+
`[nestjs-codegen] Initial route discovery failed, falling back to pages-only: ${err instanceof Error ? err.message : String(err)}`
|
|
4512
|
+
);
|
|
4513
|
+
try {
|
|
4514
|
+
await generate(config, lastRoutes);
|
|
4515
|
+
} catch {
|
|
4516
|
+
}
|
|
4348
4517
|
}
|
|
4349
4518
|
}
|
|
4519
|
+
if (options.deferInitialGenerate) {
|
|
4520
|
+
void runInitialPass().catch((err) => {
|
|
4521
|
+
console.warn(
|
|
4522
|
+
`[nestjs-codegen] Background initial generate failed: ${err instanceof Error ? err.message : String(err)}`
|
|
4523
|
+
);
|
|
4524
|
+
});
|
|
4525
|
+
} else {
|
|
4526
|
+
await runInitialPass();
|
|
4527
|
+
}
|
|
4350
4528
|
let pagesDebounceTimer;
|
|
4351
4529
|
const pagesGlob = config.pages?.glob ?? ".nestjs-codegen-no-pages";
|
|
4352
|
-
const pagesWatcher = chokidar.watch(
|
|
4530
|
+
const pagesWatcher = chokidar.watch(join16(config.codegen.cwd, pagesGlob), {
|
|
4353
4531
|
ignoreInitial: true,
|
|
4354
4532
|
persistent: true,
|
|
4355
4533
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
@@ -4376,7 +4554,7 @@ async function watch(config, onChange) {
|
|
|
4376
4554
|
pagesWatcher.on("unlink", schedulePagesRegenerate);
|
|
4377
4555
|
let contractsDebounceTimer;
|
|
4378
4556
|
const pendingChangedPaths = /* @__PURE__ */ new Set();
|
|
4379
|
-
const contractsWatcher = chokidar.watch(
|
|
4557
|
+
const contractsWatcher = chokidar.watch(join16(config.codegen.cwd, config.contracts.glob), {
|
|
4380
4558
|
ignoreInitial: true,
|
|
4381
4559
|
persistent: true,
|
|
4382
4560
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
@@ -4406,7 +4584,7 @@ async function watch(config, onChange) {
|
|
|
4406
4584
|
contractsWatcher.on("add", (p) => scheduleContractsRegenerate(p));
|
|
4407
4585
|
contractsWatcher.on("change", (p) => scheduleContractsRegenerate(p));
|
|
4408
4586
|
contractsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p));
|
|
4409
|
-
const formsWatcher = chokidar.watch(
|
|
4587
|
+
const formsWatcher = chokidar.watch(join16(config.codegen.cwd, config.forms.watch), {
|
|
4410
4588
|
ignoreInitial: true,
|
|
4411
4589
|
persistent: true,
|
|
4412
4590
|
awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
|
|
@@ -4434,9 +4612,13 @@ async function watch(config, onChange) {
|
|
|
4434
4612
|
|
|
4435
4613
|
// src/nest/module.ts
|
|
4436
4614
|
var CODEGEN_MODULE_OPTIONS = Symbol("NESTJS_CODEGEN_MODULE_OPTIONS");
|
|
4615
|
+
function isProductionEnv(env) {
|
|
4616
|
+
return (env ?? "").trim().toLowerCase() === "production";
|
|
4617
|
+
}
|
|
4437
4618
|
function shouldRun(options, env) {
|
|
4438
4619
|
if (options.enabled !== void 0) return options.enabled;
|
|
4439
|
-
|
|
4620
|
+
if (isProductionEnv(env)) return options.runInProduction === true;
|
|
4621
|
+
return true;
|
|
4440
4622
|
}
|
|
4441
4623
|
var NestjsCodegenService = class {
|
|
4442
4624
|
constructor(options) {
|
|
@@ -4445,11 +4627,21 @@ var NestjsCodegenService = class {
|
|
|
4445
4627
|
logger = new Logger("NestjsCodegen");
|
|
4446
4628
|
watcher = null;
|
|
4447
4629
|
async onApplicationBootstrap() {
|
|
4448
|
-
if (!shouldRun(this.options, process.env.NODE_ENV))
|
|
4449
|
-
|
|
4630
|
+
if (!shouldRun(this.options, process.env.NODE_ENV)) {
|
|
4631
|
+
if (this.options.enabled === void 0 && isProductionEnv(process.env.NODE_ENV)) {
|
|
4632
|
+
this.logger.log("Skipped in production (set runInProduction: true to override).");
|
|
4633
|
+
}
|
|
4634
|
+
return;
|
|
4635
|
+
}
|
|
4636
|
+
const {
|
|
4637
|
+
enabled: _enabled,
|
|
4638
|
+
runInProduction: _runInProduction,
|
|
4639
|
+
cwd,
|
|
4640
|
+
...userConfig
|
|
4641
|
+
} = this.options;
|
|
4450
4642
|
try {
|
|
4451
4643
|
const config = resolveConfig(userConfig, cwd ?? process.cwd());
|
|
4452
|
-
this.watcher = await watch(config);
|
|
4644
|
+
this.watcher = await watch(config, void 0, { deferInitialGenerate: true });
|
|
4453
4645
|
this.logger.log(`Watching ${config.contracts.glob} \u2192 ${config.codegen.outDir}`);
|
|
4454
4646
|
} catch (err) {
|
|
4455
4647
|
this.logger.warn(
|