@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.js CHANGED
@@ -43,7 +43,7 @@ import {
43
43
  import { qualifiedTableName as qualifiedTableName2, SchemaAnalyzer as SchemaAnalyzer2 } from "@drzl/analyzer";
44
44
  import chokidar from "chokidar";
45
45
  import { Command } from "commander";
46
- import * as path7 from "path";
46
+ import * as path8 from "path";
47
47
 
48
48
  // src/output.ts
49
49
  import { Chalk } from "chalk";
@@ -1254,11 +1254,11 @@ function authTableWarnings(analysis) {
1254
1254
  import { Chalk as Chalk2 } from "chalk";
1255
1255
  var PLAIN = new Chalk2({ level: 0 });
1256
1256
  var HANDLED_CODES = /* @__PURE__ */ new Set(["DRZL_ANL_UNKNOWN_COLUMN"]);
1257
- function splitPath(path8) {
1258
- if (!path8) return {};
1259
- const dot = path8.lastIndexOf(".");
1260
- if (dot <= 0) return { table: path8 };
1261
- return { table: path8.slice(0, dot), column: path8.slice(dot + 1) };
1257
+ function splitPath(path9) {
1258
+ if (!path9) return {};
1259
+ const dot = path9.lastIndexOf(".");
1260
+ if (dot <= 0) return { table: path9 };
1261
+ return { table: path9.slice(0, dot), column: path9.slice(dot + 1) };
1262
1262
  }
1263
1263
  function namedColumns(parsed) {
1264
1264
  const out = [];
@@ -1810,11 +1810,11 @@ function issueTouches(issue, table) {
1810
1810
  return dot > 0 && own.includes(issue.path.slice(0, dot));
1811
1811
  }
1812
1812
  function issueColumn(issue, table) {
1813
- const path8 = issue.path ?? "";
1813
+ const path9 = issue.path ?? "";
1814
1814
  const names = namesOf(table);
1815
1815
  for (const prefix of [names.qualified, names.tsName, names.name, table.name]) {
1816
- if (path8.startsWith(`${prefix}.`)) {
1817
- const rest = path8.slice(prefix.length + 1);
1816
+ if (path9.startsWith(`${prefix}.`)) {
1817
+ const rest = path9.slice(prefix.length + 1);
1818
1818
  if (table.columns.some((c) => c.name === rest)) return rest;
1819
1819
  }
1820
1820
  }
@@ -2973,6 +2973,79 @@ function displayPath(file, cwd = process.cwd()) {
2973
2973
  return rel && !rel.startsWith("..") ? rel : file;
2974
2974
  }
2975
2975
 
2976
+ // src/manifest.ts
2977
+ import path4 from "path";
2978
+ import { promises as fs4 } from "fs";
2979
+ var MANIFEST_VERSION = 1;
2980
+ var MANIFEST_PATH = path4.join(".drzl", "manifest.json");
2981
+ function toPosix(p) {
2982
+ return p.split(path4.sep).join("/");
2983
+ }
2984
+ function manifestEntries(files, root) {
2985
+ const rel = files.map((f) => toPosix(path4.relative(root, f)));
2986
+ return [...new Set(rel)].sort();
2987
+ }
2988
+ async function readManifest(root) {
2989
+ try {
2990
+ const raw = await fs4.readFile(path4.join(root, MANIFEST_PATH), "utf8");
2991
+ const parsed = JSON.parse(raw);
2992
+ if (!parsed || typeof parsed !== "object" || parsed.version !== MANIFEST_VERSION || !Array.isArray(parsed.files) || !parsed.files.every((f) => typeof f === "string")) {
2993
+ return void 0;
2994
+ }
2995
+ return { version: MANIFEST_VERSION, files: parsed.files };
2996
+ } catch {
2997
+ return void 0;
2998
+ }
2999
+ }
3000
+ async function writeManifest(root, files) {
3001
+ const manifest = { version: MANIFEST_VERSION, files: manifestEntries(files, root) };
3002
+ const target = path4.join(root, MANIFEST_PATH);
3003
+ await fs4.mkdir(path4.dirname(target), { recursive: true });
3004
+ await fs4.writeFile(target, `${JSON.stringify(manifest, null, 2)}
3005
+ `, "utf8");
3006
+ }
3007
+ async function staleFiles(previous, writtenNow, root) {
3008
+ if (!previous) return [];
3009
+ const current = new Set(manifestEntries(writtenNow, root));
3010
+ const gone = previous.files.filter((f) => !current.has(f));
3011
+ const out = [];
3012
+ for (const file of gone) {
3013
+ let present = true;
3014
+ try {
3015
+ await fs4.access(path4.join(root, file));
3016
+ } catch {
3017
+ present = false;
3018
+ }
3019
+ out.push({ file, present });
3020
+ }
3021
+ return out;
3022
+ }
3023
+ async function pruneStale(root, stale) {
3024
+ const deleted = [];
3025
+ for (const entry of stale) {
3026
+ if (!entry.present) continue;
3027
+ const target = path4.resolve(root, entry.file);
3028
+ const inside = target === root || target.startsWith(root + path4.sep);
3029
+ if (!inside) continue;
3030
+ try {
3031
+ await fs4.rm(target);
3032
+ deleted.push(entry.file);
3033
+ } catch {
3034
+ }
3035
+ }
3036
+ return deleted;
3037
+ }
3038
+ function staleWarning(stale) {
3039
+ const present = stale.filter((s) => s.present);
3040
+ if (!present.length) return void 0;
3041
+ const list = present.map((s) => s.file).join(", ");
3042
+ 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.`;
3043
+ }
3044
+ function nextManifestFiles(writtenNow, stale, root) {
3045
+ const kept = stale.filter((s) => s.present).map((s) => path4.resolve(root, s.file));
3046
+ return manifestEntries([...writtenNow, ...kept], root);
3047
+ }
3048
+
2976
3049
  // src/unified-diff.ts
2977
3050
  var DEFAULT_DIFF_LIMITS = {
2978
3051
  maxLines: 4e3,
@@ -3215,8 +3288,8 @@ function createRebuildScheduler(options) {
3215
3288
 
3216
3289
  // src/init.ts
3217
3290
  import { SchemaAnalyzer } from "@drzl/analyzer";
3218
- import * as fs4 from "fs";
3219
- import * as path4 from "path";
3291
+ import * as fs5 from "fs";
3292
+ import * as path5 from "path";
3220
3293
  var INIT_GENERATOR_CHOICES = [
3221
3294
  { kind: "zod", packageName: "@drzl/generator-zod", label: "Zod validators" },
3222
3295
  { kind: "valibot", packageName: "@drzl/generator-valibot", label: "Valibot validators" },
@@ -3287,7 +3360,7 @@ async function detectSchema(cwd) {
3287
3360
  kitFiles = null;
3288
3361
  }
3289
3362
  if (kitFiles && kitPath) {
3290
- const rel = path4.relative(cwd, kitPath) || path4.basename(kitPath);
3363
+ const rel = path5.relative(cwd, kitPath) || path5.basename(kitPath);
3291
3364
  const report = await classifySchemaCandidate(kitFiles);
3292
3365
  if (report.verdict === "confirmed" || report.verdict === "unverified") {
3293
3366
  notes.push(
@@ -3303,10 +3376,10 @@ async function detectSchema(cwd) {
3303
3376
  }
3304
3377
  notes.push(`${rel} names schema files that declare no Drizzle tables; looking elsewhere.`);
3305
3378
  }
3306
- const present = schemaCandidates().filter((c) => fs4.existsSync(path4.resolve(cwd, c)));
3379
+ const present = schemaCandidates().filter((c) => fs5.existsSync(path5.resolve(cwd, c)));
3307
3380
  const unverified = [];
3308
3381
  for (const file of present) {
3309
- const report = await classifySchemaCandidate(path4.resolve(cwd, file));
3382
+ const report = await classifySchemaCandidate(path5.resolve(cwd, file));
3310
3383
  if (report.verdict === "confirmed") {
3311
3384
  notes.push(
3312
3385
  `Schema found at ${file} (${report.tables} table${report.tables === 1 ? "" : "s"})`
@@ -3408,7 +3481,7 @@ async function promptForPlan(args) {
3408
3481
  if (answer === null) endedEarly = true;
3409
3482
  else if (answer.trim()) {
3410
3483
  const typed = answer.trim();
3411
- const report = await classifySchemaCandidate(path4.resolve(cwd, typed));
3484
+ const report = await classifySchemaCandidate(path5.resolve(cwd, typed));
3412
3485
  if (report.verdict === "confirmed") {
3413
3486
  write(` ${typed}: ${report.tables} table${report.tables === 1 ? "" : "s"}`);
3414
3487
  } else if (report.verdict === "unverified") {
@@ -3470,8 +3543,8 @@ function parseGeneratorsFlag(value) {
3470
3543
  return parts;
3471
3544
  }
3472
3545
  async function runInit(args) {
3473
- const target = path4.resolve(args.cwd, "drzl.config.ts");
3474
- const existing = CONFIG_FILE_NAMES.find((name) => fs4.existsSync(path4.resolve(args.cwd, name)));
3546
+ const target = path5.resolve(args.cwd, "drzl.config.ts");
3547
+ const existing = CONFIG_FILE_NAMES.find((name) => fs5.existsSync(path5.resolve(args.cwd, name)));
3475
3548
  if (existing) {
3476
3549
  args.error(
3477
3550
  `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.`
@@ -3510,8 +3583,8 @@ async function runInit(args) {
3510
3583
  generators: fromFlag ?? [DEFAULT_GENERATOR_KIND]
3511
3584
  };
3512
3585
  if (args.schemaFlag) {
3513
- const full = path4.resolve(args.cwd, args.schemaFlag);
3514
- if (!fs4.existsSync(full)) {
3586
+ const full = path5.resolve(args.cwd, args.schemaFlag);
3587
+ if (!fs5.existsSync(full)) {
3515
3588
  args.log(`--schema ${args.schemaFlag} is not there yet. Writing it anyway.`);
3516
3589
  } else if ((await classifySchemaCandidate(full)).verdict === "rejected") {
3517
3590
  args.log(`--schema ${args.schemaFlag} declares no Drizzle tables. Writing it anyway.`);
@@ -3519,7 +3592,7 @@ async function runInit(args) {
3519
3592
  }
3520
3593
  }
3521
3594
  try {
3522
- fs4.writeFileSync(target, renderInitConfig(plan), { flag: "wx" });
3595
+ fs5.writeFileSync(target, renderInitConfig(plan), { flag: "wx" });
3523
3596
  } catch (e) {
3524
3597
  if (e?.code === "EEXIST") {
3525
3598
  args.error(
@@ -3543,9 +3616,9 @@ async function runInit(args) {
3543
3616
 
3544
3617
  // src/sponsor.ts
3545
3618
  import { existsSync as existsSync3, mkdirSync, readFileSync, writeFileSync as writeFileSync2 } from "fs";
3546
- import path5 from "path";
3547
- var CACHE_DIR = path5.join(process.cwd(), "node_modules", ".cache", "@drzl");
3548
- var CACHE_FILE = path5.join(CACHE_DIR, "sponsor-message.json");
3619
+ import path6 from "path";
3620
+ var CACHE_DIR = path6.join(process.cwd(), "node_modules", ".cache", "@drzl");
3621
+ var CACHE_FILE = path6.join(CACHE_DIR, "sponsor-message.json");
3549
3622
  var DEFAULT_INTERVAL_MS = 1e3 * 60 * 15;
3550
3623
  var shownThisProcess = false;
3551
3624
  var tips = [
@@ -3614,11 +3687,11 @@ function writeCache(payload) {
3614
3687
 
3615
3688
  // src/version.ts
3616
3689
  import { readFileSync as readFileSync2 } from "fs";
3617
- import * as path6 from "path";
3690
+ import * as path7 from "path";
3618
3691
  import { fileURLToPath } from "url";
3619
3692
  var PACKAGE_NAME = "@drzl/cli";
3620
3693
  function moduleDir() {
3621
- return path6.dirname(fileURLToPath(import.meta.url));
3694
+ return path7.dirname(fileURLToPath(import.meta.url));
3622
3695
  }
3623
3696
  function readVersionFrom(manifestPath) {
3624
3697
  let raw;
@@ -3641,7 +3714,7 @@ function readVersionFrom(manifestPath) {
3641
3714
  return manifest.version;
3642
3715
  }
3643
3716
  function readCliVersion() {
3644
- return readVersionFrom(path6.join(moduleDir(), "..", "package.json"));
3717
+ return readVersionFrom(path7.join(moduleDir(), "..", "package.json"));
3645
3718
  }
3646
3719
  var CLI_VERSION = readCliVersion();
3647
3720
 
@@ -3730,8 +3803,8 @@ withOutputFlags(
3730
3803
  const errors = res.issues.some((i) => i.level === "error");
3731
3804
  const code = unreadable ? EXIT_FAILED : errors ? EXIT_FINDINGS : EXIT_OK;
3732
3805
  if (opts.out && !opts.json) {
3733
- const fs5 = await import("fs/promises");
3734
- await fs5.writeFile(opts.out, JSON.stringify(res, null, 2), "utf8");
3806
+ const fs6 = await import("fs/promises");
3807
+ await fs6.writeFile(opts.out, JSON.stringify(res, null, 2), "utf8");
3735
3808
  spinner.succeed(`Analysis written to ${opts.out} in ${ms}ms`);
3736
3809
  } else {
3737
3810
  spinner.succeed(`Analyzed in ${ms}ms`);
@@ -3868,7 +3941,7 @@ async function explainSchemaSource(opts, out) {
3868
3941
  schema: source.schema,
3869
3942
  label: describeSchemaTarget(source.schema),
3870
3943
  config: cfg,
3871
- ...source.source === "drizzle-kit" && source.drizzleKitConfigPath ? { note: `Schema from ${path7.relative(process.cwd(), source.drizzleKitConfigPath)}` } : {}
3944
+ ...source.source === "drizzle-kit" && source.drizzleKitConfigPath ? { note: `Schema from ${path8.relative(process.cwd(), source.drizzleKitConfigPath)}` } : {}
3872
3945
  };
3873
3946
  }
3874
3947
  const detected = await detectSchema(process.cwd());
@@ -3978,7 +4051,11 @@ withOutputFlags(
3978
4051
  ).option(
3979
4052
  "--check",
3980
4053
  "regenerate and fail if the result differs from what is on disk, without changing it"
3981
- ).option("--dry-run", "report what would be written, and write nothing", false)
4054
+ ).option("--dry-run", "report what would be written, and write nothing", false).option(
4055
+ "--prune",
4056
+ "delete files a previous run wrote that this one did not, and nothing else",
4057
+ false
4058
+ )
3982
4059
  ).action(async (opts) => {
3983
4060
  const out = outputFor(opts);
3984
4061
  const planning = !!opts.check || !!opts.dryRun;
@@ -4028,7 +4105,7 @@ withOutputFlags(
4028
4105
  const n = source.schema.length;
4029
4106
  out.note(
4030
4107
  out.errStyle.gray(
4031
- `Schema from ${path7.relative(process.cwd(), source.drizzleKitConfigPath)} (${n} file${n === 1 ? "" : "s"})`
4108
+ `Schema from ${path8.relative(process.cwd(), source.drizzleKitConfigPath)} (${n} file${n === 1 ? "" : "s"})`
4032
4109
  )
4033
4110
  );
4034
4111
  }
@@ -4127,6 +4204,19 @@ withOutputFlags(
4127
4204
  files: e.files,
4128
4205
  changes: e.changes.map((c) => ({ file: displayPath(c.file), status: c.status }))
4129
4206
  }));
4207
+ const previousManifest = await readManifest(process.cwd());
4208
+ const writtenNow = plan.files.map((f) => f.file);
4209
+ const stale = await staleFiles(previousManifest, writtenNow, process.cwd());
4210
+ let stillStale = stale;
4211
+ if (opts.prune && !planning) {
4212
+ const removed = await pruneStale(process.cwd(), stale);
4213
+ for (const file of removed) out.warn(`drzl generate: removed stale ${file}`);
4214
+ const gone = new Set(removed);
4215
+ stillStale = stale.filter((s) => !gone.has(s.file));
4216
+ } else {
4217
+ const warning = staleWarning(stale);
4218
+ if (warning) warn(warning);
4219
+ }
4130
4220
  if (planning) {
4131
4221
  const wrote = await verifyNothingWasWritten(outputDirs, existing);
4132
4222
  if (wrote.length) {
@@ -4136,6 +4226,14 @@ withOutputFlags(
4136
4226
  process.exit(EXIT_FAILED);
4137
4227
  }
4138
4228
  }
4229
+ if (!planning) {
4230
+ await writeManifest(
4231
+ process.cwd(),
4232
+ nextManifestFiles(writtenNow, stillStale, process.cwd()).map(
4233
+ (f) => path8.resolve(process.cwd(), f)
4234
+ )
4235
+ );
4236
+ }
4139
4237
  if (opts.check) {
4140
4238
  const drift = pendingChanges(plan);
4141
4239
  const upToDate = drift.length === 0;
@@ -4377,10 +4475,10 @@ program.command("watch").description("Watch schema and regenerate on changes").o
4377
4475
  return;
4378
4476
  }
4379
4477
  let cfg = loaded;
4380
- const abs = (p) => path7.resolve(process.cwd(), p);
4478
+ const abs = (p) => path8.resolve(process.cwd(), p);
4381
4479
  const isInside = (child, parent) => {
4382
- const rel = path7.relative(parent, child);
4383
- return !!rel && !rel.startsWith("..") && !path7.isAbsolute(rel);
4480
+ const rel = path8.relative(parent, child);
4481
+ return !!rel && !rel.startsWith("..") && !path8.isAbsolute(rel);
4384
4482
  };
4385
4483
  let source;
4386
4484
  try {
@@ -4416,7 +4514,7 @@ program.command("watch").description("Watch schema and regenerate on changes").o
4416
4514
  if (full === dir || isInside(full, dir)) return true;
4417
4515
  }
4418
4516
  if (stats?.isDirectory()) return false;
4419
- const ext = path7.extname(full);
4517
+ const ext = path8.extname(full);
4420
4518
  if (!ext) return false;
4421
4519
  return !WATCHED_EXTENSIONS.has(ext);
4422
4520
  };
@@ -4579,7 +4677,7 @@ program.command("watch").description("Watch schema and regenerate on changes").o
4579
4677
  } else {
4580
4678
  out.note(
4581
4679
  out.errStyle.gray(
4582
- "Watching:\n " + Array.from(currentTargets).map((p) => path7.relative(process.cwd(), p)).join("\n ")
4680
+ "Watching:\n " + Array.from(currentTargets).map((p) => path8.relative(process.cwd(), p)).join("\n ")
4583
4681
  )
4584
4682
  );
4585
4683
  }