@drzl/cli 4.1.0 → 4.3.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.
@@ -45,7 +45,7 @@ var AffixSchema = z.object({
45
45
  }).strict();
46
46
  var ImportExtensionSchema = z.enum(IMPORT_EXTENSIONS);
47
47
  var GeneratorSchema = z.object({
48
- kind: z.enum(["orpc", "service", "zod", "valibot", "arktype"]),
48
+ kind: z.enum(["orpc", "service", "zod", "valibot", "arktype", "typebox"]),
49
49
  /**
50
50
  * Overrides the top-level `importExtension` for this generator alone, for a project whose
51
51
  * generated directories are compiled by different tsconfigs.
@@ -53,6 +53,18 @@ var GeneratorSchema = z.object({
53
53
  importExtension: ImportExtensionSchema.optional(),
54
54
  template: z.string().optional(),
55
55
  includeRelations: z.boolean().optional(),
56
+ /**
57
+ * Type `json` and `jsonb` columns from the schema rather than leaving them wide.
58
+ *
59
+ * `.$type<T>()` is a compile-time cast, so no runtime-derived validator can see it and
60
+ * `drizzle-orm/zod` types every json column as its generic `Json`. A generator can reference
61
+ * `typeof <table>.$inferSelect['<column>']` instead, which is the declared type resolved by
62
+ * TypeScript itself, so generics, unions and imported interfaces all work.
63
+ *
64
+ * Off by default because it makes the generated file import your schema module, as a
65
+ * type-only import that disappears at build time.
66
+ */
67
+ typedJson: z.boolean().optional(),
56
68
  naming: NamingSchema.optional(),
57
69
  outputHeader: z.object({
58
70
  enabled: z.boolean().default(true).optional(),
@@ -260,6 +272,7 @@ function computeGeneratorOutputDirs(cfg, cwd = process.cwd()) {
260
272
  if (g.kind === "zod") dirs.add(abs(g.path ?? "src/validators/zod"));
261
273
  if (g.kind === "valibot") dirs.add(abs(g.path ?? "src/validators/valibot"));
262
274
  if (g.kind === "arktype") dirs.add(abs(g.path ?? "src/validators/arktype"));
275
+ if (g.kind === "typebox") dirs.add(abs(g.path ?? "src/validators/typebox"));
263
276
  }
264
277
  return [...dirs];
265
278
  }
@@ -327,4 +340,4 @@ export {
327
340
  filterTables,
328
341
  computeWatchTargets
329
342
  };
330
- //# sourceMappingURL=chunk-PP33LGZD.js.map
343
+ //# sourceMappingURL=chunk-CHVAKQVU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts"],"sourcesContent":["import type { AffixOptions } from '@drzl/validation-core';\nimport {\n AFFIX_PROBE_TABLE,\n DEFAULT_IMPORT_EXTENSION,\n IMPORT_EXTENSIONS,\n NAME_MODES,\n resolveAffix,\n schemaName,\n validateAffix,\n} from '@drzl/validation-core';\nimport * as fs from 'node:fs';\nimport { createRequire } from 'node:module';\nimport * as path from 'node:path';\nimport { z } from 'zod';\n\nexport const NamingSchema = z\n .object({\n routerSuffix: z.string().default('Router'),\n procedureCase: z.enum(['camel', 'kebab', 'snake']).default('camel'),\n })\n .partial();\n\n/** One affix for every mode, or a per-mode map. Keys match drzl's internal mode names. */\nconst AffixValueSchema = z.union(\n [\n z.string(),\n z\n .object({\n insert: z.string().optional(),\n update: z.string().optional(),\n select: z.string().optional(),\n })\n .strict(),\n ],\n {\n error:\n 'Expected a string to use for every mode, or an object with any of the keys \"insert\", ' +\n '\"update\" and \"select\". Those keys are lowercase, matching the mode names drzl uses ' +\n 'everywhere else.',\n }\n);\n\nconst AffixPartSchema = z\n .object({\n prefix: AffixValueSchema.optional(),\n suffix: AffixValueSchema.optional(),\n })\n .strict();\n\nexport const AffixSchema = z\n .object({\n /**\n * `preserve` (default) keeps today's output: the Drizzle export name goes into the\n * identifier verbatim, so `export const users` yields `InsertusersSchema`. `pascal`\n * upper-camels it first, yielding `InsertUsersSchema`.\n */\n tableCase: z.enum(['preserve', 'pascal']).optional(),\n schema: AffixPartSchema.optional(),\n type: AffixPartSchema.optional(),\n })\n .strict();\n\n/**\n * How every relative specifier drzl invents spells its extension.\n *\n * The generated files land in the consumer's own source tree, so the consumer's\n * `moduleResolution` decides which forms resolve. `js` is the only one that resolves under\n * all of `bundler`, `node10`, `node16` and `nodenext` with no compiler flag, so it is the\n * default. See the `ImportExtension` docs in `@drzl/validation-core` for the measured grid.\n */\nexport const ImportExtensionSchema = z.enum(IMPORT_EXTENSIONS);\n\nexport const GeneratorSchema = z.object({\n kind: z.enum(['orpc', 'service', 'zod', 'valibot', 'arktype', 'typebox']),\n /**\n * Overrides the top-level `importExtension` for this generator alone, for a project whose\n * generated directories are compiled by different tsconfigs.\n */\n importExtension: ImportExtensionSchema.optional(),\n template: z.string().optional(),\n includeRelations: z.boolean().optional(),\n /**\n * Type `json` and `jsonb` columns from the schema rather than leaving them wide.\n *\n * `.$type<T>()` is a compile-time cast, so no runtime-derived validator can see it and\n * `drizzle-orm/zod` types every json column as its generic `Json`. A generator can reference\n * `typeof <table>.$inferSelect['<column>']` instead, which is the declared type resolved by\n * TypeScript itself, so generics, unions and imported interfaces all work.\n *\n * Off by default because it makes the generated file import your schema module, as a\n * type-only import that disappears at build time.\n */\n typedJson: z.boolean().optional(),\n naming: NamingSchema.optional(),\n outputHeader: z\n .object({\n enabled: z.boolean().default(true).optional(),\n text: z.string().optional(),\n })\n .optional(),\n format: z\n .object({\n enabled: z.boolean().default(true).optional(),\n engine: z.enum(['auto', 'prettier', 'biome']).default('auto').optional(),\n configPath: z.string().optional(),\n })\n .optional(),\n // service generator specific options\n path: z.string().optional(),\n dataAccess: z.enum(['stub', 'drizzle']).default('stub').optional(),\n dbImportPath: z.string().optional(),\n schemaImportPath: z.string().optional(),\n // zod/valibot/arktype generator specific options\n schemaSuffix: z.string().optional(),\n fileSuffix: z.string().optional(),\n /**\n * Prefixes, suffixes and table casing for generated identifiers (zod/valibot/arktype).\n * Omitting it reproduces the output of every previous release exactly.\n */\n affix: AffixSchema.optional(),\n // orpc validation sharing\n validation: z\n .object({\n useShared: z.boolean().default(false).optional(),\n library: z.enum(['zod', 'valibot', 'arktype']).default('zod').optional(),\n importPath: z.string().optional(),\n schemaSuffix: z.string().optional(),\n /**\n * How the validation generator named its exports. Usually left unset: the CLI copies\n * it from the sibling generator whose `kind` matches `library`.\n */\n affix: AffixSchema.optional(),\n })\n .optional(),\n // template options\n templateOptions: z.record(z.string(), z.any()).optional(),\n});\n\nexport const AnalyzerSchema = z.object({\n includeRelations: z.boolean().default(true),\n validateConstraints: z.boolean().default(true),\n includeHeuristicRelations: z.boolean().default(false),\n});\n\nexport const ConfigSchema = z\n .object({\n schema: z.string(),\n outDir: z.string().default('src/api'),\n /**\n * Which tables to generate for, matched against the database table name.\n *\n * There was no way to say this, and every generator loops over every table it finds, so\n * DRZL emitted unauthenticated CRUD over whatever shared the schema file. That is noise for\n * a migrations table and a genuine leak for an auth one: Better Auth puts `user`, `session`,\n * `account` and `verification` alongside your own tables, and `account` holds\n * `accessToken`, `refreshToken`, `idToken` and `password`.\n *\n * Deliberately name-based and explicit rather than detecting any particular library. Auth\n * table names are all renameable, so a built-in list would miss renamed tables and, worse,\n * silently skip an ordinary table that happened to be called `user`, which is usually the\n * application's main entity.\n *\n * `exclude` wins over `include`. Patterns support `*`, matching within a name.\n */\n include: z.array(z.string()).optional(),\n exclude: z.array(z.string()).optional(),\n /**\n * How every relative specifier drzl invents spells its extension, for every generator.\n * A generator may override it. Defaults to `js`, which is the only form that resolves\n * under every `moduleResolution` without a compiler flag.\n */\n importExtension: ImportExtensionSchema.default(DEFAULT_IMPORT_EXTENSION),\n analyzer: AnalyzerSchema.default({\n includeRelations: true,\n validateConstraints: true,\n includeHeuristicRelations: false,\n }),\n generators: z\n .array(GeneratorSchema)\n .min(1)\n .default([{ kind: 'orpc' } as any]),\n })\n // Reject an affix before anything is written, rather than emitting a file that cannot\n // compile. Only `affix` is inspected; the legacy flat `schemaSuffix` is left alone so\n // configs that parse today keep parsing.\n .superRefine((cfg, ctx) => {\n cfg.generators.forEach((g, i) => {\n const report = (base: (string | number)[], affix?: AffixOptions, schemaSuffix?: string) => {\n for (const issue of validateAffix(affix, schemaSuffix)) {\n ctx.addIssue({\n code: 'custom',\n path: ['generators', i, ...base, ...issue.path],\n message: issue.message,\n });\n }\n };\n report(['affix'], g.affix as AffixOptions | undefined, g.schemaSuffix);\n report(\n ['validation', 'affix'],\n g.validation?.affix as AffixOptions | undefined,\n g.validation?.schemaSuffix\n );\n });\n });\n\n// ✨ Separate input vs output types\nexport type DrzlConfigInput = z.input<typeof ConfigSchema>;\nexport type DrzlConfig = z.output<typeof ConfigSchema>;\n\nexport function defineConfig<T extends DrzlConfigInput>(cfg: T): T {\n return cfg;\n}\n\ntype GeneratorConfig = DrzlConfig['generators'][number];\n\nfunction sharedSchemaNames(opts: { affix?: AffixOptions; schemaSuffix?: string }): string[] {\n const resolved = resolveAffix(opts);\n return NAME_MODES.map((mode) => schemaName(mode, AFFIX_PROBE_TABLE, resolved));\n}\n\n/**\n * Fill in cross-generator defaults and refuse configs whose generators would disagree.\n *\n * An oRPC router that imports shared schemas has to spell the exact names the validation\n * generator exported. Both sides used to be configured independently, so they could silently\n * drift into a router that does not compile. When an oRPC generator uses shared validation\n * and exactly one sibling generator produces that library, its `affix` is copied across.\n *\n * Deliberately conservative about the pre-existing flat `schemaSuffix`: a disagreement there\n * is only reported, never repaired, because repairing it would change the bytes an existing\n * config emits.\n *\n * `importExtension` is pushed down here too. A consumer compiles the whole generated tree\n * with one tsconfig, so the setting that has to hold is the same for every generator, and\n * every call site downstream can then read it off the generator without knowing about the\n * top-level default.\n */\nexport function resolveConfig(cfg: DrzlConfig): { config: DrzlConfig; warnings: string[] } {\n const warnings: string[] = [];\n const generators: GeneratorConfig[] = cfg.generators.map((g) => ({\n ...g,\n importExtension: g.importExtension ?? cfg.importExtension,\n }));\n\n for (const g of generators) {\n if (g.kind !== 'orpc') continue;\n const v = g.validation;\n if (!v?.useShared) continue;\n\n const library = v.library ?? 'zod';\n const siblings = generators.filter((s) => s.kind === library);\n // Zero siblings means the user points at a barrel drzl does not generate; more than one\n // means there is no single source of truth. Either way, leave the config alone.\n if (siblings.length !== 1) continue;\n const sibling = siblings[0];\n\n const theirs = sharedSchemaNames({\n affix: sibling.affix as AffixOptions | undefined,\n schemaSuffix: sibling.schemaSuffix,\n });\n\n if (!v.affix) {\n if (sibling.affix) {\n // Bake the sibling's fully resolved naming in, so its own schemaSuffix fallback\n // travels with it and cannot be re-interpreted on the oRPC side.\n g.validation = {\n ...v,\n affix: resolveAffix({\n affix: sibling.affix as AffixOptions,\n schemaSuffix: sibling.schemaSuffix,\n }),\n };\n continue;\n }\n const mine = sharedSchemaNames({ schemaSuffix: v.schemaSuffix });\n if (mine.join(',') !== theirs.join(',')) {\n warnings.push(\n `drzl config: the \"orpc\" generator's validation.schemaSuffix ` +\n `(${JSON.stringify(v.schemaSuffix ?? 'Schema')}) does not match the \"${library}\" ` +\n `generator's schemaSuffix (${JSON.stringify(sibling.schemaSuffix ?? 'Schema')}). ` +\n `The router will import ${mine.join(', ')} but the \"${library}\" generator exports ` +\n `${theirs.join(', ')}, so the generated router will not compile. Set both to the ` +\n `same value, or move to \"affix\", which is inherited automatically.`\n );\n }\n continue;\n }\n\n const mine = sharedSchemaNames({\n affix: v.affix as AffixOptions,\n schemaSuffix: v.schemaSuffix,\n });\n if (mine.join(',') !== theirs.join(',')) {\n throw new Error(\n `drzl config: the \"orpc\" generator imports shared ${library} schemas, but its ` +\n `validation.affix disagrees with the \"${library}\" generator's own naming. The router ` +\n `would import ${mine.join(', ')} while the \"${library}\" generator exports ` +\n `${theirs.join(', ')}. Make them match, or drop validation.affix and let it be ` +\n `inherited from the \"${library}\" generator.`\n );\n }\n }\n\n return { config: { ...cfg, generators }, warnings };\n}\n\n/**\n * Parse, then resolve cross-generator defaults. Both `generate` and `watch` go through\n * loadConfig, so putting the resolution here is what keeps the two duplicated generator\n * dispatch blocks in cli.ts from needing the logic twice.\n */\nfunction finalize(raw: unknown): DrzlConfig {\n const { config, warnings } = resolveConfig(ConfigSchema.parse(raw));\n for (const w of warnings) console.warn(w);\n return config;\n}\n\nexport async function loadConfig(customPath?: string): Promise<DrzlConfig | null> {\n const fsp = await import('node:fs/promises');\n\n const candidates = customPath\n ? [customPath]\n : [\n 'drzl.config.ts',\n 'drzl.config.mjs',\n 'drzl.config.js',\n 'drzl.config.cjs',\n 'drzl.config.json',\n ];\n\n for (const c of candidates) {\n const p = path.resolve(process.cwd(), c);\n try {\n await fsp.access(p);\n } catch {\n continue;\n }\n\n const ext = path.extname(p).toLowerCase();\n\n // JSON: read directly\n if (ext === '.json') {\n const raw = JSON.parse(await fsp.readFile(p, 'utf8'));\n return finalize(raw);\n }\n\n // Everything else (TS/JS/MJS/CJS) -> Jiti with cache-busting\n const { createJiti } = await import('jiti');\n const stat = await fsp.stat(p);\n\n // Passing __filename is safe in CJS; fallback to cwd if not defined.\n const base =\n typeof __filename !== 'undefined' ? __filename : path.join(process.cwd(), 'index.js');\n\n const jiti = createJiti(base, {\n moduleCache: false, // re-evaluate each time\n fsCache: true, // keep transform cache\n cacheVersion: String(stat.mtimeMs), // bump on edit\n interopDefault: true,\n tryNative: false, // <-- prevent native import of .ts\n // debug: true,\n }) as any;\n\n const mod = await jiti.import(p);\n const raw = mod?.default ?? mod;\n return finalize(raw);\n }\n\n return null;\n}\n\n/** Absolute output dirs for all generators (to ignore in watcher). */\nexport function computeGeneratorOutputDirs(cfg: DrzlConfig, cwd = process.cwd()): string[] {\n const abs = (p: string) => path.resolve(cwd, p);\n const dirs = new Set<string>();\n dirs.add(abs(cfg.outDir)); // orpc\n for (const g of cfg.generators) {\n if (g.kind === 'service') dirs.add(abs(g.path ?? 'src/services'));\n if (g.kind === 'zod') dirs.add(abs(g.path ?? 'src/validators/zod'));\n if (g.kind === 'valibot') dirs.add(abs(g.path ?? 'src/validators/valibot'));\n if (g.kind === 'arktype') dirs.add(abs(g.path ?? 'src/validators/arktype'));\n if (g.kind === 'typebox') dirs.add(abs(g.path ?? 'src/validators/typebox'));\n }\n return [...dirs];\n}\n\n/** Resolve custom template directories (local path or installed package). */\nexport function resolveTemplateDirsSync(cfg: DrzlConfig, cwd = process.cwd()): string[] {\n const results: string[] = [];\n const req = createRequire(\n typeof __filename !== 'undefined' ? __filename : path.join(process.cwd(), 'index.js')\n );\n\n for (const g of cfg.generators) {\n const t = g.template;\n if (!t || t === 'standard' || t === 'minimal') continue;\n\n // Try package resolution relative to cwd\n let pkgDir: string | null = null;\n try {\n const pkg = req.resolve(`${t}/package.json`, { paths: [cwd] as any });\n pkgDir = path.dirname(pkg);\n } catch {}\n\n if (pkgDir) {\n results.push(pkgDir);\n continue;\n }\n\n // Local path-like template\n if (/[./\\\\]/.test(t)) {\n const abs = path.resolve(cwd, t);\n if (fs.existsSync(abs)) results.push(abs);\n }\n }\n\n return Array.from(new Set(results));\n}\n\n/** Build watch targets (exclude output dirs; watcher will ignore those). */\n/**\n * Narrow an analysis's tables to the ones the config asked for.\n *\n * Matching is on the database table name, anchored, with `*` as the only metacharacter. Anchored\n * matters: `user` must not also drop `users`, and a substring match would. `exclude` is applied\n * after `include`, so the safer direction wins when both name the same table.\n */\nexport function filterTables<T extends { name: string }>(\n tables: T[],\n opts: { include?: string[]; exclude?: string[] }\n): T[] {\n const toRegExp = (pattern: string) =>\n new RegExp(\n '^' +\n pattern\n .split('*')\n .map((part) => part.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'))\n .join('.*') +\n '$'\n );\n\n const matches = (patterns: string[], name: string) =>\n patterns.some((p) => toRegExp(p).test(name));\n\n let out = tables;\n if (opts.include?.length) out = out.filter((t) => matches(opts.include!, t.name));\n if (opts.exclude?.length) out = out.filter((t) => !matches(opts.exclude!, t.name));\n return out;\n}\n\nexport function computeWatchTargets(cfg: DrzlConfig, cwd = process.cwd()): string[] {\n const abs = (p: string) => path.resolve(cwd, p);\n const schemaAbs = abs(cfg.schema);\n // The schema's directory, not a glob under it. Chokidar removed glob support in v4 and treats\n // `<dir>/**/*.{ts,tsx,js}` as a literal path, so it watched a directory named `**` that does\n // not exist: no event ever fired and `drzl watch` did its initial build and then sat inert.\n // A directory is watched recursively by chokidar itself, and the extension filtering that the\n // glob was doing now happens on the event instead.\n const targets = new Set<string>([\n path.dirname(schemaAbs),\n abs('drzl.config.ts'),\n abs('drzl.config.js'),\n abs('drzl.config.mjs'),\n abs('drzl.config.cjs'),\n ]);\n for (const t of resolveTemplateDirsSync(cfg, cwd)) targets.add(t);\n return [...targets];\n}\n"],"mappings":"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","names":["mine","raw"]}
package/dist/cli.cjs CHANGED
@@ -30,7 +30,7 @@ var import_chalk2 = __toESM(require("chalk"), 1);
30
30
  var import_chokidar = __toESM(require("chokidar"), 1);
31
31
  var import_cli_progress = __toESM(require("cli-progress"), 1);
32
32
  var import_commander = require("commander");
33
- var path3 = __toESM(require("path"), 1);
33
+ var path4 = __toESM(require("path"), 1);
34
34
  var import_ora = __toESM(require("ora"), 1);
35
35
 
36
36
  // src/config.ts
@@ -72,7 +72,7 @@ var AffixSchema = import_zod.z.object({
72
72
  }).strict();
73
73
  var ImportExtensionSchema = import_zod.z.enum(import_validation_core.IMPORT_EXTENSIONS);
74
74
  var GeneratorSchema = import_zod.z.object({
75
- kind: import_zod.z.enum(["orpc", "service", "zod", "valibot", "arktype"]),
75
+ kind: import_zod.z.enum(["orpc", "service", "zod", "valibot", "arktype", "typebox"]),
76
76
  /**
77
77
  * Overrides the top-level `importExtension` for this generator alone, for a project whose
78
78
  * generated directories are compiled by different tsconfigs.
@@ -80,6 +80,18 @@ var GeneratorSchema = import_zod.z.object({
80
80
  importExtension: ImportExtensionSchema.optional(),
81
81
  template: import_zod.z.string().optional(),
82
82
  includeRelations: import_zod.z.boolean().optional(),
83
+ /**
84
+ * Type `json` and `jsonb` columns from the schema rather than leaving them wide.
85
+ *
86
+ * `.$type<T>()` is a compile-time cast, so no runtime-derived validator can see it and
87
+ * `drizzle-orm/zod` types every json column as its generic `Json`. A generator can reference
88
+ * `typeof <table>.$inferSelect['<column>']` instead, which is the declared type resolved by
89
+ * TypeScript itself, so generics, unions and imported interfaces all work.
90
+ *
91
+ * Off by default because it makes the generated file import your schema module, as a
92
+ * type-only import that disappears at build time.
93
+ */
94
+ typedJson: import_zod.z.boolean().optional(),
83
95
  naming: NamingSchema.optional(),
84
96
  outputHeader: import_zod.z.object({
85
97
  enabled: import_zod.z.boolean().default(true).optional(),
@@ -284,6 +296,7 @@ function computeGeneratorOutputDirs(cfg, cwd = process.cwd()) {
284
296
  if (g.kind === "zod") dirs.add(abs(g.path ?? "src/validators/zod"));
285
297
  if (g.kind === "valibot") dirs.add(abs(g.path ?? "src/validators/valibot"));
286
298
  if (g.kind === "arktype") dirs.add(abs(g.path ?? "src/validators/arktype"));
299
+ if (g.kind === "typebox") dirs.add(abs(g.path ?? "src/validators/typebox"));
287
300
  }
288
301
  return [...dirs];
289
302
  }
@@ -336,12 +349,63 @@ function computeWatchTargets(cfg, cwd = process.cwd()) {
336
349
  return [...targets];
337
350
  }
338
351
 
339
- // src/sponsor.ts
340
- var import_chalk = __toESM(require("chalk"), 1);
352
+ // src/drift.ts
341
353
  var import_node_fs = require("fs");
342
354
  var import_node_path = __toESM(require("path"), 1);
343
- var CACHE_DIR = import_node_path.default.join(process.cwd(), "node_modules", ".cache", "@drzl");
344
- var CACHE_FILE = import_node_path.default.join(CACHE_DIR, "sponsor-message.json");
355
+ async function snapshotDir(dir) {
356
+ const out = /* @__PURE__ */ new Map();
357
+ async function walk(current) {
358
+ let entries;
359
+ try {
360
+ entries = await import_node_fs.promises.readdir(current, { withFileTypes: true });
361
+ } catch {
362
+ return;
363
+ }
364
+ for (const e of entries) {
365
+ const full = import_node_path.default.join(current, e.name);
366
+ if (e.isDirectory()) await walk(full);
367
+ else out.set(import_node_path.default.relative(dir, full), await import_node_fs.promises.readFile(full, "utf8"));
368
+ }
369
+ }
370
+ await walk(dir);
371
+ return out;
372
+ }
373
+ async function snapshotAll(dirs) {
374
+ const all = /* @__PURE__ */ new Map();
375
+ for (const dir of dirs) {
376
+ for (const [rel, content] of await snapshotDir(dir)) {
377
+ all.set(import_node_path.default.join(dir, rel), content);
378
+ }
379
+ }
380
+ return all;
381
+ }
382
+ function diffSnapshots(before, after) {
383
+ const out = [];
384
+ for (const [file, content] of after) {
385
+ if (!before.has(file)) out.push({ file, status: "added" });
386
+ else if (before.get(file) !== content) out.push({ file, status: "changed" });
387
+ }
388
+ for (const file of before.keys()) {
389
+ if (!after.has(file)) out.push({ file, status: "removed" });
390
+ }
391
+ return out.sort((a, b) => a.file.localeCompare(b.file));
392
+ }
393
+ async function restoreSnapshot(before, after) {
394
+ for (const [file, content] of before) {
395
+ await import_node_fs.promises.mkdir(import_node_path.default.dirname(file), { recursive: true });
396
+ await import_node_fs.promises.writeFile(file, content, "utf8");
397
+ }
398
+ for (const file of after.keys()) {
399
+ if (!before.has(file)) await import_node_fs.promises.rm(file, { force: true });
400
+ }
401
+ }
402
+
403
+ // src/sponsor.ts
404
+ var import_chalk = __toESM(require("chalk"), 1);
405
+ var import_node_fs2 = require("fs");
406
+ var import_node_path2 = __toESM(require("path"), 1);
407
+ var CACHE_DIR = import_node_path2.default.join(process.cwd(), "node_modules", ".cache", "@drzl");
408
+ var CACHE_FILE = import_node_path2.default.join(CACHE_DIR, "sponsor-message.json");
345
409
  var DEFAULT_INTERVAL_MS = 1e3 * 60 * 15;
346
410
  var shownThisProcess = false;
347
411
  var tips = [
@@ -362,7 +426,7 @@ function maybeShowSponsorMessage({
362
426
  const hideRequested = hideViaEnv === "1" || hideViaEnv === "true";
363
427
  if (hideRequested || process.env.CI && !force || shownThisProcess && !force) return;
364
428
  try {
365
- (0, import_node_fs.mkdirSync)(CACHE_DIR, { recursive: true });
429
+ (0, import_node_fs2.mkdirSync)(CACHE_DIR, { recursive: true });
366
430
  const payload = readCache();
367
431
  payload.runs += 1;
368
432
  const now = Date.now();
@@ -389,11 +453,11 @@ ${green("Pro tip:")} ${tip}
389
453
  }
390
454
  }
391
455
  function readCache() {
392
- if (!(0, import_node_fs.existsSync)(CACHE_FILE)) {
456
+ if (!(0, import_node_fs2.existsSync)(CACHE_FILE)) {
393
457
  return { runs: 0 };
394
458
  }
395
459
  try {
396
- const data = JSON.parse((0, import_node_fs.readFileSync)(CACHE_FILE, "utf8"));
460
+ const data = JSON.parse((0, import_node_fs2.readFileSync)(CACHE_FILE, "utf8"));
397
461
  if (typeof data.runs !== "number") return { runs: 0 };
398
462
  return data;
399
463
  } catch {
@@ -401,7 +465,7 @@ function readCache() {
401
465
  }
402
466
  }
403
467
  function writeCache(payload) {
404
- (0, import_node_fs.writeFileSync)(CACHE_FILE, JSON.stringify(payload, null, 2), "utf8");
468
+ (0, import_node_fs2.writeFileSync)(CACHE_FILE, JSON.stringify(payload, null, 2), "utf8");
405
469
  }
406
470
 
407
471
  // src/cli.ts
@@ -428,8 +492,8 @@ program.command("analyze").argument("<schema>", "path to drizzle schema (TS)").o
428
492
  if (opts.json) {
429
493
  console.log(json);
430
494
  } else if (opts.out) {
431
- const fs2 = await import("fs/promises");
432
- await fs2.writeFile(opts.out, json, "utf8");
495
+ const fs3 = await import("fs/promises");
496
+ await fs3.writeFile(opts.out, json, "utf8");
433
497
  spinner?.succeed(import_chalk2.default.green(`Analysis written to ${opts.out} in ${ms}ms`));
434
498
  } else {
435
499
  spinner?.succeed(import_chalk2.default.green(`Analyzed in ${ms}ms`));
@@ -449,7 +513,10 @@ program.command("analyze").argument("<schema>", "path to drizzle schema (TS)").o
449
513
  process.exit(1);
450
514
  }
451
515
  });
452
- program.command("generate").description("Run configured generators (drzl.config.*)").option("-c, --config <path>", "path to drzl.config").action(async (opts) => {
516
+ program.command("generate").description("Run configured generators (drzl.config.*)").option("-c, --config <path>", "path to drzl.config").option(
517
+ "--check",
518
+ "regenerate and fail if the result differs from what is on disk, without changing it"
519
+ ).action(async (opts) => {
453
520
  try {
454
521
  const cfg = await loadConfig(opts.config);
455
522
  if (!cfg) {
@@ -469,6 +536,8 @@ program.command("generate").description("Run configured generators (drzl.config.
469
536
  });
470
537
  analysis.tables = filterTables(analysis.tables, cfg);
471
538
  spinner.succeed(`Analysis complete in ${Date.now() - t0}ms`);
539
+ const driftDirs = computeGeneratorOutputDirs(cfg);
540
+ const driftBefore = opts.check ? await snapshotAll(driftDirs) : null;
472
541
  const progress = new import_cli_progress.default.SingleBar(
473
542
  { hideCursor: true },
474
543
  import_cli_progress.default.Presets.shades_classic
@@ -533,7 +602,11 @@ program.command("generate").description("Run configured generators (drzl.config.
533
602
  schemaSuffix: g.schemaSuffix,
534
603
  fileSuffix: g.fileSuffix,
535
604
  importExtension: g.importExtension,
536
- affix: g.affix
605
+ affix: g.affix,
606
+ // Needed by typedJson, which imports the schema back to reference the type
607
+ // Drizzle inferred for a json column.
608
+ schemaPath: cfg.schema,
609
+ typedJson: g.typedJson
537
610
  });
538
611
  progress.stop();
539
612
  (0, import_ora.default)().succeed(import_chalk2.default.green(`Generated (zod): ${files.length} files`));
@@ -599,8 +672,55 @@ program.command("generate").description("Run configured generators (drzl.config.
599
672
  console.error(import_chalk2.default.gray("Error details:"), e?.message ?? e);
600
673
  process.exit(1);
601
674
  }
675
+ } else if (g.kind === "typebox") {
676
+ try {
677
+ const { TypeBoxGenerator } = await import("@drzl/generator-typebox");
678
+ const gen = new TypeBoxGenerator(analysis);
679
+ const target = g.path ?? "src/validators/typebox";
680
+ const files = await gen.generate({
681
+ outDir: target,
682
+ outputHeader: g.outputHeader,
683
+ format: g.format,
684
+ schemaSuffix: g.schemaSuffix,
685
+ fileSuffix: g.fileSuffix,
686
+ importExtension: g.importExtension,
687
+ affix: g.affix
688
+ });
689
+ progress.stop();
690
+ (0, import_ora.default)().succeed(import_chalk2.default.green(`Generated (typebox): ${files.length} files`));
691
+ files.forEach((f) => console.log(" -", import_chalk2.default.cyan(f)));
692
+ } catch (e) {
693
+ progress.stop();
694
+ console.error(
695
+ import_chalk2.default.red("ArkType generator missing."),
696
+ import_chalk2.default.yellow("\nInstall with: npm install @drzl/generator-typebox")
697
+ );
698
+ console.error(import_chalk2.default.gray("Error details:"), e?.message ?? e);
699
+ process.exit(1);
700
+ }
602
701
  }
603
702
  }
703
+ if (driftBefore) {
704
+ const after = await snapshotAll(driftDirs);
705
+ const drift = diffSnapshots(driftBefore, after);
706
+ await restoreSnapshot(driftBefore, after);
707
+ if (drift.length) {
708
+ console.error(
709
+ import_chalk2.default.red(`
710
+ Generated output is out of date (${drift.length} file(s)):`)
711
+ );
712
+ for (const d of drift) {
713
+ const mark = d.status === "added" ? "+" : d.status === "removed" ? "-" : "~";
714
+ console.error(` ${mark} ${import_chalk2.default.yellow(d.status.padEnd(8))} ${path4.relative(process.cwd(), d.file)}`);
715
+ }
716
+ console.error(
717
+ import_chalk2.default.dim("\nRun `drzl generate` and commit the result. Nothing was written by this check.")
718
+ );
719
+ process.exit(1);
720
+ }
721
+ console.log(import_chalk2.default.green("Generated output is up to date."));
722
+ return;
723
+ }
604
724
  if (cfg.generators.length) {
605
725
  maybeShowSponsorMessage({ reason: "generate" });
606
726
  }
@@ -640,10 +760,10 @@ program.command("watch").description("Watch schema and regenerate on changes").o
640
760
  process.exit(2);
641
761
  return;
642
762
  }
643
- const abs = (p) => path3.resolve(process.cwd(), p);
763
+ const abs = (p) => path4.resolve(process.cwd(), p);
644
764
  const isInside = (child, parent) => {
645
- const rel = path3.relative(parent, child);
646
- return !!rel && !rel.startsWith("..") && !path3.isAbsolute(rel);
765
+ const rel = path4.relative(parent, child);
766
+ return !!rel && !rel.startsWith("..") && !path4.isAbsolute(rel);
647
767
  };
648
768
  const ignoredOutDirs = new Set(computeGeneratorOutputDirs(cfg).map(abs));
649
769
  const currentTargets = new Set(computeWatchTargets(cfg).map(abs));
@@ -668,7 +788,7 @@ program.command("watch").description("Watch schema and regenerate on changes").o
668
788
  if (full === dir || isInside(full, dir)) return true;
669
789
  }
670
790
  if (stats?.isDirectory()) return false;
671
- const ext = path3.extname(full);
791
+ const ext = path4.extname(full);
672
792
  if (!ext) return false;
673
793
  return !WATCHED_EXTENSIONS.has(ext);
674
794
  };
@@ -902,7 +1022,7 @@ program.command("watch").description("Watch schema and regenerate on changes").o
902
1022
  } else {
903
1023
  console.log(
904
1024
  import_chalk2.default.gray(
905
- "Watching:\n " + Array.from(currentTargets).map((p) => path3.relative(process.cwd(), p)).join("\n ")
1025
+ "Watching:\n " + Array.from(currentTargets).map((p) => path4.relative(process.cwd(), p)).join("\n ")
906
1026
  )
907
1027
  );
908
1028
  }
@@ -910,9 +1030,9 @@ program.command("watch").description("Watch schema and regenerate on changes").o
910
1030
  await run();
911
1031
  });
912
1032
  program.command("init").description("Scaffold a drzl.config.ts").option("-y, --yes", "accept defaults").action(async (_opts) => {
913
- const fs2 = await import("fs/promises");
914
- const path4 = await import("path");
915
- const target = path4.resolve(process.cwd(), "drzl.config.ts");
1033
+ const fs3 = await import("fs/promises");
1034
+ const path5 = await import("path");
1035
+ const target = path5.resolve(process.cwd(), "drzl.config.ts");
916
1036
  const template = `export default {
917
1037
  schema: 'src/db/schema.ts',
918
1038
  outDir: 'src/api',
@@ -923,7 +1043,7 @@ program.command("init").description("Scaffold a drzl.config.ts").option("-y, --y
923
1043
  } as const
924
1044
  `;
925
1045
  try {
926
- await fs2.writeFile(target, template, { flag: "wx" });
1046
+ await fs3.writeFile(target, template, { flag: "wx" });
927
1047
  console.log(import_chalk2.default.green(`Created ${target}`));
928
1048
  } catch (e) {
929
1049
  console.error(import_chalk2.default.red("Init failed:"), e?.message ?? e);