@dudousxd/nestjs-codegen 0.10.0 → 0.12.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.
@@ -174,8 +174,8 @@ function applyDefaults(userConfig, cwd) {
174
174
  }
175
175
 
176
176
  // src/watch/watcher.ts
177
- var import_promises14 = require("fs/promises");
178
- var import_node_path16 = require("path");
177
+ var import_promises15 = require("fs/promises");
178
+ var import_node_path17 = require("path");
179
179
  var import_chokidar = __toESM(require("chokidar"), 1);
180
180
 
181
181
  // src/discovery/contracts-fast.ts
@@ -2315,8 +2315,8 @@ function extractFromSourceFile(sourceFile, project) {
2315
2315
  }
2316
2316
 
2317
2317
  // src/generate.ts
2318
- var import_promises11 = require("fs/promises");
2319
- var import_node_path14 = require("path");
2318
+ var import_promises14 = require("fs/promises");
2319
+ var import_node_path16 = require("path");
2320
2320
 
2321
2321
  // src/discovery/pages.ts
2322
2322
  var import_promises2 = require("fs/promises");
@@ -2930,7 +2930,11 @@ function buildRequestModel(c) {
2930
2930
  urlExpr,
2931
2931
  optsExpr,
2932
2932
  responseType: `${TA}['response']`,
2933
- queryKeyExpr: `[${flat}, input] as const`
2933
+ // When no input is supplied the key omits the trailing element entirely
2934
+ // (`[name]` rather than `[name, undefined]`) so the bare `.queryKey()` is a
2935
+ // clean prefix that partial-matches every parametrized variant — making it
2936
+ // directly usable for `invalidateQueries`.
2937
+ queryKeyExpr: `(input === undefined ? [${flat}] as const : [${flat}, input] as const)`
2934
2938
  };
2935
2939
  }
2936
2940
  function renderFetcherRequest(req) {
@@ -4207,9 +4211,159 @@ function buildEmpty() {
4207
4211
  ].join("\n");
4208
4212
  }
4209
4213
 
4214
+ // src/generate-manifest.ts
4215
+ var import_node_crypto = require("crypto");
4216
+ var import_promises13 = require("fs/promises");
4217
+ var import_node_path15 = require("path");
4218
+ var import_fast_glob3 = __toESM(require("fast-glob"), 1);
4219
+
4220
+ // src/watch/lock-file.ts
4221
+ var import_promises11 = require("fs/promises");
4222
+ var import_promises12 = require("fs/promises");
4223
+ var import_node_path14 = require("path");
4224
+ var LOCK_FILE = ".watcher.lock";
4225
+ function isProcessAlive(pid) {
4226
+ try {
4227
+ process.kill(pid, 0);
4228
+ return true;
4229
+ } catch {
4230
+ return false;
4231
+ }
4232
+ }
4233
+ async function acquireLock(outDir) {
4234
+ await (0, import_promises12.mkdir)(outDir, { recursive: true });
4235
+ const lockPath = (0, import_node_path14.join)(outDir, LOCK_FILE);
4236
+ const lockData = { pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
4237
+ try {
4238
+ const fd = await (0, import_promises11.open)(lockPath, "wx");
4239
+ await fd.writeFile(`${JSON.stringify(lockData, null, 2)}
4240
+ `, "utf8");
4241
+ await fd.close();
4242
+ } catch (err) {
4243
+ if (err.code === "EEXIST") {
4244
+ try {
4245
+ const raw = await (0, import_promises12.readFile)(lockPath, "utf8");
4246
+ const existing = JSON.parse(raw);
4247
+ if (isProcessAlive(existing.pid)) return null;
4248
+ await (0, import_promises12.unlink)(lockPath);
4249
+ return acquireLock(outDir);
4250
+ } catch {
4251
+ return null;
4252
+ }
4253
+ }
4254
+ return null;
4255
+ }
4256
+ return {
4257
+ release: async () => {
4258
+ try {
4259
+ await (0, import_promises12.unlink)(lockPath);
4260
+ } catch {
4261
+ }
4262
+ }
4263
+ };
4264
+ }
4265
+
4266
+ // src/index.ts
4267
+ var VERSION = "0.12.0";
4268
+
4269
+ // src/generate-manifest.ts
4270
+ var MANIFEST_FILE = ".codegen-manifest.json";
4271
+ var LOCK_FILE2 = ".watcher.lock";
4272
+ function isManifestShape(value) {
4273
+ if (typeof value !== "object" || value === null) return false;
4274
+ const candidate = value;
4275
+ if (typeof candidate.version !== "string") return false;
4276
+ if (typeof candidate.hash !== "string") return false;
4277
+ if (!Array.isArray(candidate.files)) return false;
4278
+ return candidate.files.every((entry) => typeof entry === "string");
4279
+ }
4280
+ function serializeConfig(config) {
4281
+ try {
4282
+ return JSON.stringify(config, (_key, value) => {
4283
+ if (typeof value === "function") return `[fn:${value.name}]${value.toString()}`;
4284
+ return value;
4285
+ });
4286
+ } catch {
4287
+ return `unserializable:${config.codegen.outDir}:${config.contracts.glob}`;
4288
+ }
4289
+ }
4290
+ async function discoverInputFiles(config) {
4291
+ const globs = [config.contracts.glob, config.forms.watch];
4292
+ if (config.pages) globs.push(config.pages.glob);
4293
+ const cwd = config.codegen.cwd;
4294
+ const matched = await (0, import_fast_glob3.default)(globs, { cwd, absolute: true, onlyFiles: true });
4295
+ return [...new Set(matched)].sort();
4296
+ }
4297
+ async function computeInputsHash(config) {
4298
+ const hash = (0, import_node_crypto.createHash)("sha256");
4299
+ hash.update(`version:${VERSION}
4300
+ `);
4301
+ hash.update(`config:${serializeConfig(config)}
4302
+ `);
4303
+ const inputFiles = await discoverInputFiles(config);
4304
+ const cwd = config.codegen.cwd;
4305
+ for (const file of inputFiles) {
4306
+ const contents = await (0, import_promises13.readFile)(file, "utf8");
4307
+ hash.update(`file:${(0, import_node_path15.relative)(cwd, file)}
4308
+ `);
4309
+ hash.update(contents);
4310
+ hash.update("\n");
4311
+ }
4312
+ return hash.digest("hex");
4313
+ }
4314
+ async function readManifest(outDir) {
4315
+ try {
4316
+ const raw = await (0, import_promises13.readFile)((0, import_node_path15.join)(outDir, MANIFEST_FILE), "utf8");
4317
+ const parsed = JSON.parse(raw);
4318
+ if (!isManifestShape(parsed)) return null;
4319
+ return { version: parsed.version, hash: parsed.hash, files: parsed.files };
4320
+ } catch {
4321
+ return null;
4322
+ }
4323
+ }
4324
+ async function writeManifest(outDir, manifest) {
4325
+ await (0, import_promises13.writeFile)((0, import_node_path15.join)(outDir, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}
4326
+ `, "utf8");
4327
+ }
4328
+ async function listOutputFiles(outDir) {
4329
+ const found = [];
4330
+ async function walk(dir) {
4331
+ const entries = await (0, import_promises13.readdir)(dir, { withFileTypes: true }).catch(() => []);
4332
+ for (const entry of entries) {
4333
+ const abs = (0, import_node_path15.join)(dir, entry.name);
4334
+ if (entry.isDirectory()) {
4335
+ await walk(abs);
4336
+ } else if (entry.isFile()) {
4337
+ const rel = (0, import_node_path15.relative)(outDir, abs);
4338
+ if (rel === MANIFEST_FILE || rel === LOCK_FILE2) continue;
4339
+ found.push(rel);
4340
+ }
4341
+ }
4342
+ }
4343
+ await walk(outDir);
4344
+ return found.sort();
4345
+ }
4346
+ async function allOutputsExist(outDir, files) {
4347
+ const present = new Set(await listOutputFiles(outDir));
4348
+ return files.every((file) => present.has(file));
4349
+ }
4350
+ async function isManifestFresh(outDir, manifest, inputsHash) {
4351
+ if (manifest === null) return false;
4352
+ if (manifest.version !== VERSION) return false;
4353
+ if (manifest.hash !== inputsHash) return false;
4354
+ if (manifest.files.length === 0) return false;
4355
+ return allOutputsExist(outDir, manifest.files);
4356
+ }
4357
+
4210
4358
  // src/generate.ts
4211
4359
  async function generate(config, inputRoutes = []) {
4212
4360
  setCodegenDebug(config.debug);
4361
+ const inputsHash = await computeInputsHash(config);
4362
+ const manifest = await readManifest(config.codegen.outDir);
4363
+ if (await isManifestFresh(config.codegen.outDir, manifest, inputsHash)) {
4364
+ console.log(`[nestjs-codegen] ${config.codegen.outDir} up to date, skipped`);
4365
+ return;
4366
+ }
4213
4367
  const extensions = config.extensions ?? [];
4214
4368
  let routes = inputRoutes;
4215
4369
  const ctx = createExtensionContext(config, () => routes);
@@ -4266,69 +4420,29 @@ async function generate(config, inputRoutes = []) {
4266
4420
  if (extensions.length > 0) {
4267
4421
  const extraFiles = await collectEmittedFiles(extensions, ctx);
4268
4422
  for (const file of extraFiles) {
4269
- const dest = (0, import_node_path14.join)(config.codegen.outDir, file.path);
4270
- await (0, import_promises11.mkdir)((0, import_node_path14.dirname)(dest), { recursive: true });
4271
- await (0, import_promises11.writeFile)(dest, file.contents, "utf8");
4272
- }
4273
- }
4274
- }
4275
-
4276
- // src/watch/lock-file.ts
4277
- var import_promises12 = require("fs/promises");
4278
- var import_promises13 = require("fs/promises");
4279
- var import_node_path15 = require("path");
4280
- var LOCK_FILE = ".watcher.lock";
4281
- function isProcessAlive(pid) {
4282
- try {
4283
- process.kill(pid, 0);
4284
- return true;
4285
- } catch {
4286
- return false;
4287
- }
4288
- }
4289
- async function acquireLock(outDir) {
4290
- await (0, import_promises13.mkdir)(outDir, { recursive: true });
4291
- const lockPath = (0, import_node_path15.join)(outDir, LOCK_FILE);
4292
- const lockData = { pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
4293
- try {
4294
- const fd = await (0, import_promises12.open)(lockPath, "wx");
4295
- await fd.writeFile(`${JSON.stringify(lockData, null, 2)}
4296
- `, "utf8");
4297
- await fd.close();
4298
- } catch (err) {
4299
- if (err.code === "EEXIST") {
4300
- try {
4301
- const raw = await (0, import_promises13.readFile)(lockPath, "utf8");
4302
- const existing = JSON.parse(raw);
4303
- if (isProcessAlive(existing.pid)) return null;
4304
- await (0, import_promises13.unlink)(lockPath);
4305
- return acquireLock(outDir);
4306
- } catch {
4307
- return null;
4308
- }
4423
+ const dest = (0, import_node_path16.join)(config.codegen.outDir, file.path);
4424
+ await (0, import_promises14.mkdir)((0, import_node_path16.dirname)(dest), { recursive: true });
4425
+ await (0, import_promises14.writeFile)(dest, file.contents, "utf8");
4309
4426
  }
4310
- return null;
4311
4427
  }
4312
- return {
4313
- release: async () => {
4314
- try {
4315
- await (0, import_promises13.unlink)(lockPath);
4316
- } catch {
4317
- }
4318
- }
4319
- };
4428
+ const outputFiles = await listOutputFiles(config.codegen.outDir);
4429
+ await writeManifest(config.codegen.outDir, {
4430
+ version: VERSION,
4431
+ hash: inputsHash,
4432
+ files: outputFiles
4433
+ });
4320
4434
  }
4321
4435
 
4322
4436
  // src/watch/watcher.ts
4323
4437
  var PAGES_DEBOUNCE_MS = 150;
4324
4438
  var NO_OP_WATCHER = { close: async () => {
4325
4439
  } };
4326
- async function watch(config, onChange) {
4440
+ async function watch(config, onChange, options = {}) {
4327
4441
  const lock = await acquireLock(config.codegen.outDir);
4328
4442
  if (lock === null) {
4329
4443
  let holderPid = "unknown";
4330
4444
  try {
4331
- const raw = await (0, import_promises14.readFile)((0, import_node_path16.join)(config.codegen.outDir, ".watcher.lock"), "utf8");
4445
+ const raw = await (0, import_promises15.readFile)((0, import_node_path17.join)(config.codegen.outDir, ".watcher.lock"), "utf8");
4332
4446
  const data = JSON.parse(raw);
4333
4447
  if (data.pid !== void 0) holderPid = String(data.pid);
4334
4448
  } catch {
@@ -4351,22 +4465,33 @@ async function watch(config, onChange) {
4351
4465
  }
4352
4466
  return discovery;
4353
4467
  }
4354
- try {
4355
- const initialRoutes = (await getDiscovery()).discover();
4356
- lastRoutes = initialRoutes;
4357
- await generate(config, initialRoutes);
4358
- } catch (err) {
4359
- console.warn(
4360
- `[nestjs-codegen] Initial route discovery failed, falling back to pages-only: ${err instanceof Error ? err.message : String(err)}`
4361
- );
4468
+ async function runInitialPass() {
4362
4469
  try {
4363
- await generate(config, lastRoutes);
4364
- } catch {
4470
+ const initialRoutes = (await getDiscovery()).discover();
4471
+ lastRoutes = initialRoutes;
4472
+ await generate(config, initialRoutes);
4473
+ } catch (err) {
4474
+ console.warn(
4475
+ `[nestjs-codegen] Initial route discovery failed, falling back to pages-only: ${err instanceof Error ? err.message : String(err)}`
4476
+ );
4477
+ try {
4478
+ await generate(config, lastRoutes);
4479
+ } catch {
4480
+ }
4365
4481
  }
4366
4482
  }
4483
+ if (options.deferInitialGenerate) {
4484
+ void runInitialPass().catch((err) => {
4485
+ console.warn(
4486
+ `[nestjs-codegen] Background initial generate failed: ${err instanceof Error ? err.message : String(err)}`
4487
+ );
4488
+ });
4489
+ } else {
4490
+ await runInitialPass();
4491
+ }
4367
4492
  let pagesDebounceTimer;
4368
4493
  const pagesGlob = config.pages?.glob ?? ".nestjs-codegen-no-pages";
4369
- const pagesWatcher = import_chokidar.default.watch((0, import_node_path16.join)(config.codegen.cwd, pagesGlob), {
4494
+ const pagesWatcher = import_chokidar.default.watch((0, import_node_path17.join)(config.codegen.cwd, pagesGlob), {
4370
4495
  ignoreInitial: true,
4371
4496
  persistent: true,
4372
4497
  awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
@@ -4393,7 +4518,7 @@ async function watch(config, onChange) {
4393
4518
  pagesWatcher.on("unlink", schedulePagesRegenerate);
4394
4519
  let contractsDebounceTimer;
4395
4520
  const pendingChangedPaths = /* @__PURE__ */ new Set();
4396
- const contractsWatcher = import_chokidar.default.watch((0, import_node_path16.join)(config.codegen.cwd, config.contracts.glob), {
4521
+ const contractsWatcher = import_chokidar.default.watch((0, import_node_path17.join)(config.codegen.cwd, config.contracts.glob), {
4397
4522
  ignoreInitial: true,
4398
4523
  persistent: true,
4399
4524
  awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
@@ -4423,7 +4548,7 @@ async function watch(config, onChange) {
4423
4548
  contractsWatcher.on("add", (p) => scheduleContractsRegenerate(p));
4424
4549
  contractsWatcher.on("change", (p) => scheduleContractsRegenerate(p));
4425
4550
  contractsWatcher.on("unlink", (p) => scheduleContractsRegenerate(p));
4426
- const formsWatcher = import_chokidar.default.watch((0, import_node_path16.join)(config.codegen.cwd, config.forms.watch), {
4551
+ const formsWatcher = import_chokidar.default.watch((0, import_node_path17.join)(config.codegen.cwd, config.forms.watch), {
4427
4552
  ignoreInitial: true,
4428
4553
  persistent: true,
4429
4554
  awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 20 }
@@ -4451,9 +4576,13 @@ async function watch(config, onChange) {
4451
4576
 
4452
4577
  // src/nest/module.ts
4453
4578
  var CODEGEN_MODULE_OPTIONS = Symbol("NESTJS_CODEGEN_MODULE_OPTIONS");
4579
+ function isProductionEnv(env) {
4580
+ return (env ?? "").trim().toLowerCase() === "production";
4581
+ }
4454
4582
  function shouldRun(options, env) {
4455
4583
  if (options.enabled !== void 0) return options.enabled;
4456
- return env !== "production";
4584
+ if (isProductionEnv(env)) return options.runInProduction === true;
4585
+ return true;
4457
4586
  }
4458
4587
  var NestjsCodegenService = class {
4459
4588
  constructor(options) {
@@ -4462,11 +4591,21 @@ var NestjsCodegenService = class {
4462
4591
  logger = new import_common.Logger("NestjsCodegen");
4463
4592
  watcher = null;
4464
4593
  async onApplicationBootstrap() {
4465
- if (!shouldRun(this.options, process.env.NODE_ENV)) return;
4466
- const { enabled: _enabled, cwd, ...userConfig } = this.options;
4594
+ if (!shouldRun(this.options, process.env.NODE_ENV)) {
4595
+ if (this.options.enabled === void 0 && isProductionEnv(process.env.NODE_ENV)) {
4596
+ this.logger.log("Skipped in production (set runInProduction: true to override).");
4597
+ }
4598
+ return;
4599
+ }
4600
+ const {
4601
+ enabled: _enabled,
4602
+ runInProduction: _runInProduction,
4603
+ cwd,
4604
+ ...userConfig
4605
+ } = this.options;
4467
4606
  try {
4468
4607
  const config = resolveConfig(userConfig, cwd ?? process.cwd());
4469
- this.watcher = await watch(config);
4608
+ this.watcher = await watch(config, void 0, { deferInitialGenerate: true });
4470
4609
  this.logger.log(`Watching ${config.contracts.glob} \u2192 ${config.codegen.outDir}`);
4471
4610
  } catch (err) {
4472
4611
  this.logger.warn(