@drzl/cli 1.1.0 → 2.0.1

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
  }
@@ -320,6 +446,7 @@ program.command("generate").description("Run configured generators (drzl.config.
320
446
  );
321
447
  const total = analysis.tables.length || 1;
322
448
  progress.start(total, 0);
449
+ const servicesDir = cfg.generators.find((x) => x.kind === "service")?.path ?? "src/services";
323
450
  for (const g of cfg.generators) {
324
451
  if (g.kind === "orpc") {
325
452
  const gen = new import_generator_orpc.ORPCGenerator(analysis);
@@ -331,7 +458,9 @@ program.command("generate").description("Run configured generators (drzl.config.
331
458
  outputHeader: g.outputHeader,
332
459
  format: g.format,
333
460
  templateOptions: g.templateOptions,
461
+ importExtension: g.importExtension,
334
462
  validation: g.validation,
463
+ servicesDir,
335
464
  onProgress: ({ index }) => progress.update(index)
336
465
  });
337
466
  progress.stop();
@@ -348,7 +477,8 @@ program.command("generate").description("Run configured generators (drzl.config.
348
477
  format: g.format,
349
478
  dataAccess: g.dataAccess,
350
479
  dbImportPath: g.dbImportPath,
351
- schemaImportPath: g.schemaImportPath
480
+ schemaImportPath: g.schemaImportPath,
481
+ importExtension: g.importExtension
352
482
  });
353
483
  progress.stop();
354
484
  (0, import_ora.default)().succeed(import_chalk2.default.green(`Generated (service): ${files.length} files`));
@@ -372,7 +502,9 @@ program.command("generate").description("Run configured generators (drzl.config.
372
502
  outputHeader: g.outputHeader,
373
503
  format: g.format,
374
504
  schemaSuffix: g.schemaSuffix,
375
- fileSuffix: g.fileSuffix
505
+ fileSuffix: g.fileSuffix,
506
+ importExtension: g.importExtension,
507
+ affix: g.affix
376
508
  });
377
509
  progress.stop();
378
510
  (0, import_ora.default)().succeed(import_chalk2.default.green(`Generated (zod): ${files.length} files`));
@@ -396,7 +528,9 @@ program.command("generate").description("Run configured generators (drzl.config.
396
528
  outputHeader: g.outputHeader,
397
529
  format: g.format,
398
530
  schemaSuffix: g.schemaSuffix,
399
- fileSuffix: g.fileSuffix
531
+ fileSuffix: g.fileSuffix,
532
+ importExtension: g.importExtension,
533
+ affix: g.affix
400
534
  });
401
535
  progress.stop();
402
536
  (0, import_ora.default)().succeed(import_chalk2.default.green(`Generated (valibot): ${files.length} files`));
@@ -420,7 +554,9 @@ program.command("generate").description("Run configured generators (drzl.config.
420
554
  outputHeader: g.outputHeader,
421
555
  format: g.format,
422
556
  schemaSuffix: g.schemaSuffix,
423
- fileSuffix: g.fileSuffix
557
+ fileSuffix: g.fileSuffix,
558
+ importExtension: g.importExtension,
559
+ affix: g.affix
424
560
  });
425
561
  progress.stop();
426
562
  (0, import_ora.default)().succeed(import_chalk2.default.green(`Generated (arktype): ${files.length} files`));
@@ -576,6 +712,7 @@ program.command("watch").description("Watch schema and regenerate on changes").o
576
712
  outputHeader: g.outputHeader,
577
713
  format: g.format,
578
714
  templateOptions: g.templateOptions,
715
+ importExtension: g.importExtension,
579
716
  validation: g.validation
580
717
  });
581
718
  opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
@@ -594,7 +731,8 @@ program.command("watch").description("Watch schema and regenerate on changes").o
594
731
  format: g.format,
595
732
  dataAccess: g.dataAccess,
596
733
  dbImportPath: g.dbImportPath,
597
- schemaImportPath: g.schemaImportPath
734
+ schemaImportPath: g.schemaImportPath,
735
+ importExtension: g.importExtension
598
736
  });
599
737
  opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
600
738
  import_chalk2.default.green(`Generated (service): ${files.length} files`),
@@ -619,7 +757,9 @@ program.command("watch").description("Watch schema and regenerate on changes").o
619
757
  outputHeader: g.outputHeader,
620
758
  format: g.format,
621
759
  schemaSuffix: g.schemaSuffix,
622
- fileSuffix: g.fileSuffix
760
+ fileSuffix: g.fileSuffix,
761
+ importExtension: g.importExtension,
762
+ affix: g.affix
623
763
  });
624
764
  opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
625
765
  import_chalk2.default.green(`Generated (zod): ${files.length} files`),
@@ -644,7 +784,9 @@ program.command("watch").description("Watch schema and regenerate on changes").o
644
784
  outputHeader: g.outputHeader,
645
785
  format: g.format,
646
786
  schemaSuffix: g.schemaSuffix,
647
- fileSuffix: g.fileSuffix
787
+ fileSuffix: g.fileSuffix,
788
+ importExtension: g.importExtension,
789
+ affix: g.affix
648
790
  });
649
791
  opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
650
792
  import_chalk2.default.green(`Generated (valibot): ${files.length} files`),
@@ -669,7 +811,9 @@ program.command("watch").description("Watch schema and regenerate on changes").o
669
811
  outputHeader: g.outputHeader,
670
812
  format: g.format,
671
813
  schemaSuffix: g.schemaSuffix,
672
- fileSuffix: g.fileSuffix
814
+ fileSuffix: g.fileSuffix,
815
+ importExtension: g.importExtension,
816
+ affix: g.affix
673
817
  });
674
818
  opts.json ? console.log(JSON.stringify({ event: "generate_complete", kind: g.kind, files })) : console.log(
675
819
  import_chalk2.default.green(`Generated (arktype): ${files.length} files`),