@xlameiro/env-typegen 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -110,14 +110,27 @@ import { existsSync } from "fs";
110
110
  import path from "path";
111
111
  import { pathToFileURL } from "url";
112
112
  var CONFIG_FILE_NAMES = [
113
- "env-typegen.config.ts",
114
113
  "env-typegen.config.mjs",
115
- "env-typegen.config.js"
114
+ "env-typegen.config.js",
115
+ "env-typegen.config.ts"
116
116
  ];
117
117
  async function loadConfig(cwd = process.cwd()) {
118
118
  for (const name of CONFIG_FILE_NAMES) {
119
119
  const filePath = path.resolve(cwd, name);
120
120
  if (existsSync(filePath)) {
121
+ if (filePath.endsWith(".ts")) {
122
+ throw new Error(
123
+ `Config file "${name}" was found but TypeScript files cannot be loaded directly at runtime.
124
+ Rename it to "env-typegen.config.mjs" and use ESM export syntax:
125
+
126
+ // env-typegen.config.mjs
127
+ import { defineConfig } from "@xlameiro/env-typegen";
128
+ export default defineConfig({ input: ".env.example" });
129
+
130
+ Tip: keep env-typegen.config.ts for IDE autocompletion and create a sibling
131
+ env-typegen.config.mjs for runtime loading.`
132
+ );
133
+ }
121
134
  const fileUrl = pathToFileURL(filePath).href;
122
135
  const mod = await import(fileUrl);
123
136
  return mod.default;
@@ -133,12 +146,13 @@ import path5 from "path";
133
146
  import path2 from "path";
134
147
  function generateDeclaration(parsed) {
135
148
  const fileName = path2.basename(parsed.filePath);
136
- const lines = [];
137
- lines.push("// Generated by env-typegen \u2014 do not edit manually");
138
- lines.push(`// Source: ${fileName}`);
139
- lines.push("");
140
- lines.push("declare namespace NodeJS {");
141
- lines.push(" interface ProcessEnv {");
149
+ const lines = [
150
+ "// Generated by env-typegen \u2014 do not edit manually",
151
+ `// Source: ${fileName}`,
152
+ "",
153
+ "declare namespace NodeJS {",
154
+ " interface ProcessEnv {"
155
+ ];
142
156
  for (const variable of parsed.vars) {
143
157
  const effectiveType = variable.annotatedType ?? variable.inferredType;
144
158
  const optional = variable.isOptional ? "?" : "";
@@ -153,12 +167,14 @@ function generateDeclaration(parsed) {
153
167
  }
154
168
  lines.push(propLine);
155
169
  }
156
- lines.push(" }");
157
- lines.push("}");
170
+ lines.push(" }", "}");
158
171
  return lines.join("\n") + "\n";
159
172
  }
160
173
 
161
174
  // src/generators/t3-generator.ts
175
+ function escapeJsStringLiteral(value) {
176
+ return value.replaceAll("\\", String.raw`\\`).replaceAll('"', String.raw`\"`);
177
+ }
162
178
  function toT3ZodType(envVarType) {
163
179
  if (envVarType === "number") return "z.coerce.number()";
164
180
  if (envVarType === "boolean") return "z.coerce.boolean()";
@@ -166,51 +182,47 @@ function toT3ZodType(envVarType) {
166
182
  if (envVarType === "email") return "z.string().email()";
167
183
  return "z.string()";
168
184
  }
185
+ function buildZodExpr(variable) {
186
+ const effectiveType = variable.annotatedType ?? variable.inferredType;
187
+ let zodExpr = toT3ZodType(effectiveType);
188
+ if (variable.description !== void 0) {
189
+ zodExpr += `.describe("${escapeJsStringLiteral(variable.description)}")`;
190
+ }
191
+ if (variable.isOptional) {
192
+ zodExpr += ".optional()";
193
+ }
194
+ return zodExpr;
195
+ }
169
196
  function generateT3Env(parsed) {
170
197
  const serverVars = parsed.vars.filter((v) => !v.isClientSide);
171
198
  const clientVars = parsed.vars.filter((v) => v.isClientSide);
172
- const lines = [];
173
- lines.push("// Generated by env-typegen \u2014 do not edit manually");
174
- lines.push('import { createEnv } from "@t3-oss/env-nextjs";');
175
- lines.push('import { z } from "zod";');
176
- lines.push("");
177
- lines.push("export const env = createEnv({");
199
+ const lines = [
200
+ "// Generated by env-typegen \u2014 do not edit manually",
201
+ 'import { createEnv } from "@t3-oss/env-nextjs";',
202
+ 'import { z } from "zod";',
203
+ "",
204
+ "export const env = createEnv({"
205
+ ];
178
206
  if (serverVars.length > 0) {
179
- lines.push(" server: {");
180
- for (const variable of serverVars) {
181
- const effectiveType = variable.annotatedType ?? variable.inferredType;
182
- let zodExpr = toT3ZodType(effectiveType);
183
- if (variable.description !== void 0) {
184
- zodExpr += `.describe("${variable.description.replace(/"/g, '\\"')}")`;
185
- }
186
- if (variable.isOptional) {
187
- zodExpr += ".optional()";
188
- }
189
- lines.push(` ${variable.key}: ${zodExpr},`);
190
- }
191
- lines.push(" },");
207
+ lines.push(
208
+ " server: {",
209
+ ...serverVars.map((v) => ` ${v.key}: ${buildZodExpr(v)},`),
210
+ " },"
211
+ );
192
212
  }
193
213
  if (clientVars.length > 0) {
194
- lines.push(" client: {");
195
- for (const variable of clientVars) {
196
- const effectiveType = variable.annotatedType ?? variable.inferredType;
197
- let zodExpr = toT3ZodType(effectiveType);
198
- if (variable.description !== void 0) {
199
- zodExpr += `.describe("${variable.description.replace(/"/g, '\\"')}")`;
200
- }
201
- if (variable.isOptional) {
202
- zodExpr += ".optional()";
203
- }
204
- lines.push(` ${variable.key}: ${zodExpr},`);
205
- }
206
- lines.push(" },");
207
- }
208
- lines.push(" runtimeEnv: {");
209
- for (const variable of parsed.vars) {
210
- lines.push(` ${variable.key}: process.env.${variable.key},`);
214
+ lines.push(
215
+ " client: {",
216
+ ...clientVars.map((v) => ` ${v.key}: ${buildZodExpr(v)},`),
217
+ " },"
218
+ );
211
219
  }
212
- lines.push(" },");
213
- lines.push("});");
220
+ lines.push(
221
+ " runtimeEnv: {",
222
+ ...parsed.vars.map((v) => ` ${v.key}: process.env.${v.key},`),
223
+ " },",
224
+ "});"
225
+ );
214
226
  return lines.join("\n") + "\n";
215
227
  }
216
228
 
@@ -227,12 +239,14 @@ function generateTypeScriptTypes(parsed) {
227
239
  const fileName = path3.basename(parsed.filePath);
228
240
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
229
241
  const lines = [];
230
- lines.push("// Generated by env-typegen \u2014 do not edit manually");
231
- lines.push(`// Source: ${fileName}`);
232
- lines.push(`// Generated at: ${timestamp}`);
233
- lines.push("");
234
- lines.push("declare namespace NodeJS {");
235
- lines.push(" interface ProcessEnv {");
242
+ lines.push(
243
+ "// Generated by env-typegen \u2014 do not edit manually",
244
+ `// Source: ${fileName}`,
245
+ `// Generated at: ${timestamp}`,
246
+ "",
247
+ "declare namespace NodeJS {",
248
+ " interface ProcessEnv {"
249
+ );
236
250
  for (const variable of parsed.vars) {
237
251
  const effectiveType = variable.annotatedType ?? variable.inferredType;
238
252
  const optional = variable.isOptional ? "?" : "";
@@ -247,10 +261,7 @@ function generateTypeScriptTypes(parsed) {
247
261
  }
248
262
  lines.push(propLine);
249
263
  }
250
- lines.push(" }");
251
- lines.push("}");
252
- lines.push("");
253
- lines.push("export type EnvVars = {");
264
+ lines.push(" }", "}", "", "export type EnvVars = {");
254
265
  for (const variable of parsed.vars) {
255
266
  const effectiveType = variable.annotatedType ?? variable.inferredType;
256
267
  const tsType = toTsType(effectiveType);
@@ -260,9 +271,11 @@ function generateTypeScriptTypes(parsed) {
260
271
  lines.push("};");
261
272
  if (hasClientVars) {
262
273
  const clientKeyUnion = clientVars.map((v) => `"${v.key}"`).join(" | ");
263
- lines.push("");
264
- lines.push(`export type ServerEnvVars = Omit<EnvVars, ${clientKeyUnion}>;`);
265
- lines.push(`export type ClientEnvVars = Pick<EnvVars, ${clientKeyUnion}>;`);
274
+ lines.push(
275
+ "",
276
+ `export type ServerEnvVars = Omit<EnvVars, ${clientKeyUnion}>;`,
277
+ `export type ClientEnvVars = Pick<EnvVars, ${clientKeyUnion}>;`
278
+ );
266
279
  }
267
280
  return lines.join("\n") + "\n";
268
281
  }
@@ -279,27 +292,29 @@ function generateZodSchema(parsed) {
279
292
  const serverVars = parsed.vars.filter((v) => !v.isClientSide);
280
293
  const clientVars = parsed.vars.filter((v) => v.isClientSide);
281
294
  const lines = [];
282
- lines.push("// Generated by env-typegen \u2014 do not edit manually");
283
- lines.push('import { z } from "zod";');
284
- lines.push("");
285
- lines.push("export const serverEnvSchema = z.object({");
295
+ lines.push(
296
+ "// Generated by env-typegen \u2014 do not edit manually",
297
+ 'import { z } from "zod";',
298
+ "",
299
+ "export const serverEnvSchema = z.object({"
300
+ );
286
301
  for (const variable of serverVars) {
287
302
  const effectiveType = variable.annotatedType ?? variable.inferredType;
288
303
  const zodExpr = variable.isOptional ? `${toZodType(effectiveType)}.optional()` : toZodType(effectiveType);
289
304
  lines.push(` ${variable.key}: ${zodExpr},`);
290
305
  }
291
- lines.push("});");
292
- lines.push("");
293
- lines.push("export const clientEnvSchema = z.object({");
306
+ lines.push("});", "", "export const clientEnvSchema = z.object({");
294
307
  for (const variable of clientVars) {
295
308
  const effectiveType = variable.annotatedType ?? variable.inferredType;
296
309
  const zodExpr = variable.isOptional ? `${toZodType(effectiveType)}.optional()` : toZodType(effectiveType);
297
310
  lines.push(` ${variable.key}: ${zodExpr},`);
298
311
  }
299
- lines.push("});");
300
- lines.push("");
301
- lines.push("export const envSchema = serverEnvSchema.merge(clientEnvSchema);");
302
- lines.push("export type Env = z.infer<typeof envSchema>;");
312
+ lines.push(
313
+ "});",
314
+ "",
315
+ "export const envSchema = serverEnvSchema.merge(clientEnvSchema);",
316
+ "export type Env = z.infer<typeof envSchema>;"
317
+ );
303
318
  return lines.join("\n") + "\n";
304
319
  }
305
320
 
@@ -359,7 +374,9 @@ var inferenceRules = [
359
374
  {
360
375
  id: "P8_numeric_literal",
361
376
  priority: 8,
362
- match: (_key, value) => /^\d+(\.\d+)?$/.test(value),
377
+ // Non-capturing group with \d keeps the dot/digit boundary unambiguous,
378
+ // eliminating super-linear backtracking (ReDoS-safe).
379
+ match: (_key, value) => /^\d+(?:\.\d+)?$/.test(value),
363
380
  type: "number"
364
381
  },
365
382
  {
@@ -377,7 +394,9 @@ var inferenceRules = [
377
394
  {
378
395
  id: "P11_email_literal",
379
396
  priority: 11,
380
- match: (_key, value) => /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value),
397
+ // Dots are excluded from each domain-segment character class so that the
398
+ // literal \. separators are unambiguous, preventing super-linear backtracking.
399
+ match: (_key, value) => /^[^@\s]+@[^@\s.]+(?:\.[^@\s.]+)+$/.test(value),
381
400
  type: "email"
382
401
  },
383
402
  {
@@ -494,7 +513,50 @@ function parseCommentBlock(lines) {
494
513
 
495
514
  // src/parser/env-parser.ts
496
515
  var ENV_VAR_RE = /^([A-Z_][A-Z0-9_]*)=(.*)$/;
497
- var SECTION_HEADER_RE = /^#\s+[-=]{3,}\s+(.+?)\s+[-=]{3,}\s*$/;
516
+ var SECTION_HEADER_RE = /^#\s+[-=]{3,}\s+(\S+(?:\s+\S+)*)\s+[-=]{3,}\s*$/;
517
+ function buildParsedVar(params, commentBlock, options) {
518
+ const annotations = parseCommentBlock(commentBlock);
519
+ const extraRules = options?.inferenceRules;
520
+ const inferredType = inferType(
521
+ params.key,
522
+ params.rawValue,
523
+ ...extraRules === void 0 ? [] : [{ extraRules }]
524
+ );
525
+ const isRequired = params.rawValue.length > 0 || annotations.isRequired;
526
+ const isOptional = params.rawValue.length === 0 && !annotations.isRequired;
527
+ const isClientSide = params.key.startsWith("NEXT_PUBLIC_");
528
+ const parsedVar = {
529
+ key: params.key,
530
+ rawValue: params.rawValue,
531
+ inferredType,
532
+ isRequired,
533
+ isOptional,
534
+ isClientSide,
535
+ lineNumber: params.lineNumber
536
+ };
537
+ if (annotations.annotatedType !== void 0) {
538
+ parsedVar.annotatedType = annotations.annotatedType;
539
+ }
540
+ if (annotations.description !== void 0) {
541
+ parsedVar.description = annotations.description;
542
+ }
543
+ if (params.currentGroup !== void 0) {
544
+ parsedVar.group = params.currentGroup;
545
+ }
546
+ if (annotations.enumValues !== void 0) {
547
+ parsedVar.enumValues = annotations.enumValues;
548
+ }
549
+ if (annotations.constraints !== void 0) {
550
+ parsedVar.constraints = annotations.constraints;
551
+ }
552
+ if (annotations.runtime !== void 0) {
553
+ parsedVar.runtime = annotations.runtime;
554
+ }
555
+ if (annotations.isSecret !== void 0) {
556
+ parsedVar.isSecret = annotations.isSecret;
557
+ }
558
+ return parsedVar;
559
+ }
498
560
  function parseEnvFileContent(content, filePath, options) {
499
561
  const lines = content.split("\n");
500
562
  const vars = [];
@@ -524,53 +586,18 @@ function parseEnvFileContent(content, filePath, options) {
524
586
  continue;
525
587
  }
526
588
  const envMatch = ENV_VAR_RE.exec(trimmed);
527
- if (envMatch !== null) {
528
- const key = envMatch[1] ?? "";
529
- const rawValue = envMatch[2] ?? "";
530
- const annotations = parseCommentBlock(commentBlock);
531
- const inferredType = inferType(
532
- key,
533
- rawValue,
534
- ...options?.inferenceRules !== void 0 ? [{ extraRules: options.inferenceRules }] : []
535
- );
536
- const isRequired = rawValue.length > 0 || annotations.isRequired;
537
- const isOptional = rawValue.length === 0 && !annotations.isRequired;
538
- const isClientSide = key.startsWith("NEXT_PUBLIC_");
539
- const parsedVar = {
540
- key,
541
- rawValue,
542
- inferredType,
543
- isRequired,
544
- isOptional,
545
- isClientSide,
546
- lineNumber
547
- };
548
- if (annotations.annotatedType !== void 0) {
549
- parsedVar.annotatedType = annotations.annotatedType;
550
- }
551
- if (annotations.description !== void 0) {
552
- parsedVar.description = annotations.description;
553
- }
554
- if (currentGroup !== void 0) {
555
- parsedVar.group = currentGroup;
556
- }
557
- if (annotations.enumValues !== void 0) {
558
- parsedVar.enumValues = annotations.enumValues;
559
- }
560
- if (annotations.constraints !== void 0) {
561
- parsedVar.constraints = annotations.constraints;
562
- }
563
- if (annotations.runtime !== void 0) {
564
- parsedVar.runtime = annotations.runtime;
565
- }
566
- if (annotations.isSecret !== void 0) {
567
- parsedVar.isSecret = annotations.isSecret;
568
- }
569
- vars.push(parsedVar);
570
- commentBlock = [];
571
- } else {
589
+ if (envMatch === null) {
572
590
  commentBlock = [];
591
+ continue;
573
592
  }
593
+ vars.push(
594
+ buildParsedVar(
595
+ { key: envMatch[1] ?? "", rawValue: envMatch[2] ?? "", lineNumber, currentGroup },
596
+ commentBlock,
597
+ options
598
+ )
599
+ );
600
+ commentBlock = [];
574
601
  }
575
602
  return { filePath, vars, groups };
576
603
  }
@@ -690,7 +717,7 @@ async function runGenerate(options) {
690
717
  const parsed = parseEnvFileContent(
691
718
  content,
692
719
  inputPath,
693
- inferenceRules2 !== void 0 ? { inferenceRules: inferenceRules2 } : void 0
720
+ inferenceRules2 === void 0 ? void 0 : { inferenceRules: inferenceRules2 }
694
721
  );
695
722
  for (const generator of generators) {
696
723
  let generated = buildOutput(generator, parsed);
@@ -729,8 +756,13 @@ function readEntryValue(entry, keys) {
729
756
  }
730
757
  return void 0;
731
758
  }
759
+ function getVercelEntries(value) {
760
+ if (Array.isArray(value)) return value;
761
+ if (isRecord(value) && Array.isArray(value.envs)) return value.envs;
762
+ return [];
763
+ }
732
764
  function parseVercelPayload(value) {
733
- const entries = Array.isArray(value) ? value : isRecord(value) && Array.isArray(value.envs) ? value.envs : [];
765
+ const entries = getVercelEntries(value);
734
766
  const result = {};
735
767
  for (const entry of entries) {
736
768
  if (!isRecord(entry)) continue;
@@ -741,8 +773,13 @@ function parseVercelPayload(value) {
741
773
  }
742
774
  return result;
743
775
  }
776
+ function getCloudflareEntries(value) {
777
+ if (Array.isArray(value)) return value;
778
+ if (isRecord(value) && Array.isArray(value.result)) return value.result;
779
+ return [];
780
+ }
744
781
  function parseCloudflarePayload(value) {
745
- const entries = Array.isArray(value) ? value : isRecord(value) && Array.isArray(value.result) ? value.result : [];
782
+ const entries = getCloudflareEntries(value);
746
783
  const result = {};
747
784
  for (const entry of entries) {
748
785
  if (!isRecord(entry)) continue;
@@ -760,7 +797,7 @@ function parseAwsPayload(value) {
760
797
  if (!isRecord(entry)) continue;
761
798
  const name = readEntryValue(entry, ["Name", "name"]);
762
799
  if (name === void 0) continue;
763
- const key = name.split("/").filter((part) => part.length > 0).pop() ?? name;
800
+ const key = name.split("/").findLast((part) => part.length > 0) ?? name;
764
801
  const envValue = readEntryValue(entry, ["Value", "value"]) ?? "";
765
802
  result[key] = envValue;
766
803
  }
@@ -905,7 +942,7 @@ function findDefaultContractPath(cwd) {
905
942
  async function loadValidationContract(options) {
906
943
  const { fallbackExamplePath, contractPath, cwd = process.cwd() } = options;
907
944
  const discoveredContractPath = findDefaultContractPath(cwd);
908
- const resolvedContractPath = contractPath !== void 0 ? path7.resolve(cwd, contractPath) : discoveredContractPath;
945
+ const resolvedContractPath = contractPath === void 0 ? discoveredContractPath : path7.resolve(cwd, contractPath);
909
946
  if (resolvedContractPath !== void 0 && existsSync2(resolvedContractPath)) {
910
947
  const moduleUrl = pathToFileURL2(resolvedContractPath).href;
911
948
  const moduleValue = await import(moduleUrl);
@@ -948,7 +985,14 @@ async function loadPluginFromPath(pluginPath, cwd) {
948
985
  const moduleValue = await import(pathToFileURL3(resolvedPath).href);
949
986
  const candidate = moduleValue.default ?? moduleValue.plugin;
950
987
  if (isPlugin(candidate)) return candidate;
951
- throw new Error(`Invalid plugin at ${resolvedPath}. Expected a plugin object export.`);
988
+ throw new Error(
989
+ `Invalid plugin at ${resolvedPath}.
990
+ Expected a default export matching:
991
+ { name: string,
992
+ transformSource?(ctx: { environment: string; values: Record<string, string> }): Record<string, string>,
993
+ transformReport?(report: ValidationReport): ValidationReport,
994
+ transformContract?(contract: EnvContract): EnvContract }`
995
+ );
952
996
  }
953
997
  async function loadPlugins(options) {
954
998
  const cwd = options.cwd ?? process.cwd();
@@ -993,8 +1037,8 @@ function applyReportPlugins(report, plugins) {
993
1037
  }
994
1038
 
995
1039
  // src/validation/engine.ts
996
- var EMAIL_RE = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
997
- var SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?(?:\+[0-9A-Za-z-.]+)?$/;
1040
+ var EMAIL_RE = /^[^@\s]+@[^@\s.]+(?:\.[^@\s.]+)+$/;
1041
+ var SEMVER_RE = /^\d+\.\d+\.\d+(?:-[\w.-]+)?(?:\+[\w.-]+)?$/;
998
1042
  function detectReceivedType(value) {
999
1043
  const normalized = value.trim();
1000
1044
  if (normalized.length === 0) return "unknown";
@@ -1014,65 +1058,72 @@ function detectReceivedType(value) {
1014
1058
  }
1015
1059
  return "string";
1016
1060
  }
1017
- function validateValueAgainstExpected(expected, rawValue) {
1018
- const normalized = rawValue.trim();
1019
- const receivedType = detectReceivedType(normalized);
1020
- if (expected.type === "unknown") return { isValid: true, receivedType };
1021
- if (expected.type === "string") return { isValid: true, receivedType };
1022
- if (expected.type === "number") {
1023
- const parsed = Number(normalized);
1024
- if (Number.isNaN(parsed) || !Number.isFinite(parsed)) {
1025
- return { isValid: false, receivedType, issueType: "invalid_type" };
1026
- }
1027
- if (expected.min !== void 0 && parsed < expected.min) {
1028
- return { isValid: false, receivedType, issueType: "invalid_value" };
1029
- }
1030
- if (expected.max !== void 0 && parsed > expected.max) {
1031
- return { isValid: false, receivedType, issueType: "invalid_value" };
1032
- }
1033
- return { isValid: true, receivedType };
1061
+ function validateNumber(expected, normalized, receivedType) {
1062
+ const parsed = Number(normalized);
1063
+ if (Number.isNaN(parsed) || !Number.isFinite(parsed)) {
1064
+ return { isValid: false, receivedType, issueType: "invalid_type" };
1034
1065
  }
1035
- if (expected.type === "boolean") {
1036
- if (!["true", "false", "1", "0", "yes", "no"].includes(normalized.toLowerCase())) {
1037
- return { isValid: false, receivedType, issueType: "invalid_type" };
1038
- }
1039
- return { isValid: true, receivedType };
1066
+ if (expected.min !== void 0 && parsed < expected.min) {
1067
+ return { isValid: false, receivedType, issueType: "invalid_value" };
1040
1068
  }
1041
- if (expected.type === "enum") {
1042
- if (!expected.values.includes(normalized)) {
1043
- return { isValid: false, receivedType, issueType: "invalid_value" };
1044
- }
1045
- return { isValid: true, receivedType };
1069
+ if (expected.max !== void 0 && parsed > expected.max) {
1070
+ return { isValid: false, receivedType, issueType: "invalid_value" };
1046
1071
  }
1047
- if (expected.type === "url") {
1048
- try {
1049
- const value = new URL(normalized);
1050
- if (value.protocol.length === 0)
1051
- return { isValid: false, receivedType, issueType: "invalid_type" };
1052
- return { isValid: true, receivedType };
1053
- } catch {
1054
- return { isValid: false, receivedType, issueType: "invalid_type" };
1055
- }
1072
+ return { isValid: true, receivedType };
1073
+ }
1074
+ function validateBoolean(normalized, receivedType) {
1075
+ if (!["true", "false", "1", "0", "yes", "no"].includes(normalized.toLowerCase())) {
1076
+ return { isValid: false, receivedType, issueType: "invalid_type" };
1056
1077
  }
1057
- if (expected.type === "email") {
1058
- if (!EMAIL_RE.test(normalized))
1078
+ return { isValid: true, receivedType };
1079
+ }
1080
+ function validateEnum(expected, normalized, receivedType) {
1081
+ if (!expected.values.includes(normalized)) {
1082
+ return { isValid: false, receivedType, issueType: "invalid_value" };
1083
+ }
1084
+ return { isValid: true, receivedType };
1085
+ }
1086
+ function validateUrl(normalized, receivedType) {
1087
+ try {
1088
+ const value = new URL(normalized);
1089
+ if (value.protocol.length === 0)
1059
1090
  return { isValid: false, receivedType, issueType: "invalid_type" };
1060
1091
  return { isValid: true, receivedType };
1092
+ } catch {
1093
+ return { isValid: false, receivedType, issueType: "invalid_type" };
1061
1094
  }
1062
- if (expected.type === "json") {
1063
- try {
1064
- const parsed = JSON.parse(normalized);
1065
- if (typeof parsed === "object" && parsed !== null) return { isValid: true, receivedType };
1066
- return { isValid: false, receivedType, issueType: "invalid_type" };
1067
- } catch {
1068
- return { isValid: false, receivedType, issueType: "invalid_type" };
1069
- }
1095
+ }
1096
+ function validateEmail(normalized, receivedType) {
1097
+ if (!EMAIL_RE.test(normalized))
1098
+ return { isValid: false, receivedType, issueType: "invalid_type" };
1099
+ return { isValid: true, receivedType };
1100
+ }
1101
+ function validateJson(normalized, receivedType) {
1102
+ try {
1103
+ const parsed = JSON.parse(normalized);
1104
+ if (typeof parsed === "object" && parsed !== null) return { isValid: true, receivedType };
1105
+ return { isValid: false, receivedType, issueType: "invalid_type" };
1106
+ } catch {
1107
+ return { isValid: false, receivedType, issueType: "invalid_type" };
1070
1108
  }
1071
- if (expected.type === "semver") {
1072
- if (!SEMVER_RE.test(normalized))
1073
- return { isValid: false, receivedType, issueType: "invalid_value" };
1109
+ }
1110
+ function validateSemver(normalized, receivedType) {
1111
+ if (!SEMVER_RE.test(normalized))
1112
+ return { isValid: false, receivedType, issueType: "invalid_value" };
1113
+ return { isValid: true, receivedType };
1114
+ }
1115
+ function validateValueAgainstExpected(expected, rawValue) {
1116
+ const normalized = rawValue.trim();
1117
+ const receivedType = detectReceivedType(normalized);
1118
+ if (expected.type === "unknown" || expected.type === "string")
1074
1119
  return { isValid: true, receivedType };
1075
- }
1120
+ if (expected.type === "number") return validateNumber(expected, normalized, receivedType);
1121
+ if (expected.type === "boolean") return validateBoolean(normalized, receivedType);
1122
+ if (expected.type === "enum") return validateEnum(expected, normalized, receivedType);
1123
+ if (expected.type === "url") return validateUrl(normalized, receivedType);
1124
+ if (expected.type === "email") return validateEmail(normalized, receivedType);
1125
+ if (expected.type === "json") return validateJson(normalized, receivedType);
1126
+ if (expected.type === "semver") return validateSemver(normalized, receivedType);
1076
1127
  return { isValid: true, receivedType };
1077
1128
  }
1078
1129
  function toIssueCode(issueType) {
@@ -1144,57 +1195,62 @@ function buildReport(env, issues, recommendations) {
1144
1195
  function isClientSecret(variable, key) {
1145
1196
  return variable.secret === true && (variable.clientSide || key.startsWith("NEXT_PUBLIC_"));
1146
1197
  }
1198
+ function checkContractVariable(key, variable, context) {
1199
+ const { options, issues } = context;
1200
+ const value = options.values[key];
1201
+ const hasValue = value !== void 0 && value.trim().length > 0;
1202
+ if (variable.required && !hasValue) {
1203
+ issues.push(
1204
+ createIssue({
1205
+ type: "missing",
1206
+ severity: "error",
1207
+ key,
1208
+ environment: options.environment,
1209
+ message: `Required variable ${key} is missing.`,
1210
+ debugValues: options.debugValues,
1211
+ expected: variable.expected
1212
+ })
1213
+ );
1214
+ return;
1215
+ }
1216
+ if (!hasValue) return;
1217
+ const validation = validateValueAgainstExpected(variable.expected, value);
1218
+ if (!validation.isValid) {
1219
+ const message = validation.issueType === "invalid_type" ? `Variable ${key} has invalid type.` : `Variable ${key} has invalid value.`;
1220
+ issues.push(
1221
+ createIssue({
1222
+ type: validation.issueType,
1223
+ severity: "error",
1224
+ key,
1225
+ environment: options.environment,
1226
+ message,
1227
+ value,
1228
+ debugValues: options.debugValues,
1229
+ expected: variable.expected,
1230
+ receivedType: validation.receivedType
1231
+ })
1232
+ );
1233
+ }
1234
+ if (isClientSecret(variable, key)) {
1235
+ issues.push(
1236
+ createIssue({
1237
+ type: "secret_exposed",
1238
+ severity: "error",
1239
+ key,
1240
+ environment: options.environment,
1241
+ message: `Secret variable ${key} is marked as client-side.`,
1242
+ value,
1243
+ debugValues: options.debugValues,
1244
+ expected: variable.expected
1245
+ })
1246
+ );
1247
+ }
1248
+ }
1147
1249
  function validateAgainstContract(options) {
1148
1250
  const issues = [];
1149
1251
  const contractKeys = new Set(Object.keys(options.contract.variables));
1150
1252
  for (const [key, variable] of Object.entries(options.contract.variables)) {
1151
- const value = options.values[key];
1152
- const hasValue = value !== void 0 && value.trim().length > 0;
1153
- if (variable.required && !hasValue) {
1154
- issues.push(
1155
- createIssue({
1156
- type: "missing",
1157
- severity: "error",
1158
- key,
1159
- environment: options.environment,
1160
- message: `Required variable ${key} is missing.`,
1161
- debugValues: options.debugValues,
1162
- expected: variable.expected
1163
- })
1164
- );
1165
- continue;
1166
- }
1167
- if (!hasValue) continue;
1168
- const validation = validateValueAgainstExpected(variable.expected, value);
1169
- if (!validation.isValid) {
1170
- issues.push(
1171
- createIssue({
1172
- type: validation.issueType,
1173
- severity: "error",
1174
- key,
1175
- environment: options.environment,
1176
- message: validation.issueType === "invalid_type" ? `Variable ${key} has invalid type.` : `Variable ${key} has invalid value.`,
1177
- value,
1178
- debugValues: options.debugValues,
1179
- expected: variable.expected,
1180
- receivedType: validation.receivedType
1181
- })
1182
- );
1183
- }
1184
- if (isClientSecret(variable, key)) {
1185
- issues.push(
1186
- createIssue({
1187
- type: "secret_exposed",
1188
- severity: "error",
1189
- key,
1190
- environment: options.environment,
1191
- message: `Secret variable ${key} is marked as client-side.`,
1192
- value,
1193
- debugValues: options.debugValues,
1194
- expected: variable.expected
1195
- })
1196
- );
1197
- }
1253
+ checkContractVariable(key, variable, { options, issues });
1198
1254
  }
1199
1255
  for (const [key, value] of Object.entries(options.values)) {
1200
1256
  if (contractKeys.has(key)) continue;
@@ -1222,6 +1278,97 @@ function collectUnionKeys(contract, sources) {
1222
1278
  }
1223
1279
  return union;
1224
1280
  }
1281
+ function diffMissingEntries(key, missing, context) {
1282
+ const { variable, options, issues } = context;
1283
+ for (const entry of missing) {
1284
+ issues.push(
1285
+ createIssue({
1286
+ type: "missing",
1287
+ severity: "error",
1288
+ key,
1289
+ environment: entry.sourceName,
1290
+ message: `Variable ${key} is missing in ${entry.sourceName}.`,
1291
+ debugValues: options.debugValues,
1292
+ ...variable !== void 0 && { expected: variable.expected }
1293
+ })
1294
+ );
1295
+ }
1296
+ }
1297
+ function diffTypeConflicts(key, present, context) {
1298
+ const { variable, options, issues } = context;
1299
+ const typeBySource = /* @__PURE__ */ new Map();
1300
+ for (const entry of present) {
1301
+ typeBySource.set(entry.sourceName, detectReceivedType(entry.value ?? ""));
1302
+ }
1303
+ if (new Set(typeBySource.values()).size <= 1) return;
1304
+ for (const [sourceName, detectedType] of typeBySource.entries()) {
1305
+ issues.push(
1306
+ createIssue({
1307
+ type: "conflict",
1308
+ severity: "error",
1309
+ key,
1310
+ environment: sourceName,
1311
+ message: `Variable ${key} has conflicting inferred type across environments.`,
1312
+ debugValues: options.debugValues,
1313
+ receivedType: detectedType,
1314
+ ...options.sources[sourceName]?.[key] !== void 0 && {
1315
+ value: options.sources[sourceName]?.[key]
1316
+ },
1317
+ ...variable !== void 0 && { expected: variable.expected }
1318
+ })
1319
+ );
1320
+ }
1321
+ }
1322
+ function diffPresentEntry(key, entry, context) {
1323
+ const { variable, options, issues } = context;
1324
+ if (entry.value === void 0) return;
1325
+ if (variable === void 0) {
1326
+ const severity = options.strict ? "error" : "warning";
1327
+ issues.push(
1328
+ createIssue({
1329
+ type: "extra",
1330
+ severity,
1331
+ key,
1332
+ environment: entry.sourceName,
1333
+ message: `Variable ${key} is not defined in the contract.`,
1334
+ value: entry.value,
1335
+ debugValues: options.debugValues
1336
+ })
1337
+ );
1338
+ return;
1339
+ }
1340
+ const validation = validateValueAgainstExpected(variable.expected, entry.value);
1341
+ if (!validation.isValid) {
1342
+ const message = validation.issueType === "invalid_type" ? `Variable ${key} has invalid type in ${entry.sourceName}.` : `Variable ${key} has invalid value in ${entry.sourceName}.`;
1343
+ issues.push(
1344
+ createIssue({
1345
+ type: validation.issueType,
1346
+ severity: "error",
1347
+ key,
1348
+ environment: entry.sourceName,
1349
+ message,
1350
+ value: entry.value,
1351
+ debugValues: options.debugValues,
1352
+ expected: variable.expected,
1353
+ receivedType: validation.receivedType
1354
+ })
1355
+ );
1356
+ }
1357
+ if (isClientSecret(variable, key)) {
1358
+ issues.push(
1359
+ createIssue({
1360
+ type: "secret_exposed",
1361
+ severity: "error",
1362
+ key,
1363
+ environment: entry.sourceName,
1364
+ message: `Secret variable ${key} is marked as client-side.`,
1365
+ value: entry.value,
1366
+ debugValues: options.debugValues,
1367
+ expected: variable.expected
1368
+ })
1369
+ );
1370
+ }
1371
+ }
1225
1372
  function diffEnvironmentSources(options) {
1226
1373
  const issues = [];
1227
1374
  const sourceNames = Object.keys(options.sources);
@@ -1254,92 +1401,13 @@ function diffEnvironmentSources(options) {
1254
1401
  }
1255
1402
  continue;
1256
1403
  }
1404
+ const ctx = { variable, options, issues };
1257
1405
  if (present.length > 0) {
1258
- for (const entry of missing) {
1259
- issues.push(
1260
- createIssue({
1261
- type: "missing",
1262
- severity: "error",
1263
- key,
1264
- environment: entry.sourceName,
1265
- message: `Variable ${key} is missing in ${entry.sourceName}.`,
1266
- debugValues: options.debugValues,
1267
- ...variable !== void 0 && { expected: variable.expected }
1268
- })
1269
- );
1270
- }
1406
+ diffMissingEntries(key, missing, ctx);
1271
1407
  }
1272
- const typeBySource = /* @__PURE__ */ new Map();
1408
+ diffTypeConflicts(key, present, ctx);
1273
1409
  for (const entry of present) {
1274
- const detected = detectReceivedType(entry.value ?? "");
1275
- typeBySource.set(entry.sourceName, detected);
1276
- }
1277
- if (new Set(typeBySource.values()).size > 1) {
1278
- for (const [sourceName, detectedType] of typeBySource.entries()) {
1279
- issues.push(
1280
- createIssue({
1281
- type: "conflict",
1282
- severity: "error",
1283
- key,
1284
- environment: sourceName,
1285
- message: `Variable ${key} has conflicting inferred type across environments.`,
1286
- debugValues: options.debugValues,
1287
- receivedType: detectedType,
1288
- ...options.sources[sourceName]?.[key] !== void 0 && {
1289
- value: options.sources[sourceName]?.[key]
1290
- },
1291
- ...variable !== void 0 && { expected: variable.expected }
1292
- })
1293
- );
1294
- }
1295
- }
1296
- for (const entry of present) {
1297
- if (entry.value === void 0) continue;
1298
- if (variable === void 0) {
1299
- const severity = options.strict ? "error" : "warning";
1300
- issues.push(
1301
- createIssue({
1302
- type: "extra",
1303
- severity,
1304
- key,
1305
- environment: entry.sourceName,
1306
- message: `Variable ${key} is not defined in the contract.`,
1307
- value: entry.value,
1308
- debugValues: options.debugValues
1309
- })
1310
- );
1311
- continue;
1312
- }
1313
- const validation = validateValueAgainstExpected(variable.expected, entry.value);
1314
- if (!validation.isValid) {
1315
- issues.push(
1316
- createIssue({
1317
- type: validation.issueType,
1318
- severity: "error",
1319
- key,
1320
- environment: entry.sourceName,
1321
- message: validation.issueType === "invalid_type" ? `Variable ${key} has invalid type in ${entry.sourceName}.` : `Variable ${key} has invalid value in ${entry.sourceName}.`,
1322
- value: entry.value,
1323
- debugValues: options.debugValues,
1324
- expected: variable.expected,
1325
- receivedType: validation.receivedType
1326
- })
1327
- );
1328
- }
1329
- if (isClientSecret(variable, key)) {
1330
- issues.push(
1331
- createIssue({
1332
- type: "secret_exposed",
1333
- severity: "error",
1334
- key,
1335
- environment: entry.sourceName,
1336
- message: `Secret variable ${key} is marked as client-side.`,
1337
- value: entry.value,
1338
- debugValues: options.debugValues,
1339
- expected: variable.expected
1340
- })
1341
- );
1342
- }
1410
+ diffPresentEntry(key, entry, ctx);
1343
1411
  }
1344
1412
  }
1345
1413
  return buildReport("diff", issues);
@@ -1392,7 +1460,7 @@ function parseEnvSourceContent(content) {
1392
1460
  for (const line of lines) {
1393
1461
  const trimmed = line.trim();
1394
1462
  if (trimmed.length === 0 || trimmed.startsWith("#")) continue;
1395
- const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.exec(trimmed);
1463
+ const match = /^(?:export\s+)?([A-Za-z_]\w*)=(.*)$/.exec(trimmed);
1396
1464
  if (match === null) continue;
1397
1465
  const key = match[1] ?? "";
1398
1466
  const rawValue = match[2] ?? "";
@@ -1405,11 +1473,11 @@ async function loadEnvSource(options) {
1405
1473
  try {
1406
1474
  const content = await readFile3(resolvedPath, "utf8");
1407
1475
  return parseEnvSourceContent(content);
1408
- } catch (errorValue) {
1409
- if (options.allowMissing === true && errorValue instanceof Error && "code" in errorValue && errorValue.code === "ENOENT") {
1476
+ } catch (error_) {
1477
+ if (options.allowMissing === true && error_ instanceof Error && "code" in error_ && error_.code === "ENOENT") {
1410
1478
  return {};
1411
1479
  }
1412
- throw errorValue;
1480
+ throw error_;
1413
1481
  }
1414
1482
  }
1415
1483
 
@@ -1423,8 +1491,8 @@ function toJsonString(report, mode) {
1423
1491
  `;
1424
1492
  }
1425
1493
  function formatIssue(issue) {
1426
- const expected = issue.expected !== void 0 ? ` expected=${issue.expected.type}` : "";
1427
- const received = issue.receivedType !== void 0 ? ` received=${issue.receivedType}` : "";
1494
+ const expected = issue.expected === void 0 ? "" : ` expected=${issue.expected.type}`;
1495
+ const received = issue.receivedType === void 0 ? "" : ` received=${issue.receivedType}`;
1428
1496
  return `${issue.severity.toUpperCase()} [${issue.code}] ${issue.environment}:${issue.key} ${issue.message}${expected}${received}`;
1429
1497
  }
1430
1498
  function formatHumanReport(report) {
@@ -1433,15 +1501,13 @@ function formatHumanReport(report) {
1433
1501
  `Status: ${report.status.toUpperCase()} (errors=${report.summary.errors}, warnings=${report.summary.warnings}, total=${report.summary.total})`
1434
1502
  );
1435
1503
  if (report.issues.length > 0) {
1436
- lines.push("");
1437
- lines.push("Issues:");
1504
+ lines.push("", "Issues:");
1438
1505
  for (const issue of report.issues) {
1439
1506
  lines.push(`- ${formatIssue(issue)}`);
1440
1507
  }
1441
1508
  }
1442
1509
  if (report.recommendations !== void 0 && report.recommendations.length > 0) {
1443
- lines.push("");
1444
- lines.push("Recommendations:");
1510
+ lines.push("", "Recommendations:");
1445
1511
  for (const recommendation of report.recommendations) {
1446
1512
  lines.push(`- ${recommendation}`);
1447
1513
  }
@@ -1486,7 +1552,11 @@ var HELP_TEXT = {
1486
1552
  " --cloud-file <path> Cloud snapshot JSON file",
1487
1553
  " --plugin <path> Plugin module path (repeatable)",
1488
1554
  " -c, --config <path> Config file path",
1489
- " -h, --help Show this help"
1555
+ " -h, --help Show this help",
1556
+ "",
1557
+ "Exit codes:",
1558
+ " 0 All checks passed (status: ok or warn)",
1559
+ " 1 One or more checks failed (status: fail) or invalid usage"
1490
1560
  ].join("\n"),
1491
1561
  diff: [
1492
1562
  "Usage: env-typegen diff [options]",
@@ -1505,7 +1575,11 @@ var HELP_TEXT = {
1505
1575
  " --cloud-file <path> Cloud snapshot JSON file added to diff sources",
1506
1576
  " --plugin <path> Plugin module path (repeatable)",
1507
1577
  " -c, --config <path> Config file path",
1508
- " -h, --help Show this help"
1578
+ " -h, --help Show this help",
1579
+ "",
1580
+ "Exit codes:",
1581
+ " 0 All checks passed (status: ok or warn)",
1582
+ " 1 One or more checks failed (status: fail) or invalid usage"
1509
1583
  ].join("\n"),
1510
1584
  doctor: [
1511
1585
  "Usage: env-typegen doctor [options]",
@@ -1525,7 +1599,11 @@ var HELP_TEXT = {
1525
1599
  " --cloud-file <path> Cloud snapshot JSON file",
1526
1600
  " --plugin <path> Plugin module path (repeatable)",
1527
1601
  " -c, --config <path> Config file path",
1528
- " -h, --help Show this help"
1602
+ " -h, --help Show this help",
1603
+ "",
1604
+ "Exit codes:",
1605
+ " 0 All checks passed (status: ok or warn)",
1606
+ " 1 One or more checks failed (status: fail) or invalid usage"
1529
1607
  ].join("\n")
1530
1608
  };
1531
1609
  function resolveConfigRelative(value, configDir) {
@@ -1612,7 +1690,10 @@ function parseValidationArgs(argv) {
1612
1690
  }
1613
1691
  });
1614
1692
  const castValues = values;
1615
- const jsonMode = castValues.json === true ? assignedMode === "off" ? "compact" : assignedMode : "off";
1693
+ let jsonMode = "off";
1694
+ if (castValues.json === true) {
1695
+ jsonMode = assignedMode === "off" ? "compact" : assignedMode;
1696
+ }
1616
1697
  return { values: castValues, jsonMode };
1617
1698
  }
1618
1699
  function resolveStrict(values, fileConfig) {
@@ -1685,12 +1766,12 @@ async function runCheckCommand(args) {
1685
1766
  const provider = context.cloudProvider;
1686
1767
  let environment = args.values.env?.[0] ?? ".env";
1687
1768
  let sourceValues;
1688
- if (provider !== void 0) {
1769
+ if (provider === void 0) {
1770
+ sourceValues = await loadEnvSource({ filePath: environment, allowMissing: true });
1771
+ } else {
1689
1772
  const cloudFile = context.cloudFile ?? `${provider}.env.json`;
1690
1773
  sourceValues = await loadCloudSource({ provider, filePath: cloudFile });
1691
1774
  environment = `cloud:${provider}`;
1692
- } else {
1693
- sourceValues = await loadEnvSource({ filePath: environment, allowMissing: true });
1694
1775
  }
1695
1776
  sourceValues = applySourcePlugins({ environment, values: sourceValues }, context.plugins);
1696
1777
  const report = applyReportPlugins(
@@ -1822,8 +1903,8 @@ async function runValidationCommand(params) {
1822
1903
  }
1823
1904
 
1824
1905
  // src/watch.ts
1825
- import path12 from "path";
1826
1906
  import { watch } from "chokidar";
1907
+ import path12 from "path";
1827
1908
  function debounce(fn, delay) {
1828
1909
  let timer;
1829
1910
  return (...args) => {
@@ -1858,6 +1939,9 @@ function startWatch({ inputPath, runOptions, cwd = process.cwd() }) {
1858
1939
  if (reloaded.inferenceRules !== void 0) {
1859
1940
  runOptions.inferenceRules = reloaded.inferenceRules;
1860
1941
  }
1942
+ if (reloaded.output !== void 0) {
1943
+ runOptions.output = reloaded.output;
1944
+ }
1861
1945
  }
1862
1946
  void runGenerate(runOptions).catch((err) => {
1863
1947
  const message = err instanceof Error ? err.message : JSON.stringify(err);
@@ -1875,7 +1959,7 @@ function startWatch({ inputPath, runOptions, cwd = process.cwd() }) {
1875
1959
  const configPaths = CONFIG_FILE_NAMES.map((name) => path12.resolve(cwd, name));
1876
1960
  const configWatcher = watch(configPaths, { persistent: true, ignoreInitial: true });
1877
1961
  for (const event of ["add", "change"]) {
1878
- configWatcher.on(event, (eventPath) => void handleConfigChange(eventPath));
1962
+ configWatcher.on(event, (eventPath) => handleConfigChange(eventPath));
1879
1963
  }
1880
1964
  process.on("SIGINT", () => {
1881
1965
  void Promise.all([inputWatcher.close(), configWatcher.close()]).then(() => {
@@ -1899,7 +1983,9 @@ var HELP_TEXT2 = [
1899
1983
  "",
1900
1984
  "Options:",
1901
1985
  " -i, --input <path> Path to .env.example file(s). May be specified multiple times.",
1902
- " -o, --output <path> Output file path (default: env.generated.ts)",
1986
+ " -o, --output <path> Output base path (default: env.generated.ts).",
1987
+ " With multiple generators, suffixes are appended:",
1988
+ " env.generated.typescript.ts, .zod.ts, .t3.ts, .declaration.d.ts",
1903
1989
  " -f, --format <name> Generator format: ts|zod|t3|declaration",
1904
1990
  " May be specified multiple times.",
1905
1991
  " -g, --generator <name> Backward-compatible alias for --format",
@@ -1910,7 +1996,16 @@ var HELP_TEXT2 = [
1910
1996
  " -w, --watch Watch for changes and regenerate",
1911
1997
  " -c, --config <path> Path to config file",
1912
1998
  " -v, --version Print version",
1913
- " -h, --help Show this help"
1999
+ " -h, --help Show this help",
2000
+ "",
2001
+ "Config file:",
2002
+ " Auto-discovered in order: env-typegen.config.mjs \u2192 .js \u2192 .ts (in cwd)",
2003
+ " CLI flags always override config file values.",
2004
+ " Use defineConfig() from @xlameiro/env-typegen for IDE autocompletion.",
2005
+ "",
2006
+ "Exit codes:",
2007
+ " 0 Success \u2014 files generated without errors",
2008
+ " 1 Error \u2014 invalid flags or generation failed"
1914
2009
  ].join("\n");
1915
2010
  var VALIDATION_SUBCOMMANDS = /* @__PURE__ */ new Set(["check", "diff", "doctor"]);
1916
2011
  var FORMAT_TO_GENERATOR = {