@drzl/cli 1.1.0 → 2.0.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.
@@ -0,0 +1,301 @@
1
+ // src/config.ts
2
+ import {
3
+ AFFIX_PROBE_TABLE,
4
+ DEFAULT_IMPORT_EXTENSION,
5
+ IMPORT_EXTENSIONS,
6
+ NAME_MODES,
7
+ resolveAffix,
8
+ schemaName,
9
+ validateAffix
10
+ } from "@drzl/validation-core";
11
+ import * as fs from "fs";
12
+ import { createRequire } from "module";
13
+ import * as path from "path";
14
+ import { z } from "zod";
15
+ var NamingSchema = z.object({
16
+ routerSuffix: z.string().default("Router"),
17
+ procedureCase: z.enum(["camel", "kebab", "snake"]).default("camel")
18
+ }).partial();
19
+ var AffixValueSchema = z.union(
20
+ [
21
+ z.string(),
22
+ z.object({
23
+ insert: z.string().optional(),
24
+ update: z.string().optional(),
25
+ select: z.string().optional()
26
+ }).strict()
27
+ ],
28
+ {
29
+ error: 'Expected a string to use for every mode, or an object with any of the keys "insert", "update" and "select". Those keys are lowercase, matching the mode names drzl uses everywhere else.'
30
+ }
31
+ );
32
+ var AffixPartSchema = z.object({
33
+ prefix: AffixValueSchema.optional(),
34
+ suffix: AffixValueSchema.optional()
35
+ }).strict();
36
+ var AffixSchema = z.object({
37
+ /**
38
+ * `preserve` (default) keeps today's output: the Drizzle export name goes into the
39
+ * identifier verbatim, so `export const users` yields `InsertusersSchema`. `pascal`
40
+ * upper-camels it first, yielding `InsertUsersSchema`.
41
+ */
42
+ tableCase: z.enum(["preserve", "pascal"]).optional(),
43
+ schema: AffixPartSchema.optional(),
44
+ type: AffixPartSchema.optional()
45
+ }).strict();
46
+ var ImportExtensionSchema = z.enum(IMPORT_EXTENSIONS);
47
+ var GeneratorSchema = z.object({
48
+ kind: z.enum(["orpc", "service", "zod", "valibot", "arktype"]),
49
+ /**
50
+ * Overrides the top-level `importExtension` for this generator alone, for a project whose
51
+ * generated directories are compiled by different tsconfigs.
52
+ */
53
+ importExtension: ImportExtensionSchema.optional(),
54
+ template: z.string().optional(),
55
+ includeRelations: z.boolean().optional(),
56
+ naming: NamingSchema.optional(),
57
+ outputHeader: z.object({
58
+ enabled: z.boolean().default(true).optional(),
59
+ text: z.string().optional()
60
+ }).optional(),
61
+ format: z.object({
62
+ enabled: z.boolean().default(true).optional(),
63
+ engine: z.enum(["auto", "prettier", "biome"]).default("auto").optional(),
64
+ configPath: z.string().optional()
65
+ }).optional(),
66
+ // service generator specific options
67
+ path: z.string().optional(),
68
+ dataAccess: z.enum(["stub", "drizzle"]).default("stub").optional(),
69
+ dbImportPath: z.string().optional(),
70
+ schemaImportPath: z.string().optional(),
71
+ // zod/valibot/arktype generator specific options
72
+ schemaSuffix: z.string().optional(),
73
+ fileSuffix: z.string().optional(),
74
+ /**
75
+ * Prefixes, suffixes and table casing for generated identifiers (zod/valibot/arktype).
76
+ * Omitting it reproduces the output of every previous release exactly.
77
+ */
78
+ affix: AffixSchema.optional(),
79
+ // orpc validation sharing
80
+ validation: z.object({
81
+ useShared: z.boolean().default(false).optional(),
82
+ library: z.enum(["zod", "valibot", "arktype"]).default("zod").optional(),
83
+ importPath: z.string().optional(),
84
+ schemaSuffix: z.string().optional(),
85
+ /**
86
+ * How the validation generator named its exports. Usually left unset: the CLI copies
87
+ * it from the sibling generator whose `kind` matches `library`.
88
+ */
89
+ affix: AffixSchema.optional()
90
+ }).optional(),
91
+ // template options
92
+ templateOptions: z.record(z.string(), z.any()).optional()
93
+ });
94
+ var AnalyzerSchema = z.object({
95
+ includeRelations: z.boolean().default(true),
96
+ validateConstraints: z.boolean().default(true),
97
+ includeHeuristicRelations: z.boolean().default(false)
98
+ });
99
+ var ConfigSchema = z.object({
100
+ schema: z.string(),
101
+ outDir: z.string().default("src/api"),
102
+ /**
103
+ * How every relative specifier drzl invents spells its extension, for every generator.
104
+ * A generator may override it. Defaults to `js`, which is the only form that resolves
105
+ * under every `moduleResolution` without a compiler flag.
106
+ */
107
+ importExtension: ImportExtensionSchema.default(DEFAULT_IMPORT_EXTENSION),
108
+ analyzer: AnalyzerSchema.default({
109
+ includeRelations: true,
110
+ validateConstraints: true,
111
+ includeHeuristicRelations: false
112
+ }),
113
+ generators: z.array(GeneratorSchema).min(1).default([{ kind: "orpc" }])
114
+ }).superRefine((cfg, ctx) => {
115
+ cfg.generators.forEach((g, i) => {
116
+ const report = (base, affix, schemaSuffix) => {
117
+ for (const issue of validateAffix(affix, schemaSuffix)) {
118
+ ctx.addIssue({
119
+ code: "custom",
120
+ path: ["generators", i, ...base, ...issue.path],
121
+ message: issue.message
122
+ });
123
+ }
124
+ };
125
+ report(["affix"], g.affix, g.schemaSuffix);
126
+ report(
127
+ ["validation", "affix"],
128
+ g.validation?.affix,
129
+ g.validation?.schemaSuffix
130
+ );
131
+ });
132
+ });
133
+ function defineConfig(cfg) {
134
+ return cfg;
135
+ }
136
+ function sharedSchemaNames(opts) {
137
+ const resolved = resolveAffix(opts);
138
+ return NAME_MODES.map((mode) => schemaName(mode, AFFIX_PROBE_TABLE, resolved));
139
+ }
140
+ function resolveConfig(cfg) {
141
+ const warnings = [];
142
+ const generators = cfg.generators.map((g) => ({
143
+ ...g,
144
+ importExtension: g.importExtension ?? cfg.importExtension
145
+ }));
146
+ for (const g of generators) {
147
+ if (g.kind !== "orpc") continue;
148
+ const v = g.validation;
149
+ if (!v?.useShared) continue;
150
+ const library = v.library ?? "zod";
151
+ const siblings = generators.filter((s) => s.kind === library);
152
+ if (siblings.length !== 1) continue;
153
+ const sibling = siblings[0];
154
+ const theirs = sharedSchemaNames({
155
+ affix: sibling.affix,
156
+ schemaSuffix: sibling.schemaSuffix
157
+ });
158
+ if (!v.affix) {
159
+ if (sibling.affix) {
160
+ g.validation = {
161
+ ...v,
162
+ affix: resolveAffix({
163
+ affix: sibling.affix,
164
+ schemaSuffix: sibling.schemaSuffix
165
+ })
166
+ };
167
+ continue;
168
+ }
169
+ const mine2 = sharedSchemaNames({ schemaSuffix: v.schemaSuffix });
170
+ if (mine2.join(",") !== theirs.join(",")) {
171
+ warnings.push(
172
+ `drzl config: the "orpc" generator's validation.schemaSuffix (${JSON.stringify(v.schemaSuffix ?? "Schema")}) does not match the "${library}" generator's schemaSuffix (${JSON.stringify(sibling.schemaSuffix ?? "Schema")}). The router will import ${mine2.join(", ")} but the "${library}" generator exports ${theirs.join(", ")}, so the generated router will not compile. Set both to the same value, or move to "affix", which is inherited automatically.`
173
+ );
174
+ }
175
+ continue;
176
+ }
177
+ const mine = sharedSchemaNames({
178
+ affix: v.affix,
179
+ schemaSuffix: v.schemaSuffix
180
+ });
181
+ if (mine.join(",") !== theirs.join(",")) {
182
+ throw new Error(
183
+ `drzl config: the "orpc" generator imports shared ${library} schemas, but its validation.affix disagrees with the "${library}" generator's own naming. The router would import ${mine.join(", ")} while the "${library}" generator exports ${theirs.join(", ")}. Make them match, or drop validation.affix and let it be inherited from the "${library}" generator.`
184
+ );
185
+ }
186
+ }
187
+ return { config: { ...cfg, generators }, warnings };
188
+ }
189
+ function finalize(raw) {
190
+ const { config, warnings } = resolveConfig(ConfigSchema.parse(raw));
191
+ for (const w of warnings) console.warn(w);
192
+ return config;
193
+ }
194
+ async function loadConfig(customPath) {
195
+ const fsp = await import("fs/promises");
196
+ const candidates = customPath ? [customPath] : [
197
+ "drzl.config.ts",
198
+ "drzl.config.mjs",
199
+ "drzl.config.js",
200
+ "drzl.config.cjs",
201
+ "drzl.config.json"
202
+ ];
203
+ for (const c of candidates) {
204
+ const p = path.resolve(process.cwd(), c);
205
+ try {
206
+ await fsp.access(p);
207
+ } catch {
208
+ continue;
209
+ }
210
+ const ext = path.extname(p).toLowerCase();
211
+ if (ext === ".json") {
212
+ const raw2 = JSON.parse(await fsp.readFile(p, "utf8"));
213
+ return finalize(raw2);
214
+ }
215
+ const { createJiti } = await import("jiti");
216
+ const stat = await fsp.stat(p);
217
+ const base = typeof __filename !== "undefined" ? __filename : path.join(process.cwd(), "index.js");
218
+ const jiti = createJiti(base, {
219
+ moduleCache: false,
220
+ // re-evaluate each time
221
+ fsCache: true,
222
+ // keep transform cache
223
+ cacheVersion: String(stat.mtimeMs),
224
+ // bump on edit
225
+ interopDefault: true,
226
+ tryNative: false
227
+ // <-- prevent native import of .ts
228
+ // debug: true,
229
+ });
230
+ const mod = await jiti.import(p);
231
+ const raw = mod?.default ?? mod;
232
+ return finalize(raw);
233
+ }
234
+ return null;
235
+ }
236
+ function computeGeneratorOutputDirs(cfg, cwd = process.cwd()) {
237
+ const abs = (p) => path.resolve(cwd, p);
238
+ const dirs = /* @__PURE__ */ new Set();
239
+ dirs.add(abs(cfg.outDir));
240
+ for (const g of cfg.generators) {
241
+ if (g.kind === "service") dirs.add(abs(g.path ?? "src/services"));
242
+ if (g.kind === "zod") dirs.add(abs(g.path ?? "src/validators/zod"));
243
+ if (g.kind === "valibot") dirs.add(abs(g.path ?? "src/validators/valibot"));
244
+ if (g.kind === "arktype") dirs.add(abs(g.path ?? "src/validators/arktype"));
245
+ }
246
+ return [...dirs];
247
+ }
248
+ function resolveTemplateDirsSync(cfg, cwd = process.cwd()) {
249
+ const results = [];
250
+ const req = createRequire(
251
+ typeof __filename !== "undefined" ? __filename : path.join(process.cwd(), "index.js")
252
+ );
253
+ for (const g of cfg.generators) {
254
+ const t = g.template;
255
+ if (!t || t === "standard" || t === "minimal") continue;
256
+ let pkgDir = null;
257
+ try {
258
+ const pkg = req.resolve(`${t}/package.json`, { paths: [cwd] });
259
+ pkgDir = path.dirname(pkg);
260
+ } catch {
261
+ }
262
+ if (pkgDir) {
263
+ results.push(pkgDir);
264
+ continue;
265
+ }
266
+ if (/[./\\]/.test(t)) {
267
+ const abs = path.resolve(cwd, t);
268
+ if (fs.existsSync(abs)) results.push(abs);
269
+ }
270
+ }
271
+ return Array.from(new Set(results));
272
+ }
273
+ function computeWatchTargets(cfg, cwd = process.cwd()) {
274
+ const abs = (p) => path.resolve(cwd, p);
275
+ const schemaAbs = abs(cfg.schema);
276
+ const targets = /* @__PURE__ */ new Set([
277
+ path.join(path.dirname(schemaAbs), "**/*.{ts,tsx,js}"),
278
+ abs("drzl.config.ts"),
279
+ abs("drzl.config.js"),
280
+ abs("drzl.config.mjs"),
281
+ abs("drzl.config.cjs")
282
+ ]);
283
+ for (const t of resolveTemplateDirsSync(cfg, cwd)) targets.add(t);
284
+ return [...targets];
285
+ }
286
+
287
+ export {
288
+ NamingSchema,
289
+ AffixSchema,
290
+ ImportExtensionSchema,
291
+ GeneratorSchema,
292
+ AnalyzerSchema,
293
+ ConfigSchema,
294
+ defineConfig,
295
+ resolveConfig,
296
+ loadConfig,
297
+ computeGeneratorOutputDirs,
298
+ resolveTemplateDirsSync,
299
+ computeWatchTargets
300
+ };
301
+ //# sourceMappingURL=chunk-HHT7INUJ.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']),\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 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 * 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 }\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). */\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 const targets = new Set<string>([\n path.join(path.dirname(schemaAbs), '**/*.{ts,tsx,js}'),\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
@@ -34,6 +34,7 @@ var path3 = __toESM(require("path"), 1);
34
34
  var import_ora = __toESM(require("ora"), 1);
35
35
 
36
36
  // src/config.ts
37
+ var import_validation_core = require("@drzl/validation-core");
37
38
  var fs = __toESM(require("fs"), 1);
38
39
  var import_node_module = require("module");
39
40
  var path = __toESM(require("path"), 1);
@@ -42,8 +43,41 @@ var NamingSchema = import_zod.z.object({
42
43
  routerSuffix: import_zod.z.string().default("Router"),
43
44
  procedureCase: import_zod.z.enum(["camel", "kebab", "snake"]).default("camel")
44
45
  }).partial();
46
+ var AffixValueSchema = import_zod.z.union(
47
+ [
48
+ import_zod.z.string(),
49
+ import_zod.z.object({
50
+ insert: import_zod.z.string().optional(),
51
+ update: import_zod.z.string().optional(),
52
+ select: import_zod.z.string().optional()
53
+ }).strict()
54
+ ],
55
+ {
56
+ error: 'Expected a string to use for every mode, or an object with any of the keys "insert", "update" and "select". Those keys are lowercase, matching the mode names drzl uses everywhere else.'
57
+ }
58
+ );
59
+ var AffixPartSchema = import_zod.z.object({
60
+ prefix: AffixValueSchema.optional(),
61
+ suffix: AffixValueSchema.optional()
62
+ }).strict();
63
+ var AffixSchema = import_zod.z.object({
64
+ /**
65
+ * `preserve` (default) keeps today's output: the Drizzle export name goes into the
66
+ * identifier verbatim, so `export const users` yields `InsertusersSchema`. `pascal`
67
+ * upper-camels it first, yielding `InsertUsersSchema`.
68
+ */
69
+ tableCase: import_zod.z.enum(["preserve", "pascal"]).optional(),
70
+ schema: AffixPartSchema.optional(),
71
+ type: AffixPartSchema.optional()
72
+ }).strict();
73
+ var ImportExtensionSchema = import_zod.z.enum(import_validation_core.IMPORT_EXTENSIONS);
45
74
  var GeneratorSchema = import_zod.z.object({
46
75
  kind: import_zod.z.enum(["orpc", "service", "zod", "valibot", "arktype"]),
76
+ /**
77
+ * Overrides the top-level `importExtension` for this generator alone, for a project whose
78
+ * generated directories are compiled by different tsconfigs.
79
+ */
80
+ importExtension: ImportExtensionSchema.optional(),
47
81
  template: import_zod.z.string().optional(),
48
82
  includeRelations: import_zod.z.boolean().optional(),
49
83
  naming: NamingSchema.optional(),
@@ -64,12 +98,22 @@ var GeneratorSchema = import_zod.z.object({
64
98
  // zod/valibot/arktype generator specific options
65
99
  schemaSuffix: import_zod.z.string().optional(),
66
100
  fileSuffix: import_zod.z.string().optional(),
101
+ /**
102
+ * Prefixes, suffixes and table casing for generated identifiers (zod/valibot/arktype).
103
+ * Omitting it reproduces the output of every previous release exactly.
104
+ */
105
+ affix: AffixSchema.optional(),
67
106
  // orpc validation sharing
68
107
  validation: import_zod.z.object({
69
108
  useShared: import_zod.z.boolean().default(false).optional(),
70
109
  library: import_zod.z.enum(["zod", "valibot", "arktype"]).default("zod").optional(),
71
110
  importPath: import_zod.z.string().optional(),
72
- schemaSuffix: import_zod.z.string().optional()
111
+ schemaSuffix: import_zod.z.string().optional(),
112
+ /**
113
+ * How the validation generator named its exports. Usually left unset: the CLI copies
114
+ * it from the sibling generator whose `kind` matches `library`.
115
+ */
116
+ affix: AffixSchema.optional()
73
117
  }).optional(),
74
118
  // template options
75
119
  templateOptions: import_zod.z.record(import_zod.z.string(), import_zod.z.any()).optional()
@@ -82,13 +126,95 @@ var AnalyzerSchema = import_zod.z.object({
82
126
  var ConfigSchema = import_zod.z.object({
83
127
  schema: import_zod.z.string(),
84
128
  outDir: import_zod.z.string().default("src/api"),
129
+ /**
130
+ * How every relative specifier drzl invents spells its extension, for every generator.
131
+ * A generator may override it. Defaults to `js`, which is the only form that resolves
132
+ * under every `moduleResolution` without a compiler flag.
133
+ */
134
+ importExtension: ImportExtensionSchema.default(import_validation_core.DEFAULT_IMPORT_EXTENSION),
85
135
  analyzer: AnalyzerSchema.default({
86
136
  includeRelations: true,
87
137
  validateConstraints: true,
88
138
  includeHeuristicRelations: false
89
139
  }),
90
140
  generators: import_zod.z.array(GeneratorSchema).min(1).default([{ kind: "orpc" }])
141
+ }).superRefine((cfg, ctx) => {
142
+ cfg.generators.forEach((g, i) => {
143
+ const report = (base, affix, schemaSuffix) => {
144
+ for (const issue of (0, import_validation_core.validateAffix)(affix, schemaSuffix)) {
145
+ ctx.addIssue({
146
+ code: "custom",
147
+ path: ["generators", i, ...base, ...issue.path],
148
+ message: issue.message
149
+ });
150
+ }
151
+ };
152
+ report(["affix"], g.affix, g.schemaSuffix);
153
+ report(
154
+ ["validation", "affix"],
155
+ g.validation?.affix,
156
+ g.validation?.schemaSuffix
157
+ );
158
+ });
91
159
  });
160
+ function sharedSchemaNames(opts) {
161
+ const resolved = (0, import_validation_core.resolveAffix)(opts);
162
+ return import_validation_core.NAME_MODES.map((mode) => (0, import_validation_core.schemaName)(mode, import_validation_core.AFFIX_PROBE_TABLE, resolved));
163
+ }
164
+ function resolveConfig(cfg) {
165
+ const warnings = [];
166
+ const generators = cfg.generators.map((g) => ({
167
+ ...g,
168
+ importExtension: g.importExtension ?? cfg.importExtension
169
+ }));
170
+ for (const g of generators) {
171
+ if (g.kind !== "orpc") continue;
172
+ const v = g.validation;
173
+ if (!v?.useShared) continue;
174
+ const library = v.library ?? "zod";
175
+ const siblings = generators.filter((s) => s.kind === library);
176
+ if (siblings.length !== 1) continue;
177
+ const sibling = siblings[0];
178
+ const theirs = sharedSchemaNames({
179
+ affix: sibling.affix,
180
+ schemaSuffix: sibling.schemaSuffix
181
+ });
182
+ if (!v.affix) {
183
+ if (sibling.affix) {
184
+ g.validation = {
185
+ ...v,
186
+ affix: (0, import_validation_core.resolveAffix)({
187
+ affix: sibling.affix,
188
+ schemaSuffix: sibling.schemaSuffix
189
+ })
190
+ };
191
+ continue;
192
+ }
193
+ const mine2 = sharedSchemaNames({ schemaSuffix: v.schemaSuffix });
194
+ if (mine2.join(",") !== theirs.join(",")) {
195
+ warnings.push(
196
+ `drzl config: the "orpc" generator's validation.schemaSuffix (${JSON.stringify(v.schemaSuffix ?? "Schema")}) does not match the "${library}" generator's schemaSuffix (${JSON.stringify(sibling.schemaSuffix ?? "Schema")}). The router will import ${mine2.join(", ")} but the "${library}" generator exports ${theirs.join(", ")}, so the generated router will not compile. Set both to the same value, or move to "affix", which is inherited automatically.`
197
+ );
198
+ }
199
+ continue;
200
+ }
201
+ const mine = sharedSchemaNames({
202
+ affix: v.affix,
203
+ schemaSuffix: v.schemaSuffix
204
+ });
205
+ if (mine.join(",") !== theirs.join(",")) {
206
+ throw new Error(
207
+ `drzl config: the "orpc" generator imports shared ${library} schemas, but its validation.affix disagrees with the "${library}" generator's own naming. The router would import ${mine.join(", ")} while the "${library}" generator exports ${theirs.join(", ")}. Make them match, or drop validation.affix and let it be inherited from the "${library}" generator.`
208
+ );
209
+ }
210
+ }
211
+ return { config: { ...cfg, generators }, warnings };
212
+ }
213
+ function finalize(raw) {
214
+ const { config, warnings } = resolveConfig(ConfigSchema.parse(raw));
215
+ for (const w of warnings) console.warn(w);
216
+ return config;
217
+ }
92
218
  async function loadConfig(customPath) {
93
219
  const fsp = await import("fs/promises");
94
220
  const candidates = customPath ? [customPath] : [
@@ -108,7 +234,7 @@ async function loadConfig(customPath) {
108
234
  const ext = path.extname(p).toLowerCase();
109
235
  if (ext === ".json") {
110
236
  const raw2 = JSON.parse(await fsp.readFile(p, "utf8"));
111
- return ConfigSchema.parse(raw2);
237
+ return finalize(raw2);
112
238
  }
113
239
  const { createJiti } = await import("jiti");
114
240
  const stat = await fsp.stat(p);
@@ -122,12 +248,12 @@ async function loadConfig(customPath) {
122
248
  // bump on edit
123
249
  interopDefault: true,
124
250
  tryNative: false
125
- // <— prevent native import of .ts
251
+ // <-- prevent native import of .ts
126
252
  // debug: true,
127
253
  });
128
254
  const mod = await jiti.import(p);
129
255
  const raw = mod?.default ?? mod;
130
- return ConfigSchema.parse(raw);
256
+ return finalize(raw);
131
257
  }
132
258
  return null;
133
259
  }
@@ -331,6 +457,7 @@ program.command("generate").description("Run configured generators (drzl.config.
331
457
  outputHeader: g.outputHeader,
332
458
  format: g.format,
333
459
  templateOptions: g.templateOptions,
460
+ importExtension: g.importExtension,
334
461
  validation: g.validation,
335
462
  onProgress: ({ index }) => progress.update(index)
336
463
  });
@@ -348,7 +475,8 @@ program.command("generate").description("Run configured generators (drzl.config.
348
475
  format: g.format,
349
476
  dataAccess: g.dataAccess,
350
477
  dbImportPath: g.dbImportPath,
351
- schemaImportPath: g.schemaImportPath
478
+ schemaImportPath: g.schemaImportPath,
479
+ importExtension: g.importExtension
352
480
  });
353
481
  progress.stop();
354
482
  (0, import_ora.default)().succeed(import_chalk2.default.green(`Generated (service): ${files.length} files`));
@@ -372,7 +500,9 @@ program.command("generate").description("Run configured generators (drzl.config.
372
500
  outputHeader: g.outputHeader,
373
501
  format: g.format,
374
502
  schemaSuffix: g.schemaSuffix,
375
- fileSuffix: g.fileSuffix
503
+ fileSuffix: g.fileSuffix,
504
+ importExtension: g.importExtension,
505
+ affix: g.affix
376
506
  });
377
507
  progress.stop();
378
508
  (0, import_ora.default)().succeed(import_chalk2.default.green(`Generated (zod): ${files.length} files`));
@@ -396,7 +526,9 @@ program.command("generate").description("Run configured generators (drzl.config.
396
526
  outputHeader: g.outputHeader,
397
527
  format: g.format,
398
528
  schemaSuffix: g.schemaSuffix,
399
- fileSuffix: g.fileSuffix
529
+ fileSuffix: g.fileSuffix,
530
+ importExtension: g.importExtension,
531
+ affix: g.affix
400
532
  });
401
533
  progress.stop();
402
534
  (0, import_ora.default)().succeed(import_chalk2.default.green(`Generated (valibot): ${files.length} files`));
@@ -420,7 +552,9 @@ program.command("generate").description("Run configured generators (drzl.config.
420
552
  outputHeader: g.outputHeader,
421
553
  format: g.format,
422
554
  schemaSuffix: g.schemaSuffix,
423
- fileSuffix: g.fileSuffix
555
+ fileSuffix: g.fileSuffix,
556
+ importExtension: g.importExtension,
557
+ affix: g.affix
424
558
  });
425
559
  progress.stop();
426
560
  (0, import_ora.default)().succeed(import_chalk2.default.green(`Generated (arktype): ${files.length} files`));
@@ -576,6 +710,7 @@ program.command("watch").description("Watch schema and regenerate on changes").o
576
710
  outputHeader: g.outputHeader,
577
711
  format: g.format,
578
712
  templateOptions: g.templateOptions,
713
+ importExtension: g.importExtension,
579
714
  validation: g.validation
580
715
  });
581
716
  opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
@@ -594,7 +729,8 @@ program.command("watch").description("Watch schema and regenerate on changes").o
594
729
  format: g.format,
595
730
  dataAccess: g.dataAccess,
596
731
  dbImportPath: g.dbImportPath,
597
- schemaImportPath: g.schemaImportPath
732
+ schemaImportPath: g.schemaImportPath,
733
+ importExtension: g.importExtension
598
734
  });
599
735
  opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
600
736
  import_chalk2.default.green(`Generated (service): ${files.length} files`),
@@ -619,7 +755,9 @@ program.command("watch").description("Watch schema and regenerate on changes").o
619
755
  outputHeader: g.outputHeader,
620
756
  format: g.format,
621
757
  schemaSuffix: g.schemaSuffix,
622
- fileSuffix: g.fileSuffix
758
+ fileSuffix: g.fileSuffix,
759
+ importExtension: g.importExtension,
760
+ affix: g.affix
623
761
  });
624
762
  opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
625
763
  import_chalk2.default.green(`Generated (zod): ${files.length} files`),
@@ -644,7 +782,9 @@ program.command("watch").description("Watch schema and regenerate on changes").o
644
782
  outputHeader: g.outputHeader,
645
783
  format: g.format,
646
784
  schemaSuffix: g.schemaSuffix,
647
- fileSuffix: g.fileSuffix
785
+ fileSuffix: g.fileSuffix,
786
+ importExtension: g.importExtension,
787
+ affix: g.affix
648
788
  });
649
789
  opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
650
790
  import_chalk2.default.green(`Generated (valibot): ${files.length} files`),
@@ -669,7 +809,9 @@ program.command("watch").description("Watch schema and regenerate on changes").o
669
809
  outputHeader: g.outputHeader,
670
810
  format: g.format,
671
811
  schemaSuffix: g.schemaSuffix,
672
- fileSuffix: g.fileSuffix
812
+ fileSuffix: g.fileSuffix,
813
+ importExtension: g.importExtension,
814
+ affix: g.affix
673
815
  });
674
816
  opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
675
817
  import_chalk2.default.green(`Generated (arktype): ${files.length} files`),