@drzl/cli 4.37.0 → 4.39.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/dist/cli.cjs CHANGED
@@ -28,7 +28,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  var import_analyzer3 = require("@drzl/analyzer");
29
29
  var import_chokidar = __toESM(require("chokidar"), 1);
30
30
  var import_commander = require("commander");
31
- var path8 = __toESM(require("path"), 1);
31
+ var path9 = __toESM(require("path"), 1);
32
32
 
33
33
  // src/output.ts
34
34
  var import_chalk = require("chalk");
@@ -519,6 +519,7 @@ var GeneratorKindSchema = import_zod.z.enum([
519
519
  "forms",
520
520
  "seed",
521
521
  "fast-check",
522
+ "pothos",
522
523
  "service",
523
524
  "zod",
524
525
  "valibot",
@@ -1098,6 +1099,9 @@ function seedOutDir(g, cfg) {
1098
1099
  function fastCheckOutDir(g, cfg) {
1099
1100
  return g.path ?? cfg.outDir;
1100
1101
  }
1102
+ function pothosOutDir(g, cfg) {
1103
+ return g.path ?? cfg.outDir;
1104
+ }
1101
1105
  function sharedSchemaNames(opts) {
1102
1106
  const resolved = (0, import_validation_core.resolveAffix)(opts);
1103
1107
  return import_validation_core.NAME_MODES.map((mode) => (0, import_validation_core.schemaName)(mode, import_validation_core.AFFIX_PROBE_TABLE, resolved));
@@ -1418,6 +1422,7 @@ function computeGeneratorOutputDirs(cfg, cwd = process.cwd()) {
1418
1422
  if (g.kind === "forms") dirs.add(abs(formsOutDir(g, cfg)));
1419
1423
  if (g.kind === "seed") dirs.add(abs(seedOutDir(g, cfg)));
1420
1424
  if (g.kind === "fast-check") dirs.add(abs(fastCheckOutDir(g, cfg)));
1425
+ if (g.kind === "pothos") dirs.add(abs(pothosOutDir(g, cfg)));
1421
1426
  if (g.kind === "service") dirs.add(abs(g.path ?? "src/services"));
1422
1427
  if (g.kind === "zod") dirs.add(abs(g.path ?? "src/validators/zod"));
1423
1428
  if (g.kind === "valibot") dirs.add(abs(g.path ?? "src/validators/valibot"));
@@ -1785,6 +1790,17 @@ function fastCheckOptions(g, cfg) {
1785
1790
  };
1786
1791
  }
1787
1792
 
1793
+ // src/pothos-options.ts
1794
+ function pothosOptions(g, cfg) {
1795
+ return {
1796
+ outputDir: pothosOutDir(g, cfg),
1797
+ naming: g.naming,
1798
+ outputHeader: g.outputHeader,
1799
+ format: g.format,
1800
+ importExtension: g.importExtension
1801
+ };
1802
+ }
1803
+
1788
1804
  // src/h3-options.ts
1789
1805
  var VALIDATOR_DEFAULT_DIRS6 = {
1790
1806
  zod: "src/validators/zod",
@@ -2147,6 +2163,14 @@ var GENERATORS = [
2147
2163
  outputDir: (g, cfg) => fastCheckOutDir(g, cfg),
2148
2164
  options: (g, cfg) => fastCheckOptions(g, cfg)
2149
2165
  },
2166
+ {
2167
+ kind: "pothos",
2168
+ specifier: "@drzl/generator-pothos",
2169
+ load: () => import("@drzl/generator-pothos"),
2170
+ construct: (m, analysis) => new m.PothosGenerator(analysis),
2171
+ outputDir: (g, cfg) => pothosOutDir(g, cfg),
2172
+ options: (g, cfg) => pothosOptions(g, cfg)
2173
+ },
2150
2174
  {
2151
2175
  kind: "service",
2152
2176
  specifier: "@drzl/generator-service",
@@ -2390,14 +2414,109 @@ var import_validation_core3 = require("@drzl/validation-core");
2390
2414
 
2391
2415
  // src/doctor.ts
2392
2416
  var import_validation_core2 = require("@drzl/validation-core");
2417
+
2418
+ // src/auth-tables.ts
2419
+ var SIGNATURES = [
2420
+ {
2421
+ model: "account",
2422
+ // `providerId` beside `accountId` is the distinctive pair: it is a link to an external identity
2423
+ // provider, which an application table has no reason to model this way.
2424
+ required: ["accountId", "providerId", "userId"],
2425
+ supporting: ["accessToken", "refreshToken", "idToken", "password", "scope"],
2426
+ conventionalName: "account",
2427
+ secrets: ["accessToken", "refreshToken", "idToken", "password"]
2428
+ },
2429
+ {
2430
+ model: "session",
2431
+ required: ["token", "expiresAt", "userId"],
2432
+ supporting: ["ipAddress", "userAgent"],
2433
+ conventionalName: "session",
2434
+ // A session token is a bearer credential: whoever reads it is that user until it expires.
2435
+ secrets: ["token"]
2436
+ },
2437
+ {
2438
+ model: "verification",
2439
+ // `identifier` and `value` are deliberately generic names, which is why `expiresAt` is required
2440
+ // too: the three together are a short-lived token store and little else.
2441
+ required: ["identifier", "value", "expiresAt"],
2442
+ supporting: [],
2443
+ conventionalName: "verification",
2444
+ secrets: ["value"]
2445
+ },
2446
+ {
2447
+ model: "user",
2448
+ required: ["email", "emailVerified"],
2449
+ supporting: ["name", "image"],
2450
+ conventionalName: "user",
2451
+ secrets: []
2452
+ }
2453
+ ];
2454
+ function columnKeys(table) {
2455
+ const out = /* @__PURE__ */ new Map();
2456
+ for (const c of table.columns) out.set(normalise(c.name), c);
2457
+ return out;
2458
+ }
2459
+ function normalise(name) {
2460
+ return name.replace(/[_-]/g, "").toLowerCase();
2461
+ }
2462
+ function has(keys, name) {
2463
+ return keys.has(normalise(name));
2464
+ }
2465
+ function actualName(table, wanted) {
2466
+ return table.columns.find((c) => normalise(c.name) === normalise(wanted))?.name;
2467
+ }
2468
+ function matchOne(table, sig) {
2469
+ const keys = columnKeys(table);
2470
+ const missing = sig.required.filter((r) => !has(keys, r));
2471
+ if (missing.length) return void 0;
2472
+ const supporting = sig.supporting.filter((s) => has(keys, s));
2473
+ const nameMatches = normalise(table.name) === normalise(sig.conventionalName) || normalise(table.name) === `${normalise(sig.conventionalName)}s`;
2474
+ const confidence = supporting.length > 0 || nameMatches ? "strong" : "likely";
2475
+ const matched = [...sig.required, ...supporting].map((c) => actualName(table, c)).filter((c) => Boolean(c));
2476
+ const secrets = sig.secrets.map((c) => actualName(table, c)).filter((c) => Boolean(c));
2477
+ return { table: table.name, model: sig.model, confidence, matched, secrets };
2478
+ }
2479
+ function detectAuthTables(analysis) {
2480
+ const found = [];
2481
+ for (const table of analysis.tables) {
2482
+ for (const sig of SIGNATURES) {
2483
+ const m = matchOne(table, sig);
2484
+ if (m) {
2485
+ found.push(m);
2486
+ break;
2487
+ }
2488
+ }
2489
+ }
2490
+ const hasNonUser = found.some((f) => f.model !== "user");
2491
+ return hasNonUser ? found : found.filter((f) => f.model !== "user");
2492
+ }
2493
+ function authTablesWithSecrets(matches) {
2494
+ return matches.filter((m) => m.secrets.length > 0);
2495
+ }
2496
+ function excludeSuggestion(matches) {
2497
+ const names = [...new Set(matches.map((m) => m.table))].sort();
2498
+ return `exclude: [${names.map((n) => `'${n}'`).join(", ")}]`;
2499
+ }
2500
+ function authTableWarnings(analysis) {
2501
+ const risky = authTablesWithSecrets(detectAuthTables(analysis));
2502
+ if (!risky.length) return [];
2503
+ const lines = risky.map(
2504
+ (m) => `"${m.table}" looks like an authentication library's ${m.model} table and holds ${m.secrets.map((c) => `"${c}"`).join(", ")}. Generating for it publishes ${m.secrets.length === 1 ? "that column" : "those columns"}.`
2505
+ );
2506
+ return [
2507
+ `drzl generate: ${lines.join(" ")} Keep ${risky.length === 1 ? "it" : "them"} out with ${excludeSuggestion(risky)}, or leave it if the route is deliberate.`
2508
+ ];
2509
+ }
2510
+
2511
+ // src/doctor.ts
2393
2512
  var import_chalk2 = require("chalk");
2394
2513
  var PLAIN = new import_chalk2.Chalk({ level: 0 });
2395
2514
  var HANDLED_CODES = /* @__PURE__ */ new Set(["DRZL_ANL_UNKNOWN_COLUMN"]);
2396
- function splitPath(path9) {
2397
- if (!path9) return {};
2398
- const dot = path9.lastIndexOf(".");
2399
- if (dot <= 0) return { table: path9 };
2400
- return { table: path9.slice(0, dot), column: path9.slice(dot + 1) };
2515
+ function splitPath(path10) {
2516
+ if (!path10) return {};
2517
+ const dot = path10.lastIndexOf(".");
2518
+ if (dot <= 0) return { table: path10 };
2519
+ return { table: path10.slice(0, dot), column: path10.slice(dot + 1) };
2401
2520
  }
2402
2521
  function namedColumns(parsed) {
2403
2522
  const out = [];
@@ -2562,6 +2681,18 @@ function buildDoctorReport(analysis, schemaPath) {
2562
2681
  hint: i.hint
2563
2682
  });
2564
2683
  }
2684
+ for (const match of detectAuthTables(analysis)) {
2685
+ const secrets = match.secrets.length ? ` It holds ${match.secrets.map((c) => `\`${c}\``).join(", ")}, which a generated read route would return to whoever calls it.` : "";
2686
+ findings.push({
2687
+ kind: "auth-table",
2688
+ // A warning rather than an error: this is a real leak and it is also a guess about someone
2689
+ // else's schema, and a doctor that failed the build on a guess would be switched off.
2690
+ level: "warn",
2691
+ table: match.table,
2692
+ message: `"${match.table}" matches the shape of an authentication library's ${match.model} table (${match.matched.join(", ")}).${secrets}`,
2693
+ hint: `Keep it out of every generator with ${excludeSuggestion([match])}.`
2694
+ });
2695
+ }
2565
2696
  for (const i of analysis.issues) {
2566
2697
  if (i.code !== "DRZL_ANL_UNKNOWN_COLUMN") continue;
2567
2698
  findings.push({
@@ -2600,6 +2731,11 @@ var SECTIONS = [
2600
2731
  title: "Columns DRZL cannot type",
2601
2732
  why: "These get a validator that accepts any value."
2602
2733
  },
2734
+ {
2735
+ kinds: ["auth-table"],
2736
+ title: "Tables that look like an authentication library's",
2737
+ why: "Every generator loops over every table it finds, so these get routes too."
2738
+ },
2603
2739
  {
2604
2740
  kinds: ["check-declined", "check-unknown-column", "check-not-scalar", "check-uncountable"],
2605
2741
  title: "CHECK constraints DRZL does not enforce",
@@ -2932,11 +3068,11 @@ function issueTouches(issue, table) {
2932
3068
  return dot > 0 && own.includes(issue.path.slice(0, dot));
2933
3069
  }
2934
3070
  function issueColumn(issue, table) {
2935
- const path9 = issue.path ?? "";
3071
+ const path10 = issue.path ?? "";
2936
3072
  const names = namesOf(table);
2937
3073
  for (const prefix of [names.qualified, names.tsName, names.name, table.name]) {
2938
- if (path9.startsWith(`${prefix}.`)) {
2939
- const rest = path9.slice(prefix.length + 1);
3074
+ if (path10.startsWith(`${prefix}.`)) {
3075
+ const rest = path10.slice(prefix.length + 1);
2940
3076
  if (table.columns.some((c) => c.name === rest)) return rest;
2941
3077
  }
2942
3078
  }
@@ -4095,6 +4231,79 @@ function displayPath(file, cwd = process.cwd()) {
4095
4231
  return rel && !rel.startsWith("..") ? rel : file;
4096
4232
  }
4097
4233
 
4234
+ // src/manifest.ts
4235
+ var import_node_path3 = __toESM(require("path"), 1);
4236
+ var import_node_fs3 = require("fs");
4237
+ var MANIFEST_VERSION = 1;
4238
+ var MANIFEST_PATH = import_node_path3.default.join(".drzl", "manifest.json");
4239
+ function toPosix(p) {
4240
+ return p.split(import_node_path3.default.sep).join("/");
4241
+ }
4242
+ function manifestEntries(files, root) {
4243
+ const rel = files.map((f) => toPosix(import_node_path3.default.relative(root, f)));
4244
+ return [...new Set(rel)].sort();
4245
+ }
4246
+ async function readManifest(root) {
4247
+ try {
4248
+ const raw = await import_node_fs3.promises.readFile(import_node_path3.default.join(root, MANIFEST_PATH), "utf8");
4249
+ const parsed = JSON.parse(raw);
4250
+ if (!parsed || typeof parsed !== "object" || parsed.version !== MANIFEST_VERSION || !Array.isArray(parsed.files) || !parsed.files.every((f) => typeof f === "string")) {
4251
+ return void 0;
4252
+ }
4253
+ return { version: MANIFEST_VERSION, files: parsed.files };
4254
+ } catch {
4255
+ return void 0;
4256
+ }
4257
+ }
4258
+ async function writeManifest(root, files) {
4259
+ const manifest = { version: MANIFEST_VERSION, files: manifestEntries(files, root) };
4260
+ const target = import_node_path3.default.join(root, MANIFEST_PATH);
4261
+ await import_node_fs3.promises.mkdir(import_node_path3.default.dirname(target), { recursive: true });
4262
+ await import_node_fs3.promises.writeFile(target, `${JSON.stringify(manifest, null, 2)}
4263
+ `, "utf8");
4264
+ }
4265
+ async function staleFiles(previous, writtenNow, root) {
4266
+ if (!previous) return [];
4267
+ const current = new Set(manifestEntries(writtenNow, root));
4268
+ const gone = previous.files.filter((f) => !current.has(f));
4269
+ const out = [];
4270
+ for (const file of gone) {
4271
+ let present = true;
4272
+ try {
4273
+ await import_node_fs3.promises.access(import_node_path3.default.join(root, file));
4274
+ } catch {
4275
+ present = false;
4276
+ }
4277
+ out.push({ file, present });
4278
+ }
4279
+ return out;
4280
+ }
4281
+ async function pruneStale(root, stale) {
4282
+ const deleted = [];
4283
+ for (const entry of stale) {
4284
+ if (!entry.present) continue;
4285
+ const target = import_node_path3.default.resolve(root, entry.file);
4286
+ const inside = target === root || target.startsWith(root + import_node_path3.default.sep);
4287
+ if (!inside) continue;
4288
+ try {
4289
+ await import_node_fs3.promises.rm(target);
4290
+ deleted.push(entry.file);
4291
+ } catch {
4292
+ }
4293
+ }
4294
+ return deleted;
4295
+ }
4296
+ function staleWarning(stale) {
4297
+ const present = stale.filter((s) => s.present);
4298
+ if (!present.length) return void 0;
4299
+ const list = present.map((s) => s.file).join(", ");
4300
+ return `drzl generate: ${present.length} file(s) written by a previous run were not written by this one, and are still on disk: ${list}. That usually means a table was renamed or removed. Delete them with \`drzl generate --prune\`, which removes only files a previous run recorded writing.`;
4301
+ }
4302
+ function nextManifestFiles(writtenNow, stale, root) {
4303
+ const kept = stale.filter((s) => s.present).map((s) => import_node_path3.default.resolve(root, s.file));
4304
+ return manifestEntries([...writtenNow, ...kept], root);
4305
+ }
4306
+
4098
4307
  // src/unified-diff.ts
4099
4308
  var DEFAULT_DIFF_LIMITS = {
4100
4309
  maxLines: 4e3,
@@ -4337,8 +4546,8 @@ function createRebuildScheduler(options) {
4337
4546
 
4338
4547
  // src/init.ts
4339
4548
  var import_analyzer2 = require("@drzl/analyzer");
4340
- var fs5 = __toESM(require("fs"), 1);
4341
- var path5 = __toESM(require("path"), 1);
4549
+ var fs6 = __toESM(require("fs"), 1);
4550
+ var path6 = __toESM(require("path"), 1);
4342
4551
  var INIT_GENERATOR_CHOICES = [
4343
4552
  { kind: "zod", packageName: "@drzl/generator-zod", label: "Zod validators" },
4344
4553
  { kind: "valibot", packageName: "@drzl/generator-valibot", label: "Valibot validators" },
@@ -4409,7 +4618,7 @@ async function detectSchema(cwd) {
4409
4618
  kitFiles = null;
4410
4619
  }
4411
4620
  if (kitFiles && kitPath) {
4412
- const rel = path5.relative(cwd, kitPath) || path5.basename(kitPath);
4621
+ const rel = path6.relative(cwd, kitPath) || path6.basename(kitPath);
4413
4622
  const report = await classifySchemaCandidate(kitFiles);
4414
4623
  if (report.verdict === "confirmed" || report.verdict === "unverified") {
4415
4624
  notes.push(
@@ -4425,10 +4634,10 @@ async function detectSchema(cwd) {
4425
4634
  }
4426
4635
  notes.push(`${rel} names schema files that declare no Drizzle tables; looking elsewhere.`);
4427
4636
  }
4428
- const present = schemaCandidates().filter((c) => fs5.existsSync(path5.resolve(cwd, c)));
4637
+ const present = schemaCandidates().filter((c) => fs6.existsSync(path6.resolve(cwd, c)));
4429
4638
  const unverified = [];
4430
4639
  for (const file of present) {
4431
- const report = await classifySchemaCandidate(path5.resolve(cwd, file));
4640
+ const report = await classifySchemaCandidate(path6.resolve(cwd, file));
4432
4641
  if (report.verdict === "confirmed") {
4433
4642
  notes.push(
4434
4643
  `Schema found at ${file} (${report.tables} table${report.tables === 1 ? "" : "s"})`
@@ -4530,7 +4739,7 @@ async function promptForPlan(args) {
4530
4739
  if (answer === null) endedEarly = true;
4531
4740
  else if (answer.trim()) {
4532
4741
  const typed = answer.trim();
4533
- const report = await classifySchemaCandidate(path5.resolve(cwd, typed));
4742
+ const report = await classifySchemaCandidate(path6.resolve(cwd, typed));
4534
4743
  if (report.verdict === "confirmed") {
4535
4744
  write(` ${typed}: ${report.tables} table${report.tables === 1 ? "" : "s"}`);
4536
4745
  } else if (report.verdict === "unverified") {
@@ -4592,8 +4801,8 @@ function parseGeneratorsFlag(value) {
4592
4801
  return parts;
4593
4802
  }
4594
4803
  async function runInit(args) {
4595
- const target = path5.resolve(args.cwd, "drzl.config.ts");
4596
- const existing = CONFIG_FILE_NAMES.find((name) => fs5.existsSync(path5.resolve(args.cwd, name)));
4804
+ const target = path6.resolve(args.cwd, "drzl.config.ts");
4805
+ const existing = CONFIG_FILE_NAMES.find((name) => fs6.existsSync(path6.resolve(args.cwd, name)));
4597
4806
  if (existing) {
4598
4807
  args.error(
4599
4808
  `drzl init: ${existing} already exists, so nothing was written. Delete it, or edit it by hand; init never overwrites a config, and will not write one that shadows it either.`
@@ -4632,8 +4841,8 @@ async function runInit(args) {
4632
4841
  generators: fromFlag ?? [DEFAULT_GENERATOR_KIND]
4633
4842
  };
4634
4843
  if (args.schemaFlag) {
4635
- const full = path5.resolve(args.cwd, args.schemaFlag);
4636
- if (!fs5.existsSync(full)) {
4844
+ const full = path6.resolve(args.cwd, args.schemaFlag);
4845
+ if (!fs6.existsSync(full)) {
4637
4846
  args.log(`--schema ${args.schemaFlag} is not there yet. Writing it anyway.`);
4638
4847
  } else if ((await classifySchemaCandidate(full)).verdict === "rejected") {
4639
4848
  args.log(`--schema ${args.schemaFlag} declares no Drizzle tables. Writing it anyway.`);
@@ -4641,7 +4850,7 @@ async function runInit(args) {
4641
4850
  }
4642
4851
  }
4643
4852
  try {
4644
- fs5.writeFileSync(target, renderInitConfig(plan), { flag: "wx" });
4853
+ fs6.writeFileSync(target, renderInitConfig(plan), { flag: "wx" });
4645
4854
  } catch (e) {
4646
4855
  if (e?.code === "EEXIST") {
4647
4856
  args.error(
@@ -4664,10 +4873,10 @@ async function runInit(args) {
4664
4873
  }
4665
4874
 
4666
4875
  // src/sponsor.ts
4667
- var import_node_fs3 = require("fs");
4668
- var import_node_path3 = __toESM(require("path"), 1);
4669
- var CACHE_DIR = import_node_path3.default.join(process.cwd(), "node_modules", ".cache", "@drzl");
4670
- var CACHE_FILE = import_node_path3.default.join(CACHE_DIR, "sponsor-message.json");
4876
+ var import_node_fs4 = require("fs");
4877
+ var import_node_path4 = __toESM(require("path"), 1);
4878
+ var CACHE_DIR = import_node_path4.default.join(process.cwd(), "node_modules", ".cache", "@drzl");
4879
+ var CACHE_FILE = import_node_path4.default.join(CACHE_DIR, "sponsor-message.json");
4671
4880
  var DEFAULT_INTERVAL_MS = 1e3 * 60 * 15;
4672
4881
  var shownThisProcess = false;
4673
4882
  var tips = [
@@ -4691,7 +4900,7 @@ function maybeShowSponsorMessage({
4691
4900
  if (hideRequested || process.env.CI && !force || shownThisProcess && !force) return;
4692
4901
  if (!out.wantsAsides && !force) return;
4693
4902
  try {
4694
- (0, import_node_fs3.mkdirSync)(CACHE_DIR, { recursive: true });
4903
+ (0, import_node_fs4.mkdirSync)(CACHE_DIR, { recursive: true });
4695
4904
  const payload = readCache();
4696
4905
  payload.runs += 1;
4697
4906
  const now = Date.now();
@@ -4719,11 +4928,11 @@ ${green("Pro tip:")} ${tip}
4719
4928
  }
4720
4929
  }
4721
4930
  function readCache() {
4722
- if (!(0, import_node_fs3.existsSync)(CACHE_FILE)) {
4931
+ if (!(0, import_node_fs4.existsSync)(CACHE_FILE)) {
4723
4932
  return { runs: 0 };
4724
4933
  }
4725
4934
  try {
4726
- const data = JSON.parse((0, import_node_fs3.readFileSync)(CACHE_FILE, "utf8"));
4935
+ const data = JSON.parse((0, import_node_fs4.readFileSync)(CACHE_FILE, "utf8"));
4727
4936
  if (typeof data.runs !== "number") return { runs: 0 };
4728
4937
  return data;
4729
4938
  } catch {
@@ -4731,21 +4940,21 @@ function readCache() {
4731
4940
  }
4732
4941
  }
4733
4942
  function writeCache(payload) {
4734
- (0, import_node_fs3.writeFileSync)(CACHE_FILE, JSON.stringify(payload, null, 2), "utf8");
4943
+ (0, import_node_fs4.writeFileSync)(CACHE_FILE, JSON.stringify(payload, null, 2), "utf8");
4735
4944
  }
4736
4945
 
4737
4946
  // src/version.ts
4738
- var import_node_fs4 = require("fs");
4739
- var path7 = __toESM(require("path"), 1);
4947
+ var import_node_fs5 = require("fs");
4948
+ var path8 = __toESM(require("path"), 1);
4740
4949
  var import_node_url = require("url");
4741
4950
  var PACKAGE_NAME = "@drzl/cli";
4742
4951
  function moduleDir() {
4743
- return path7.dirname((0, import_node_url.fileURLToPath)(__drzlModuleUrl));
4952
+ return path8.dirname((0, import_node_url.fileURLToPath)(__drzlModuleUrl));
4744
4953
  }
4745
4954
  function readVersionFrom(manifestPath) {
4746
4955
  let raw;
4747
4956
  try {
4748
- raw = (0, import_node_fs4.readFileSync)(manifestPath, "utf8");
4957
+ raw = (0, import_node_fs5.readFileSync)(manifestPath, "utf8");
4749
4958
  } catch (e) {
4750
4959
  throw new Error(
4751
4960
  `${PACKAGE_NAME} cannot read its own version: no manifest at ${manifestPath} (${e?.message ?? String(e)}).`
@@ -4763,7 +4972,7 @@ function readVersionFrom(manifestPath) {
4763
4972
  return manifest.version;
4764
4973
  }
4765
4974
  function readCliVersion() {
4766
- return readVersionFrom(path7.join(moduleDir(), "..", "package.json"));
4975
+ return readVersionFrom(path8.join(moduleDir(), "..", "package.json"));
4767
4976
  }
4768
4977
  var CLI_VERSION = readCliVersion();
4769
4978
 
@@ -4852,8 +5061,8 @@ withOutputFlags(
4852
5061
  const errors = res.issues.some((i) => i.level === "error");
4853
5062
  const code = unreadable ? EXIT_FAILED : errors ? EXIT_FINDINGS : EXIT_OK;
4854
5063
  if (opts.out && !opts.json) {
4855
- const fs6 = await import("fs/promises");
4856
- await fs6.writeFile(opts.out, JSON.stringify(res, null, 2), "utf8");
5064
+ const fs7 = await import("fs/promises");
5065
+ await fs7.writeFile(opts.out, JSON.stringify(res, null, 2), "utf8");
4857
5066
  spinner.succeed(`Analysis written to ${opts.out} in ${ms}ms`);
4858
5067
  } else {
4859
5068
  spinner.succeed(`Analyzed in ${ms}ms`);
@@ -4990,7 +5199,7 @@ async function explainSchemaSource(opts, out) {
4990
5199
  schema: source.schema,
4991
5200
  label: describeSchemaTarget(source.schema),
4992
5201
  config: cfg,
4993
- ...source.source === "drizzle-kit" && source.drizzleKitConfigPath ? { note: `Schema from ${path8.relative(process.cwd(), source.drizzleKitConfigPath)}` } : {}
5202
+ ...source.source === "drizzle-kit" && source.drizzleKitConfigPath ? { note: `Schema from ${path9.relative(process.cwd(), source.drizzleKitConfigPath)}` } : {}
4994
5203
  };
4995
5204
  }
4996
5205
  const detected = await detectSchema(process.cwd());
@@ -5100,7 +5309,11 @@ withOutputFlags(
5100
5309
  ).option(
5101
5310
  "--check",
5102
5311
  "regenerate and fail if the result differs from what is on disk, without changing it"
5103
- ).option("--dry-run", "report what would be written, and write nothing", false)
5312
+ ).option("--dry-run", "report what would be written, and write nothing", false).option(
5313
+ "--prune",
5314
+ "delete files a previous run wrote that this one did not, and nothing else",
5315
+ false
5316
+ )
5104
5317
  ).action(async (opts) => {
5105
5318
  const out = outputFor(opts);
5106
5319
  const planning = !!opts.check || !!opts.dryRun;
@@ -5150,7 +5363,7 @@ withOutputFlags(
5150
5363
  const n = source.schema.length;
5151
5364
  out.note(
5152
5365
  out.errStyle.gray(
5153
- `Schema from ${path8.relative(process.cwd(), source.drizzleKitConfigPath)} (${n} file${n === 1 ? "" : "s"})`
5366
+ `Schema from ${path9.relative(process.cwd(), source.drizzleKitConfigPath)} (${n} file${n === 1 ? "" : "s"})`
5154
5367
  )
5155
5368
  );
5156
5369
  }
@@ -5179,6 +5392,7 @@ withOutputFlags(
5179
5392
  analysis.tables = filterTables(narrowed.tables, cfg);
5180
5393
  for (const w of [...narrowed.warnings, ...filterWarnings]) warn(w);
5181
5394
  for (const w of wideColumnWarning(analysis.issues)) warn(w);
5395
+ for (const w of authTableWarnings(analysis)) warn(w);
5182
5396
  const empty = nothingToGenerate({
5183
5397
  schema: source.schema,
5184
5398
  analyzed: narrowed.tables,
@@ -5248,6 +5462,19 @@ withOutputFlags(
5248
5462
  files: e.files,
5249
5463
  changes: e.changes.map((c) => ({ file: displayPath(c.file), status: c.status }))
5250
5464
  }));
5465
+ const previousManifest = await readManifest(process.cwd());
5466
+ const writtenNow = plan.files.map((f) => f.file);
5467
+ const stale = await staleFiles(previousManifest, writtenNow, process.cwd());
5468
+ let stillStale = stale;
5469
+ if (opts.prune && !planning) {
5470
+ const removed = await pruneStale(process.cwd(), stale);
5471
+ for (const file of removed) out.warn(`drzl generate: removed stale ${file}`);
5472
+ const gone = new Set(removed);
5473
+ stillStale = stale.filter((s) => !gone.has(s.file));
5474
+ } else {
5475
+ const warning = staleWarning(stale);
5476
+ if (warning) warn(warning);
5477
+ }
5251
5478
  if (planning) {
5252
5479
  const wrote = await verifyNothingWasWritten(outputDirs, existing);
5253
5480
  if (wrote.length) {
@@ -5257,6 +5484,14 @@ withOutputFlags(
5257
5484
  process.exit(EXIT_FAILED);
5258
5485
  }
5259
5486
  }
5487
+ if (!planning) {
5488
+ await writeManifest(
5489
+ process.cwd(),
5490
+ nextManifestFiles(writtenNow, stillStale, process.cwd()).map(
5491
+ (f) => path9.resolve(process.cwd(), f)
5492
+ )
5493
+ );
5494
+ }
5260
5495
  if (opts.check) {
5261
5496
  const drift = pendingChanges(plan);
5262
5497
  const upToDate = drift.length === 0;
@@ -5498,10 +5733,10 @@ program.command("watch").description("Watch schema and regenerate on changes").o
5498
5733
  return;
5499
5734
  }
5500
5735
  let cfg = loaded;
5501
- const abs = (p) => path8.resolve(process.cwd(), p);
5736
+ const abs = (p) => path9.resolve(process.cwd(), p);
5502
5737
  const isInside = (child, parent) => {
5503
- const rel = path8.relative(parent, child);
5504
- return !!rel && !rel.startsWith("..") && !path8.isAbsolute(rel);
5738
+ const rel = path9.relative(parent, child);
5739
+ return !!rel && !rel.startsWith("..") && !path9.isAbsolute(rel);
5505
5740
  };
5506
5741
  let source;
5507
5742
  try {
@@ -5537,7 +5772,7 @@ program.command("watch").description("Watch schema and regenerate on changes").o
5537
5772
  if (full === dir || isInside(full, dir)) return true;
5538
5773
  }
5539
5774
  if (stats?.isDirectory()) return false;
5540
- const ext = path8.extname(full);
5775
+ const ext = path9.extname(full);
5541
5776
  if (!ext) return false;
5542
5777
  return !WATCHED_EXTENSIONS.has(ext);
5543
5778
  };
@@ -5700,7 +5935,7 @@ program.command("watch").description("Watch schema and regenerate on changes").o
5700
5935
  } else {
5701
5936
  out.note(
5702
5937
  out.errStyle.gray(
5703
- "Watching:\n " + Array.from(currentTargets).map((p) => path8.relative(process.cwd(), p)).join("\n ")
5938
+ "Watching:\n " + Array.from(currentTargets).map((p) => path9.relative(process.cwd(), p)).join("\n ")
5704
5939
  )
5705
5940
  );
5706
5941
  }