@drzl/cli 4.38.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");
@@ -2512,11 +2512,11 @@ function authTableWarnings(analysis) {
2512
2512
  var import_chalk2 = require("chalk");
2513
2513
  var PLAIN = new import_chalk2.Chalk({ level: 0 });
2514
2514
  var HANDLED_CODES = /* @__PURE__ */ new Set(["DRZL_ANL_UNKNOWN_COLUMN"]);
2515
- function splitPath(path9) {
2516
- if (!path9) return {};
2517
- const dot = path9.lastIndexOf(".");
2518
- if (dot <= 0) return { table: path9 };
2519
- 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) };
2520
2520
  }
2521
2521
  function namedColumns(parsed) {
2522
2522
  const out = [];
@@ -3068,11 +3068,11 @@ function issueTouches(issue, table) {
3068
3068
  return dot > 0 && own.includes(issue.path.slice(0, dot));
3069
3069
  }
3070
3070
  function issueColumn(issue, table) {
3071
- const path9 = issue.path ?? "";
3071
+ const path10 = issue.path ?? "";
3072
3072
  const names = namesOf(table);
3073
3073
  for (const prefix of [names.qualified, names.tsName, names.name, table.name]) {
3074
- if (path9.startsWith(`${prefix}.`)) {
3075
- const rest = path9.slice(prefix.length + 1);
3074
+ if (path10.startsWith(`${prefix}.`)) {
3075
+ const rest = path10.slice(prefix.length + 1);
3076
3076
  if (table.columns.some((c) => c.name === rest)) return rest;
3077
3077
  }
3078
3078
  }
@@ -4231,6 +4231,79 @@ function displayPath(file, cwd = process.cwd()) {
4231
4231
  return rel && !rel.startsWith("..") ? rel : file;
4232
4232
  }
4233
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
+
4234
4307
  // src/unified-diff.ts
4235
4308
  var DEFAULT_DIFF_LIMITS = {
4236
4309
  maxLines: 4e3,
@@ -4473,8 +4546,8 @@ function createRebuildScheduler(options) {
4473
4546
 
4474
4547
  // src/init.ts
4475
4548
  var import_analyzer2 = require("@drzl/analyzer");
4476
- var fs5 = __toESM(require("fs"), 1);
4477
- var path5 = __toESM(require("path"), 1);
4549
+ var fs6 = __toESM(require("fs"), 1);
4550
+ var path6 = __toESM(require("path"), 1);
4478
4551
  var INIT_GENERATOR_CHOICES = [
4479
4552
  { kind: "zod", packageName: "@drzl/generator-zod", label: "Zod validators" },
4480
4553
  { kind: "valibot", packageName: "@drzl/generator-valibot", label: "Valibot validators" },
@@ -4545,7 +4618,7 @@ async function detectSchema(cwd) {
4545
4618
  kitFiles = null;
4546
4619
  }
4547
4620
  if (kitFiles && kitPath) {
4548
- const rel = path5.relative(cwd, kitPath) || path5.basename(kitPath);
4621
+ const rel = path6.relative(cwd, kitPath) || path6.basename(kitPath);
4549
4622
  const report = await classifySchemaCandidate(kitFiles);
4550
4623
  if (report.verdict === "confirmed" || report.verdict === "unverified") {
4551
4624
  notes.push(
@@ -4561,10 +4634,10 @@ async function detectSchema(cwd) {
4561
4634
  }
4562
4635
  notes.push(`${rel} names schema files that declare no Drizzle tables; looking elsewhere.`);
4563
4636
  }
4564
- const present = schemaCandidates().filter((c) => fs5.existsSync(path5.resolve(cwd, c)));
4637
+ const present = schemaCandidates().filter((c) => fs6.existsSync(path6.resolve(cwd, c)));
4565
4638
  const unverified = [];
4566
4639
  for (const file of present) {
4567
- const report = await classifySchemaCandidate(path5.resolve(cwd, file));
4640
+ const report = await classifySchemaCandidate(path6.resolve(cwd, file));
4568
4641
  if (report.verdict === "confirmed") {
4569
4642
  notes.push(
4570
4643
  `Schema found at ${file} (${report.tables} table${report.tables === 1 ? "" : "s"})`
@@ -4666,7 +4739,7 @@ async function promptForPlan(args) {
4666
4739
  if (answer === null) endedEarly = true;
4667
4740
  else if (answer.trim()) {
4668
4741
  const typed = answer.trim();
4669
- const report = await classifySchemaCandidate(path5.resolve(cwd, typed));
4742
+ const report = await classifySchemaCandidate(path6.resolve(cwd, typed));
4670
4743
  if (report.verdict === "confirmed") {
4671
4744
  write(` ${typed}: ${report.tables} table${report.tables === 1 ? "" : "s"}`);
4672
4745
  } else if (report.verdict === "unverified") {
@@ -4728,8 +4801,8 @@ function parseGeneratorsFlag(value) {
4728
4801
  return parts;
4729
4802
  }
4730
4803
  async function runInit(args) {
4731
- const target = path5.resolve(args.cwd, "drzl.config.ts");
4732
- 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)));
4733
4806
  if (existing) {
4734
4807
  args.error(
4735
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.`
@@ -4768,8 +4841,8 @@ async function runInit(args) {
4768
4841
  generators: fromFlag ?? [DEFAULT_GENERATOR_KIND]
4769
4842
  };
4770
4843
  if (args.schemaFlag) {
4771
- const full = path5.resolve(args.cwd, args.schemaFlag);
4772
- if (!fs5.existsSync(full)) {
4844
+ const full = path6.resolve(args.cwd, args.schemaFlag);
4845
+ if (!fs6.existsSync(full)) {
4773
4846
  args.log(`--schema ${args.schemaFlag} is not there yet. Writing it anyway.`);
4774
4847
  } else if ((await classifySchemaCandidate(full)).verdict === "rejected") {
4775
4848
  args.log(`--schema ${args.schemaFlag} declares no Drizzle tables. Writing it anyway.`);
@@ -4777,7 +4850,7 @@ async function runInit(args) {
4777
4850
  }
4778
4851
  }
4779
4852
  try {
4780
- fs5.writeFileSync(target, renderInitConfig(plan), { flag: "wx" });
4853
+ fs6.writeFileSync(target, renderInitConfig(plan), { flag: "wx" });
4781
4854
  } catch (e) {
4782
4855
  if (e?.code === "EEXIST") {
4783
4856
  args.error(
@@ -4800,10 +4873,10 @@ async function runInit(args) {
4800
4873
  }
4801
4874
 
4802
4875
  // src/sponsor.ts
4803
- var import_node_fs3 = require("fs");
4804
- var import_node_path3 = __toESM(require("path"), 1);
4805
- var CACHE_DIR = import_node_path3.default.join(process.cwd(), "node_modules", ".cache", "@drzl");
4806
- 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");
4807
4880
  var DEFAULT_INTERVAL_MS = 1e3 * 60 * 15;
4808
4881
  var shownThisProcess = false;
4809
4882
  var tips = [
@@ -4827,7 +4900,7 @@ function maybeShowSponsorMessage({
4827
4900
  if (hideRequested || process.env.CI && !force || shownThisProcess && !force) return;
4828
4901
  if (!out.wantsAsides && !force) return;
4829
4902
  try {
4830
- (0, import_node_fs3.mkdirSync)(CACHE_DIR, { recursive: true });
4903
+ (0, import_node_fs4.mkdirSync)(CACHE_DIR, { recursive: true });
4831
4904
  const payload = readCache();
4832
4905
  payload.runs += 1;
4833
4906
  const now = Date.now();
@@ -4855,11 +4928,11 @@ ${green("Pro tip:")} ${tip}
4855
4928
  }
4856
4929
  }
4857
4930
  function readCache() {
4858
- if (!(0, import_node_fs3.existsSync)(CACHE_FILE)) {
4931
+ if (!(0, import_node_fs4.existsSync)(CACHE_FILE)) {
4859
4932
  return { runs: 0 };
4860
4933
  }
4861
4934
  try {
4862
- 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"));
4863
4936
  if (typeof data.runs !== "number") return { runs: 0 };
4864
4937
  return data;
4865
4938
  } catch {
@@ -4867,21 +4940,21 @@ function readCache() {
4867
4940
  }
4868
4941
  }
4869
4942
  function writeCache(payload) {
4870
- (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");
4871
4944
  }
4872
4945
 
4873
4946
  // src/version.ts
4874
- var import_node_fs4 = require("fs");
4875
- var path7 = __toESM(require("path"), 1);
4947
+ var import_node_fs5 = require("fs");
4948
+ var path8 = __toESM(require("path"), 1);
4876
4949
  var import_node_url = require("url");
4877
4950
  var PACKAGE_NAME = "@drzl/cli";
4878
4951
  function moduleDir() {
4879
- return path7.dirname((0, import_node_url.fileURLToPath)(__drzlModuleUrl));
4952
+ return path8.dirname((0, import_node_url.fileURLToPath)(__drzlModuleUrl));
4880
4953
  }
4881
4954
  function readVersionFrom(manifestPath) {
4882
4955
  let raw;
4883
4956
  try {
4884
- raw = (0, import_node_fs4.readFileSync)(manifestPath, "utf8");
4957
+ raw = (0, import_node_fs5.readFileSync)(manifestPath, "utf8");
4885
4958
  } catch (e) {
4886
4959
  throw new Error(
4887
4960
  `${PACKAGE_NAME} cannot read its own version: no manifest at ${manifestPath} (${e?.message ?? String(e)}).`
@@ -4899,7 +4972,7 @@ function readVersionFrom(manifestPath) {
4899
4972
  return manifest.version;
4900
4973
  }
4901
4974
  function readCliVersion() {
4902
- return readVersionFrom(path7.join(moduleDir(), "..", "package.json"));
4975
+ return readVersionFrom(path8.join(moduleDir(), "..", "package.json"));
4903
4976
  }
4904
4977
  var CLI_VERSION = readCliVersion();
4905
4978
 
@@ -4988,8 +5061,8 @@ withOutputFlags(
4988
5061
  const errors = res.issues.some((i) => i.level === "error");
4989
5062
  const code = unreadable ? EXIT_FAILED : errors ? EXIT_FINDINGS : EXIT_OK;
4990
5063
  if (opts.out && !opts.json) {
4991
- const fs6 = await import("fs/promises");
4992
- 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");
4993
5066
  spinner.succeed(`Analysis written to ${opts.out} in ${ms}ms`);
4994
5067
  } else {
4995
5068
  spinner.succeed(`Analyzed in ${ms}ms`);
@@ -5126,7 +5199,7 @@ async function explainSchemaSource(opts, out) {
5126
5199
  schema: source.schema,
5127
5200
  label: describeSchemaTarget(source.schema),
5128
5201
  config: cfg,
5129
- ...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)}` } : {}
5130
5203
  };
5131
5204
  }
5132
5205
  const detected = await detectSchema(process.cwd());
@@ -5236,7 +5309,11 @@ withOutputFlags(
5236
5309
  ).option(
5237
5310
  "--check",
5238
5311
  "regenerate and fail if the result differs from what is on disk, without changing it"
5239
- ).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
+ )
5240
5317
  ).action(async (opts) => {
5241
5318
  const out = outputFor(opts);
5242
5319
  const planning = !!opts.check || !!opts.dryRun;
@@ -5286,7 +5363,7 @@ withOutputFlags(
5286
5363
  const n = source.schema.length;
5287
5364
  out.note(
5288
5365
  out.errStyle.gray(
5289
- `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"})`
5290
5367
  )
5291
5368
  );
5292
5369
  }
@@ -5385,6 +5462,19 @@ withOutputFlags(
5385
5462
  files: e.files,
5386
5463
  changes: e.changes.map((c) => ({ file: displayPath(c.file), status: c.status }))
5387
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
+ }
5388
5478
  if (planning) {
5389
5479
  const wrote = await verifyNothingWasWritten(outputDirs, existing);
5390
5480
  if (wrote.length) {
@@ -5394,6 +5484,14 @@ withOutputFlags(
5394
5484
  process.exit(EXIT_FAILED);
5395
5485
  }
5396
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
+ }
5397
5495
  if (opts.check) {
5398
5496
  const drift = pendingChanges(plan);
5399
5497
  const upToDate = drift.length === 0;
@@ -5635,10 +5733,10 @@ program.command("watch").description("Watch schema and regenerate on changes").o
5635
5733
  return;
5636
5734
  }
5637
5735
  let cfg = loaded;
5638
- const abs = (p) => path8.resolve(process.cwd(), p);
5736
+ const abs = (p) => path9.resolve(process.cwd(), p);
5639
5737
  const isInside = (child, parent) => {
5640
- const rel = path8.relative(parent, child);
5641
- return !!rel && !rel.startsWith("..") && !path8.isAbsolute(rel);
5738
+ const rel = path9.relative(parent, child);
5739
+ return !!rel && !rel.startsWith("..") && !path9.isAbsolute(rel);
5642
5740
  };
5643
5741
  let source;
5644
5742
  try {
@@ -5674,7 +5772,7 @@ program.command("watch").description("Watch schema and regenerate on changes").o
5674
5772
  if (full === dir || isInside(full, dir)) return true;
5675
5773
  }
5676
5774
  if (stats?.isDirectory()) return false;
5677
- const ext = path8.extname(full);
5775
+ const ext = path9.extname(full);
5678
5776
  if (!ext) return false;
5679
5777
  return !WATCHED_EXTENSIONS.has(ext);
5680
5778
  };
@@ -5837,7 +5935,7 @@ program.command("watch").description("Watch schema and regenerate on changes").o
5837
5935
  } else {
5838
5936
  out.note(
5839
5937
  out.errStyle.gray(
5840
- "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 ")
5841
5939
  )
5842
5940
  );
5843
5941
  }