@xlameiro/env-typegen 0.1.3 → 0.1.5

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/index.js CHANGED
@@ -3,7 +3,7 @@ import path6 from 'path';
3
3
  import { pathToFileURL } from 'url';
4
4
  import { readFile, mkdir, writeFile } from 'fs/promises';
5
5
  import { format } from 'prettier';
6
- import { green, red, yellow } from 'picocolors';
6
+ import pc from 'picocolors';
7
7
  import { parseArgs } from 'util';
8
8
 
9
9
  // src/parser/comment-parser.ts
@@ -143,7 +143,9 @@ var inferenceRules = [
143
143
  {
144
144
  id: "P8_numeric_literal",
145
145
  priority: 8,
146
- match: (_key, value) => /^\d+(\.\d+)?$/.test(value),
146
+ // Non-capturing group with \d keeps the dot/digit boundary unambiguous,
147
+ // eliminating super-linear backtracking (ReDoS-safe).
148
+ match: (_key, value) => /^\d+(?:\.\d+)?$/.test(value),
147
149
  type: "number"
148
150
  },
149
151
  {
@@ -161,7 +163,9 @@ var inferenceRules = [
161
163
  {
162
164
  id: "P11_email_literal",
163
165
  priority: 11,
164
- match: (_key, value) => /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value),
166
+ // Dots are excluded from each domain-segment character class so that the
167
+ // literal \. separators are unambiguous, preventing super-linear backtracking.
168
+ match: (_key, value) => /^[^@\s]+@[^@\s.]+(?:\.[^@\s.]+)+$/.test(value),
165
169
  type: "email"
166
170
  },
167
171
  {
@@ -195,7 +199,50 @@ function inferTypesFromParsedVars(parsed, options) {
195
199
  return parsed.vars.map((item) => inferType(item.key, item.rawValue, options));
196
200
  }
197
201
  var ENV_VAR_RE = /^([A-Z_][A-Z0-9_]*)=(.*)$/;
198
- var SECTION_HEADER_RE = /^#\s+[-=]{3,}\s+(.+?)\s+[-=]{3,}\s*$/;
202
+ var SECTION_HEADER_RE = /^#\s+[-=]{3,}\s+(\S+(?:\s+\S+)*)\s+[-=]{3,}\s*$/;
203
+ function buildParsedVar(params, commentBlock, options) {
204
+ const annotations = parseCommentBlock(commentBlock);
205
+ const extraRules = options?.inferenceRules;
206
+ const inferredType = inferType(
207
+ params.key,
208
+ params.rawValue,
209
+ ...extraRules === void 0 ? [] : [{ extraRules }]
210
+ );
211
+ const isRequired = params.rawValue.length > 0 || annotations.isRequired;
212
+ const isOptional = params.rawValue.length === 0 && !annotations.isRequired;
213
+ const isClientSide = params.key.startsWith("NEXT_PUBLIC_");
214
+ const parsedVar = {
215
+ key: params.key,
216
+ rawValue: params.rawValue,
217
+ inferredType,
218
+ isRequired,
219
+ isOptional,
220
+ isClientSide,
221
+ lineNumber: params.lineNumber
222
+ };
223
+ if (annotations.annotatedType !== void 0) {
224
+ parsedVar.annotatedType = annotations.annotatedType;
225
+ }
226
+ if (annotations.description !== void 0) {
227
+ parsedVar.description = annotations.description;
228
+ }
229
+ if (params.currentGroup !== void 0) {
230
+ parsedVar.group = params.currentGroup;
231
+ }
232
+ if (annotations.enumValues !== void 0) {
233
+ parsedVar.enumValues = annotations.enumValues;
234
+ }
235
+ if (annotations.constraints !== void 0) {
236
+ parsedVar.constraints = annotations.constraints;
237
+ }
238
+ if (annotations.runtime !== void 0) {
239
+ parsedVar.runtime = annotations.runtime;
240
+ }
241
+ if (annotations.isSecret !== void 0) {
242
+ parsedVar.isSecret = annotations.isSecret;
243
+ }
244
+ return parsedVar;
245
+ }
199
246
  function parseEnvFileContent(content, filePath, options) {
200
247
  const lines = content.split("\n");
201
248
  const vars = [];
@@ -225,53 +272,18 @@ function parseEnvFileContent(content, filePath, options) {
225
272
  continue;
226
273
  }
227
274
  const envMatch = ENV_VAR_RE.exec(trimmed);
228
- if (envMatch !== null) {
229
- const key = envMatch[1] ?? "";
230
- const rawValue = envMatch[2] ?? "";
231
- const annotations = parseCommentBlock(commentBlock);
232
- const inferredType = inferType(
233
- key,
234
- rawValue,
235
- ...options?.inferenceRules !== void 0 ? [{ extraRules: options.inferenceRules }] : []
236
- );
237
- const isRequired = rawValue.length > 0 || annotations.isRequired;
238
- const isOptional = rawValue.length === 0 && !annotations.isRequired;
239
- const isClientSide = key.startsWith("NEXT_PUBLIC_");
240
- const parsedVar = {
241
- key,
242
- rawValue,
243
- inferredType,
244
- isRequired,
245
- isOptional,
246
- isClientSide,
247
- lineNumber
248
- };
249
- if (annotations.annotatedType !== void 0) {
250
- parsedVar.annotatedType = annotations.annotatedType;
251
- }
252
- if (annotations.description !== void 0) {
253
- parsedVar.description = annotations.description;
254
- }
255
- if (currentGroup !== void 0) {
256
- parsedVar.group = currentGroup;
257
- }
258
- if (annotations.enumValues !== void 0) {
259
- parsedVar.enumValues = annotations.enumValues;
260
- }
261
- if (annotations.constraints !== void 0) {
262
- parsedVar.constraints = annotations.constraints;
263
- }
264
- if (annotations.runtime !== void 0) {
265
- parsedVar.runtime = annotations.runtime;
266
- }
267
- if (annotations.isSecret !== void 0) {
268
- parsedVar.isSecret = annotations.isSecret;
269
- }
270
- vars.push(parsedVar);
271
- commentBlock = [];
272
- } else {
275
+ if (envMatch === null) {
273
276
  commentBlock = [];
277
+ continue;
274
278
  }
279
+ vars.push(
280
+ buildParsedVar(
281
+ { key: envMatch[1] ?? "", rawValue: envMatch[2] ?? "", lineNumber, currentGroup },
282
+ commentBlock,
283
+ options
284
+ )
285
+ );
286
+ commentBlock = [];
275
287
  }
276
288
  return { filePath, vars, groups };
277
289
  }
@@ -290,12 +302,14 @@ function generateTypeScriptTypes(parsed) {
290
302
  const fileName = path6.basename(parsed.filePath);
291
303
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
292
304
  const lines = [];
293
- lines.push("// Generated by env-typegen \u2014 do not edit manually");
294
- lines.push(`// Source: ${fileName}`);
295
- lines.push(`// Generated at: ${timestamp}`);
296
- lines.push("");
297
- lines.push("declare namespace NodeJS {");
298
- lines.push(" interface ProcessEnv {");
305
+ lines.push(
306
+ "// Generated by env-typegen \u2014 do not edit manually",
307
+ `// Source: ${fileName}`,
308
+ `// Generated at: ${timestamp}`,
309
+ "",
310
+ "declare namespace NodeJS {",
311
+ " interface ProcessEnv {"
312
+ );
299
313
  for (const variable of parsed.vars) {
300
314
  const effectiveType = variable.annotatedType ?? variable.inferredType;
301
315
  const optional = variable.isOptional ? "?" : "";
@@ -310,10 +324,7 @@ function generateTypeScriptTypes(parsed) {
310
324
  }
311
325
  lines.push(propLine);
312
326
  }
313
- lines.push(" }");
314
- lines.push("}");
315
- lines.push("");
316
- lines.push("export type EnvVars = {");
327
+ lines.push(" }", "}", "", "export type EnvVars = {");
317
328
  for (const variable of parsed.vars) {
318
329
  const effectiveType = variable.annotatedType ?? variable.inferredType;
319
330
  const tsType = toTsType(effectiveType);
@@ -323,28 +334,34 @@ function generateTypeScriptTypes(parsed) {
323
334
  lines.push("};");
324
335
  if (hasClientVars) {
325
336
  const clientKeyUnion = clientVars.map((v) => `"${v.key}"`).join(" | ");
326
- lines.push("");
327
- lines.push(`export type ServerEnvVars = Omit<EnvVars, ${clientKeyUnion}>;`);
328
- lines.push(`export type ClientEnvVars = Pick<EnvVars, ${clientKeyUnion}>;`);
337
+ lines.push(
338
+ "",
339
+ `export type ServerEnvVars = Omit<EnvVars, ${clientKeyUnion}>;`,
340
+ `export type ClientEnvVars = Pick<EnvVars, ${clientKeyUnion}>;`
341
+ );
329
342
  }
330
343
  return lines.join("\n") + "\n";
331
344
  }
332
345
  function generateEnvValidation(parsed) {
333
346
  const required = parsed.vars.filter((v) => v.isRequired).map((v) => v.key);
334
347
  const lines = [];
335
- lines.push("// Generated by env-typegen \u2014 do not edit manually");
336
- lines.push("");
337
- lines.push("export function validateEnv(): void {");
348
+ lines.push(
349
+ "// Generated by env-typegen \u2014 do not edit manually",
350
+ "",
351
+ "export function validateEnv(): void {"
352
+ );
338
353
  if (required.length === 0) {
339
354
  lines.push(" // No required environment variables defined");
340
355
  } else {
341
356
  const keyList = required.map((k) => `"${k}"`).join(", ");
342
- lines.push(` const required = [${keyList}];`);
343
- lines.push(" for (const key of required) {");
344
- lines.push(" if (!process.env[key]) {");
345
- lines.push(" throw new Error(`Missing required environment variable: ${key}`);");
346
- lines.push(" }");
347
- lines.push(" }");
357
+ lines.push(
358
+ ` const required = [${keyList}];`,
359
+ " for (const key of required) {",
360
+ " if (!process.env[key]) {",
361
+ " throw new Error(`Missing required environment variable: ${key}`);",
362
+ " }",
363
+ " }"
364
+ );
348
365
  }
349
366
  lines.push("}");
350
367
  return lines.join("\n") + "\n";
@@ -362,37 +379,40 @@ function generateZodSchema(parsed) {
362
379
  const serverVars = parsed.vars.filter((v) => !v.isClientSide);
363
380
  const clientVars = parsed.vars.filter((v) => v.isClientSide);
364
381
  const lines = [];
365
- lines.push("// Generated by env-typegen \u2014 do not edit manually");
366
- lines.push('import { z } from "zod";');
367
- lines.push("");
368
- lines.push("export const serverEnvSchema = z.object({");
382
+ lines.push(
383
+ "// Generated by env-typegen \u2014 do not edit manually",
384
+ 'import { z } from "zod";',
385
+ "",
386
+ "export const serverEnvSchema = z.object({"
387
+ );
369
388
  for (const variable of serverVars) {
370
389
  const effectiveType = variable.annotatedType ?? variable.inferredType;
371
390
  const zodExpr = variable.isOptional ? `${toZodType(effectiveType)}.optional()` : toZodType(effectiveType);
372
391
  lines.push(` ${variable.key}: ${zodExpr},`);
373
392
  }
374
- lines.push("});");
375
- lines.push("");
376
- lines.push("export const clientEnvSchema = z.object({");
393
+ lines.push("});", "", "export const clientEnvSchema = z.object({");
377
394
  for (const variable of clientVars) {
378
395
  const effectiveType = variable.annotatedType ?? variable.inferredType;
379
396
  const zodExpr = variable.isOptional ? `${toZodType(effectiveType)}.optional()` : toZodType(effectiveType);
380
397
  lines.push(` ${variable.key}: ${zodExpr},`);
381
398
  }
382
- lines.push("});");
383
- lines.push("");
384
- lines.push("export const envSchema = serverEnvSchema.merge(clientEnvSchema);");
385
- lines.push("export type Env = z.infer<typeof envSchema>;");
399
+ lines.push(
400
+ "});",
401
+ "",
402
+ "export const envSchema = serverEnvSchema.merge(clientEnvSchema);",
403
+ "export type Env = z.infer<typeof envSchema>;"
404
+ );
386
405
  return lines.join("\n") + "\n";
387
406
  }
388
407
  function generateDeclaration(parsed) {
389
408
  const fileName = path6.basename(parsed.filePath);
390
- const lines = [];
391
- lines.push("// Generated by env-typegen \u2014 do not edit manually");
392
- lines.push(`// Source: ${fileName}`);
393
- lines.push("");
394
- lines.push("declare namespace NodeJS {");
395
- lines.push(" interface ProcessEnv {");
409
+ const lines = [
410
+ "// Generated by env-typegen \u2014 do not edit manually",
411
+ `// Source: ${fileName}`,
412
+ "",
413
+ "declare namespace NodeJS {",
414
+ " interface ProcessEnv {"
415
+ ];
396
416
  for (const variable of parsed.vars) {
397
417
  const effectiveType = variable.annotatedType ?? variable.inferredType;
398
418
  const optional = variable.isOptional ? "?" : "";
@@ -407,12 +427,14 @@ function generateDeclaration(parsed) {
407
427
  }
408
428
  lines.push(propLine);
409
429
  }
410
- lines.push(" }");
411
- lines.push("}");
430
+ lines.push(" }", "}");
412
431
  return lines.join("\n") + "\n";
413
432
  }
414
433
 
415
434
  // src/generators/t3-generator.ts
435
+ function escapeJsStringLiteral(value) {
436
+ return value.replaceAll("\\", String.raw`\\`).replaceAll('"', String.raw`\"`);
437
+ }
416
438
  function toT3ZodType(envVarType) {
417
439
  if (envVarType === "number") return "z.coerce.number()";
418
440
  if (envVarType === "boolean") return "z.coerce.boolean()";
@@ -420,51 +442,47 @@ function toT3ZodType(envVarType) {
420
442
  if (envVarType === "email") return "z.string().email()";
421
443
  return "z.string()";
422
444
  }
445
+ function buildZodExpr(variable) {
446
+ const effectiveType = variable.annotatedType ?? variable.inferredType;
447
+ let zodExpr = toT3ZodType(effectiveType);
448
+ if (variable.description !== void 0) {
449
+ zodExpr += `.describe("${escapeJsStringLiteral(variable.description)}")`;
450
+ }
451
+ if (variable.isOptional) {
452
+ zodExpr += ".optional()";
453
+ }
454
+ return zodExpr;
455
+ }
423
456
  function generateT3Env(parsed) {
424
457
  const serverVars = parsed.vars.filter((v) => !v.isClientSide);
425
458
  const clientVars = parsed.vars.filter((v) => v.isClientSide);
426
- const lines = [];
427
- lines.push("// Generated by env-typegen \u2014 do not edit manually");
428
- lines.push('import { createEnv } from "@t3-oss/env-nextjs";');
429
- lines.push('import { z } from "zod";');
430
- lines.push("");
431
- lines.push("export const env = createEnv({");
459
+ const lines = [
460
+ "// Generated by env-typegen \u2014 do not edit manually",
461
+ 'import { createEnv } from "@t3-oss/env-nextjs";',
462
+ 'import { z } from "zod";',
463
+ "",
464
+ "export const env = createEnv({"
465
+ ];
432
466
  if (serverVars.length > 0) {
433
- lines.push(" server: {");
434
- for (const variable of serverVars) {
435
- const effectiveType = variable.annotatedType ?? variable.inferredType;
436
- let zodExpr = toT3ZodType(effectiveType);
437
- if (variable.description !== void 0) {
438
- zodExpr += `.describe("${variable.description.replace(/"/g, '\\"')}")`;
439
- }
440
- if (variable.isOptional) {
441
- zodExpr += ".optional()";
442
- }
443
- lines.push(` ${variable.key}: ${zodExpr},`);
444
- }
445
- lines.push(" },");
467
+ lines.push(
468
+ " server: {",
469
+ ...serverVars.map((v) => ` ${v.key}: ${buildZodExpr(v)},`),
470
+ " },"
471
+ );
446
472
  }
447
473
  if (clientVars.length > 0) {
448
- lines.push(" client: {");
449
- for (const variable of clientVars) {
450
- const effectiveType = variable.annotatedType ?? variable.inferredType;
451
- let zodExpr = toT3ZodType(effectiveType);
452
- if (variable.description !== void 0) {
453
- zodExpr += `.describe("${variable.description.replace(/"/g, '\\"')}")`;
454
- }
455
- if (variable.isOptional) {
456
- zodExpr += ".optional()";
457
- }
458
- lines.push(` ${variable.key}: ${zodExpr},`);
459
- }
460
- lines.push(" },");
461
- }
462
- lines.push(" runtimeEnv: {");
463
- for (const variable of parsed.vars) {
464
- lines.push(` ${variable.key}: process.env.${variable.key},`);
474
+ lines.push(
475
+ " client: {",
476
+ ...clientVars.map((v) => ` ${v.key}: ${buildZodExpr(v)},`),
477
+ " },"
478
+ );
465
479
  }
466
- lines.push(" },");
467
- lines.push("});");
480
+ lines.push(
481
+ " runtimeEnv: {",
482
+ ...parsed.vars.map((v) => ` ${v.key}: process.env.${v.key},`),
483
+ " },",
484
+ "});"
485
+ );
468
486
  return lines.join("\n") + "\n";
469
487
  }
470
488
  var CONTRACT_FILE_NAMES = [
@@ -487,9 +505,9 @@ async function loadContract(cwd = process.cwd()) {
487
505
  return void 0;
488
506
  }
489
507
  var CONFIG_FILE_NAMES = [
490
- "env-typegen.config.ts",
491
508
  "env-typegen.config.mjs",
492
- "env-typegen.config.js"
509
+ "env-typegen.config.js",
510
+ "env-typegen.config.ts"
493
511
  ];
494
512
  function defineConfig(config) {
495
513
  return config;
@@ -498,6 +516,19 @@ async function loadConfig(cwd = process.cwd()) {
498
516
  for (const name of CONFIG_FILE_NAMES) {
499
517
  const filePath = path6.resolve(cwd, name);
500
518
  if (existsSync(filePath)) {
519
+ if (filePath.endsWith(".ts")) {
520
+ throw new Error(
521
+ `Config file "${name}" was found but TypeScript files cannot be loaded directly at runtime.
522
+ Rename it to "env-typegen.config.mjs" and use ESM export syntax:
523
+
524
+ // env-typegen.config.mjs
525
+ import { defineConfig } from "@xlameiro/env-typegen";
526
+ export default defineConfig({ input: ".env.example" });
527
+
528
+ Tip: keep env-typegen.config.ts for IDE autocompletion and create a sibling
529
+ env-typegen.config.mjs for runtime loading.`
530
+ );
531
+ }
501
532
  const fileUrl = pathToFileURL(filePath).href;
502
533
  const mod = await import(fileUrl);
503
534
  return mod.default;
@@ -506,7 +537,15 @@ async function loadConfig(cwd = process.cwd()) {
506
537
  return void 0;
507
538
  }
508
539
  async function readEnvFile(filePath) {
509
- return readFile(path6.resolve(filePath), "utf8");
540
+ const resolved = path6.resolve(filePath);
541
+ try {
542
+ return await readFile(resolved, "utf8");
543
+ } catch (err) {
544
+ if (err instanceof Error && err.code === "ENOENT") {
545
+ throw new Error(`File not found: ${filePath}`);
546
+ }
547
+ throw err;
548
+ }
510
549
  }
511
550
  async function writeOutput(filePath, content) {
512
551
  const resolved = path6.resolve(filePath);
@@ -524,6 +563,7 @@ async function formatOutput(content, parser = "typescript") {
524
563
  return content;
525
564
  }
526
565
  }
566
+ var { green, red, yellow } = pc;
527
567
  function log(message) {
528
568
  console.log(message);
529
569
  }
@@ -577,10 +617,6 @@ async function persistOutput(params) {
577
617
  }
578
618
  if (dryRun) {
579
619
  if (!silent) {
580
- if (!isSingle) {
581
- console.log(`// --- ${generator}: ${outputPath} ---`);
582
- }
583
- console.log(generated);
584
620
  success(`Dry run: would write ${outputPath}`);
585
621
  }
586
622
  return;
@@ -610,7 +646,7 @@ async function runGenerate(options) {
610
646
  const parsed = parseEnvFileContent(
611
647
  content,
612
648
  inputPath,
613
- inferenceRules2 !== void 0 ? { inferenceRules: inferenceRules2 } : void 0
649
+ inferenceRules2 === void 0 ? void 0 : { inferenceRules: inferenceRules2 }
614
650
  );
615
651
  for (const generator of generators) {
616
652
  let generated = buildOutput(generator, parsed);
@@ -938,8 +974,13 @@ function readEntryValue(entry, keys) {
938
974
  }
939
975
  return void 0;
940
976
  }
977
+ function getVercelEntries(value) {
978
+ if (Array.isArray(value)) return value;
979
+ if (isRecord(value) && Array.isArray(value.envs)) return value.envs;
980
+ return [];
981
+ }
941
982
  function parseVercelPayload(value) {
942
- const entries = Array.isArray(value) ? value : isRecord(value) && Array.isArray(value.envs) ? value.envs : [];
983
+ const entries = getVercelEntries(value);
943
984
  const result = {};
944
985
  for (const entry of entries) {
945
986
  if (!isRecord(entry)) continue;
@@ -950,8 +991,13 @@ function parseVercelPayload(value) {
950
991
  }
951
992
  return result;
952
993
  }
994
+ function getCloudflareEntries(value) {
995
+ if (Array.isArray(value)) return value;
996
+ if (isRecord(value) && Array.isArray(value.result)) return value.result;
997
+ return [];
998
+ }
953
999
  function parseCloudflarePayload(value) {
954
- const entries = Array.isArray(value) ? value : isRecord(value) && Array.isArray(value.result) ? value.result : [];
1000
+ const entries = getCloudflareEntries(value);
955
1001
  const result = {};
956
1002
  for (const entry of entries) {
957
1003
  if (!isRecord(entry)) continue;
@@ -969,7 +1015,7 @@ function parseAwsPayload(value) {
969
1015
  if (!isRecord(entry)) continue;
970
1016
  const name = readEntryValue(entry, ["Name", "name"]);
971
1017
  if (name === void 0) continue;
972
- const key = name.split("/").filter((part) => part.length > 0).pop() ?? name;
1018
+ const key = name.split("/").findLast((part) => part.length > 0) ?? name;
973
1019
  const envValue = readEntryValue(entry, ["Value", "value"]) ?? "";
974
1020
  result[key] = envValue;
975
1021
  }
@@ -982,7 +1028,15 @@ function parseProviderPayload(provider, value) {
982
1028
  }
983
1029
  async function loadCloudSource(options) {
984
1030
  const resolvedPath = path6.resolve(options.filePath);
985
- const raw = await readFile(resolvedPath, "utf8");
1031
+ let raw;
1032
+ try {
1033
+ raw = await readFile(resolvedPath, "utf8");
1034
+ } catch (err) {
1035
+ if (err instanceof Error && err.code === "ENOENT") {
1036
+ throw new Error(`File not found: ${options.filePath}`);
1037
+ }
1038
+ throw err;
1039
+ }
986
1040
  const parsed = JSON.parse(raw);
987
1041
  return parseProviderPayload(options.provider, parsed);
988
1042
  }
@@ -1109,7 +1163,7 @@ function findDefaultContractPath(cwd) {
1109
1163
  async function loadValidationContract(options) {
1110
1164
  const { fallbackExamplePath, contractPath, cwd = process.cwd() } = options;
1111
1165
  const discoveredContractPath = findDefaultContractPath(cwd);
1112
- const resolvedContractPath = contractPath !== void 0 ? path6.resolve(cwd, contractPath) : discoveredContractPath;
1166
+ const resolvedContractPath = contractPath === void 0 ? discoveredContractPath : path6.resolve(cwd, contractPath);
1113
1167
  if (resolvedContractPath !== void 0 && existsSync(resolvedContractPath)) {
1114
1168
  const moduleUrl = pathToFileURL(resolvedContractPath).href;
1115
1169
  const moduleValue = await import(moduleUrl);
@@ -1145,10 +1199,20 @@ function isPlugin(value) {
1145
1199
  }
1146
1200
  async function loadPluginFromPath(pluginPath, cwd) {
1147
1201
  const resolvedPath = path6.resolve(cwd, pluginPath);
1202
+ if (!existsSync(resolvedPath)) {
1203
+ throw new Error(`Plugin not found: ${pluginPath}`);
1204
+ }
1148
1205
  const moduleValue = await import(pathToFileURL(resolvedPath).href);
1149
1206
  const candidate = moduleValue.default ?? moduleValue.plugin;
1150
1207
  if (isPlugin(candidate)) return candidate;
1151
- throw new Error(`Invalid plugin at ${resolvedPath}. Expected a plugin object export.`);
1208
+ throw new Error(
1209
+ `Invalid plugin at ${resolvedPath}.
1210
+ Expected a default export matching:
1211
+ { name: string,
1212
+ transformSource?(ctx: { environment: string; values: Record<string, string> }): Record<string, string>,
1213
+ transformReport?(report: ValidationReport): ValidationReport,
1214
+ transformContract?(contract: EnvContract): EnvContract }`
1215
+ );
1152
1216
  }
1153
1217
  async function loadPlugins(options) {
1154
1218
  const cwd = options.cwd ?? process.cwd();
@@ -1193,8 +1257,8 @@ function applyReportPlugins(report, plugins) {
1193
1257
  }
1194
1258
 
1195
1259
  // src/validation/engine.ts
1196
- var EMAIL_RE = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
1197
- var SEMVER_RE2 = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?(?:\+[0-9A-Za-z-.]+)?$/;
1260
+ var EMAIL_RE = /^[^@\s]+@[^@\s.]+(?:\.[^@\s.]+)+$/;
1261
+ var SEMVER_RE2 = /^\d+\.\d+\.\d+(?:-[\w.-]+)?(?:\+[\w.-]+)?$/;
1198
1262
  function detectReceivedType(value) {
1199
1263
  const normalized = value.trim();
1200
1264
  if (normalized.length === 0) return "unknown";
@@ -1214,65 +1278,72 @@ function detectReceivedType(value) {
1214
1278
  }
1215
1279
  return "string";
1216
1280
  }
1217
- function validateValueAgainstExpected(expected, rawValue) {
1218
- const normalized = rawValue.trim();
1219
- const receivedType = detectReceivedType(normalized);
1220
- if (expected.type === "unknown") return { isValid: true, receivedType };
1221
- if (expected.type === "string") return { isValid: true, receivedType };
1222
- if (expected.type === "number") {
1223
- const parsed = Number(normalized);
1224
- if (Number.isNaN(parsed) || !Number.isFinite(parsed)) {
1225
- return { isValid: false, receivedType, issueType: "invalid_type" };
1226
- }
1227
- if (expected.min !== void 0 && parsed < expected.min) {
1228
- return { isValid: false, receivedType, issueType: "invalid_value" };
1229
- }
1230
- if (expected.max !== void 0 && parsed > expected.max) {
1231
- return { isValid: false, receivedType, issueType: "invalid_value" };
1232
- }
1233
- return { isValid: true, receivedType };
1281
+ function validateNumber(expected, normalized, receivedType) {
1282
+ const parsed = Number(normalized);
1283
+ if (Number.isNaN(parsed) || !Number.isFinite(parsed)) {
1284
+ return { isValid: false, receivedType, issueType: "invalid_type" };
1234
1285
  }
1235
- if (expected.type === "boolean") {
1236
- if (!["true", "false", "1", "0", "yes", "no"].includes(normalized.toLowerCase())) {
1237
- return { isValid: false, receivedType, issueType: "invalid_type" };
1238
- }
1239
- return { isValid: true, receivedType };
1286
+ if (expected.min !== void 0 && parsed < expected.min) {
1287
+ return { isValid: false, receivedType, issueType: "invalid_value" };
1240
1288
  }
1241
- if (expected.type === "enum") {
1242
- if (!expected.values.includes(normalized)) {
1243
- return { isValid: false, receivedType, issueType: "invalid_value" };
1244
- }
1245
- return { isValid: true, receivedType };
1289
+ if (expected.max !== void 0 && parsed > expected.max) {
1290
+ return { isValid: false, receivedType, issueType: "invalid_value" };
1246
1291
  }
1247
- if (expected.type === "url") {
1248
- try {
1249
- const value = new URL(normalized);
1250
- if (value.protocol.length === 0)
1251
- return { isValid: false, receivedType, issueType: "invalid_type" };
1252
- return { isValid: true, receivedType };
1253
- } catch {
1254
- return { isValid: false, receivedType, issueType: "invalid_type" };
1255
- }
1292
+ return { isValid: true, receivedType };
1293
+ }
1294
+ function validateBoolean(normalized, receivedType) {
1295
+ if (!["true", "false", "1", "0", "yes", "no"].includes(normalized.toLowerCase())) {
1296
+ return { isValid: false, receivedType, issueType: "invalid_type" };
1256
1297
  }
1257
- if (expected.type === "email") {
1258
- if (!EMAIL_RE.test(normalized))
1298
+ return { isValid: true, receivedType };
1299
+ }
1300
+ function validateEnum(expected, normalized, receivedType) {
1301
+ if (!expected.values.includes(normalized)) {
1302
+ return { isValid: false, receivedType, issueType: "invalid_value" };
1303
+ }
1304
+ return { isValid: true, receivedType };
1305
+ }
1306
+ function validateUrl(normalized, receivedType) {
1307
+ try {
1308
+ const value = new URL(normalized);
1309
+ if (value.protocol.length === 0)
1259
1310
  return { isValid: false, receivedType, issueType: "invalid_type" };
1260
1311
  return { isValid: true, receivedType };
1312
+ } catch {
1313
+ return { isValid: false, receivedType, issueType: "invalid_type" };
1261
1314
  }
1262
- if (expected.type === "json") {
1263
- try {
1264
- const parsed = JSON.parse(normalized);
1265
- if (typeof parsed === "object" && parsed !== null) return { isValid: true, receivedType };
1266
- return { isValid: false, receivedType, issueType: "invalid_type" };
1267
- } catch {
1268
- return { isValid: false, receivedType, issueType: "invalid_type" };
1269
- }
1315
+ }
1316
+ function validateEmail(normalized, receivedType) {
1317
+ if (!EMAIL_RE.test(normalized))
1318
+ return { isValid: false, receivedType, issueType: "invalid_type" };
1319
+ return { isValid: true, receivedType };
1320
+ }
1321
+ function validateJson(normalized, receivedType) {
1322
+ try {
1323
+ const parsed = JSON.parse(normalized);
1324
+ if (typeof parsed === "object" && parsed !== null) return { isValid: true, receivedType };
1325
+ return { isValid: false, receivedType, issueType: "invalid_type" };
1326
+ } catch {
1327
+ return { isValid: false, receivedType, issueType: "invalid_type" };
1270
1328
  }
1271
- if (expected.type === "semver") {
1272
- if (!SEMVER_RE2.test(normalized))
1273
- return { isValid: false, receivedType, issueType: "invalid_value" };
1329
+ }
1330
+ function validateSemver(normalized, receivedType) {
1331
+ if (!SEMVER_RE2.test(normalized))
1332
+ return { isValid: false, receivedType, issueType: "invalid_value" };
1333
+ return { isValid: true, receivedType };
1334
+ }
1335
+ function validateValueAgainstExpected(expected, rawValue) {
1336
+ const normalized = rawValue.trim();
1337
+ const receivedType = detectReceivedType(normalized);
1338
+ if (expected.type === "unknown" || expected.type === "string")
1274
1339
  return { isValid: true, receivedType };
1275
- }
1340
+ if (expected.type === "number") return validateNumber(expected, normalized, receivedType);
1341
+ if (expected.type === "boolean") return validateBoolean(normalized, receivedType);
1342
+ if (expected.type === "enum") return validateEnum(expected, normalized, receivedType);
1343
+ if (expected.type === "url") return validateUrl(normalized, receivedType);
1344
+ if (expected.type === "email") return validateEmail(normalized, receivedType);
1345
+ if (expected.type === "json") return validateJson(normalized, receivedType);
1346
+ if (expected.type === "semver") return validateSemver(normalized, receivedType);
1276
1347
  return { isValid: true, receivedType };
1277
1348
  }
1278
1349
  function toIssueCode(issueType) {
@@ -1344,57 +1415,62 @@ function buildReport(env, issues, recommendations) {
1344
1415
  function isClientSecret(variable, key) {
1345
1416
  return variable.secret === true && (variable.clientSide || key.startsWith("NEXT_PUBLIC_"));
1346
1417
  }
1418
+ function checkContractVariable(key, variable, context) {
1419
+ const { options, issues } = context;
1420
+ const value = options.values[key];
1421
+ const hasValue = value !== void 0;
1422
+ if (variable.required && !hasValue) {
1423
+ issues.push(
1424
+ createIssue({
1425
+ type: "missing",
1426
+ severity: "error",
1427
+ key,
1428
+ environment: options.environment,
1429
+ message: `Required variable ${key} is missing.`,
1430
+ debugValues: options.debugValues,
1431
+ expected: variable.expected
1432
+ })
1433
+ );
1434
+ return;
1435
+ }
1436
+ if (!hasValue) return;
1437
+ const validation = validateValueAgainstExpected(variable.expected, value);
1438
+ if (!validation.isValid) {
1439
+ const message = validation.issueType === "invalid_type" ? `Variable ${key} has invalid type.` : `Variable ${key} has invalid value.`;
1440
+ issues.push(
1441
+ createIssue({
1442
+ type: validation.issueType,
1443
+ severity: "error",
1444
+ key,
1445
+ environment: options.environment,
1446
+ message,
1447
+ value,
1448
+ debugValues: options.debugValues,
1449
+ expected: variable.expected,
1450
+ receivedType: validation.receivedType
1451
+ })
1452
+ );
1453
+ }
1454
+ if (isClientSecret(variable, key)) {
1455
+ issues.push(
1456
+ createIssue({
1457
+ type: "secret_exposed",
1458
+ severity: "error",
1459
+ key,
1460
+ environment: options.environment,
1461
+ message: `Secret variable ${key} is marked as client-side.`,
1462
+ value,
1463
+ debugValues: options.debugValues,
1464
+ expected: variable.expected
1465
+ })
1466
+ );
1467
+ }
1468
+ }
1347
1469
  function validateAgainstContract(options) {
1348
1470
  const issues = [];
1349
1471
  const contractKeys = new Set(Object.keys(options.contract.variables));
1350
1472
  for (const [key, variable] of Object.entries(options.contract.variables)) {
1351
- const value = options.values[key];
1352
- const hasValue = value !== void 0 && value.trim().length > 0;
1353
- if (variable.required && !hasValue) {
1354
- issues.push(
1355
- createIssue({
1356
- type: "missing",
1357
- severity: "error",
1358
- key,
1359
- environment: options.environment,
1360
- message: `Required variable ${key} is missing.`,
1361
- debugValues: options.debugValues,
1362
- expected: variable.expected
1363
- })
1364
- );
1365
- continue;
1366
- }
1367
- if (!hasValue) continue;
1368
- const validation = validateValueAgainstExpected(variable.expected, value);
1369
- if (!validation.isValid) {
1370
- issues.push(
1371
- createIssue({
1372
- type: validation.issueType,
1373
- severity: "error",
1374
- key,
1375
- environment: options.environment,
1376
- message: validation.issueType === "invalid_type" ? `Variable ${key} has invalid type.` : `Variable ${key} has invalid value.`,
1377
- value,
1378
- debugValues: options.debugValues,
1379
- expected: variable.expected,
1380
- receivedType: validation.receivedType
1381
- })
1382
- );
1383
- }
1384
- if (isClientSecret(variable, key)) {
1385
- issues.push(
1386
- createIssue({
1387
- type: "secret_exposed",
1388
- severity: "error",
1389
- key,
1390
- environment: options.environment,
1391
- message: `Secret variable ${key} is marked as client-side.`,
1392
- value,
1393
- debugValues: options.debugValues,
1394
- expected: variable.expected
1395
- })
1396
- );
1397
- }
1473
+ checkContractVariable(key, variable, { options, issues });
1398
1474
  }
1399
1475
  for (const [key, value] of Object.entries(options.values)) {
1400
1476
  if (contractKeys.has(key)) continue;
@@ -1422,6 +1498,97 @@ function collectUnionKeys(contract, sources) {
1422
1498
  }
1423
1499
  return union;
1424
1500
  }
1501
+ function diffMissingEntries(key, missing, context) {
1502
+ const { variable, options, issues } = context;
1503
+ for (const entry of missing) {
1504
+ issues.push(
1505
+ createIssue({
1506
+ type: "missing",
1507
+ severity: "error",
1508
+ key,
1509
+ environment: entry.sourceName,
1510
+ message: `Variable ${key} is missing in ${entry.sourceName}.`,
1511
+ debugValues: options.debugValues,
1512
+ ...variable !== void 0 && { expected: variable.expected }
1513
+ })
1514
+ );
1515
+ }
1516
+ }
1517
+ function diffTypeConflicts(key, present, context) {
1518
+ const { variable, options, issues } = context;
1519
+ const typeBySource = /* @__PURE__ */ new Map();
1520
+ for (const entry of present) {
1521
+ typeBySource.set(entry.sourceName, detectReceivedType(entry.value ?? ""));
1522
+ }
1523
+ if (new Set(typeBySource.values()).size <= 1) return;
1524
+ for (const [sourceName, detectedType] of typeBySource.entries()) {
1525
+ issues.push(
1526
+ createIssue({
1527
+ type: "conflict",
1528
+ severity: "error",
1529
+ key,
1530
+ environment: sourceName,
1531
+ message: `Variable ${key} has conflicting inferred type across environments.`,
1532
+ debugValues: options.debugValues,
1533
+ receivedType: detectedType,
1534
+ ...options.sources[sourceName]?.[key] !== void 0 && {
1535
+ value: options.sources[sourceName]?.[key]
1536
+ },
1537
+ ...variable !== void 0 && { expected: variable.expected }
1538
+ })
1539
+ );
1540
+ }
1541
+ }
1542
+ function diffPresentEntry(key, entry, context) {
1543
+ const { variable, options, issues } = context;
1544
+ if (entry.value === void 0) return;
1545
+ if (variable === void 0) {
1546
+ const severity = options.strict ? "error" : "warning";
1547
+ issues.push(
1548
+ createIssue({
1549
+ type: "extra",
1550
+ severity,
1551
+ key,
1552
+ environment: entry.sourceName,
1553
+ message: `Variable ${key} is not defined in the contract.`,
1554
+ value: entry.value,
1555
+ debugValues: options.debugValues
1556
+ })
1557
+ );
1558
+ return;
1559
+ }
1560
+ const validation = validateValueAgainstExpected(variable.expected, entry.value);
1561
+ if (!validation.isValid) {
1562
+ const message = validation.issueType === "invalid_type" ? `Variable ${key} has invalid type in ${entry.sourceName}.` : `Variable ${key} has invalid value in ${entry.sourceName}.`;
1563
+ issues.push(
1564
+ createIssue({
1565
+ type: validation.issueType,
1566
+ severity: "error",
1567
+ key,
1568
+ environment: entry.sourceName,
1569
+ message,
1570
+ value: entry.value,
1571
+ debugValues: options.debugValues,
1572
+ expected: variable.expected,
1573
+ receivedType: validation.receivedType
1574
+ })
1575
+ );
1576
+ }
1577
+ if (isClientSecret(variable, key)) {
1578
+ issues.push(
1579
+ createIssue({
1580
+ type: "secret_exposed",
1581
+ severity: "error",
1582
+ key,
1583
+ environment: entry.sourceName,
1584
+ message: `Secret variable ${key} is marked as client-side.`,
1585
+ value: entry.value,
1586
+ debugValues: options.debugValues,
1587
+ expected: variable.expected
1588
+ })
1589
+ );
1590
+ }
1591
+ }
1425
1592
  function diffEnvironmentSources(options) {
1426
1593
  const issues = [];
1427
1594
  const sourceNames = Object.keys(options.sources);
@@ -1432,12 +1599,8 @@ function diffEnvironmentSources(options) {
1432
1599
  sourceName,
1433
1600
  value: options.sources[sourceName]?.[key]
1434
1601
  }));
1435
- const present = valuesBySource.filter(
1436
- (entry) => entry.value !== void 0 && entry.value !== ""
1437
- );
1438
- const missing = valuesBySource.filter(
1439
- (entry) => entry.value === void 0 || entry.value === ""
1440
- );
1602
+ const present = valuesBySource.filter((entry) => entry.value !== void 0);
1603
+ const missing = valuesBySource.filter((entry) => entry.value === void 0);
1441
1604
  if (present.length === 0 && variable?.required === true) {
1442
1605
  for (const entry of missing) {
1443
1606
  issues.push(
@@ -1454,92 +1617,13 @@ function diffEnvironmentSources(options) {
1454
1617
  }
1455
1618
  continue;
1456
1619
  }
1620
+ const ctx = { variable, options, issues };
1457
1621
  if (present.length > 0) {
1458
- for (const entry of missing) {
1459
- issues.push(
1460
- createIssue({
1461
- type: "missing",
1462
- severity: "error",
1463
- key,
1464
- environment: entry.sourceName,
1465
- message: `Variable ${key} is missing in ${entry.sourceName}.`,
1466
- debugValues: options.debugValues,
1467
- ...variable !== void 0 && { expected: variable.expected }
1468
- })
1469
- );
1470
- }
1622
+ diffMissingEntries(key, missing, ctx);
1471
1623
  }
1472
- const typeBySource = /* @__PURE__ */ new Map();
1624
+ diffTypeConflicts(key, present, ctx);
1473
1625
  for (const entry of present) {
1474
- const detected = detectReceivedType(entry.value ?? "");
1475
- typeBySource.set(entry.sourceName, detected);
1476
- }
1477
- if (new Set(typeBySource.values()).size > 1) {
1478
- for (const [sourceName, detectedType] of typeBySource.entries()) {
1479
- issues.push(
1480
- createIssue({
1481
- type: "conflict",
1482
- severity: "error",
1483
- key,
1484
- environment: sourceName,
1485
- message: `Variable ${key} has conflicting inferred type across environments.`,
1486
- debugValues: options.debugValues,
1487
- receivedType: detectedType,
1488
- ...options.sources[sourceName]?.[key] !== void 0 && {
1489
- value: options.sources[sourceName]?.[key]
1490
- },
1491
- ...variable !== void 0 && { expected: variable.expected }
1492
- })
1493
- );
1494
- }
1495
- }
1496
- for (const entry of present) {
1497
- if (entry.value === void 0) continue;
1498
- if (variable === void 0) {
1499
- const severity = options.strict ? "error" : "warning";
1500
- issues.push(
1501
- createIssue({
1502
- type: "extra",
1503
- severity,
1504
- key,
1505
- environment: entry.sourceName,
1506
- message: `Variable ${key} is not defined in the contract.`,
1507
- value: entry.value,
1508
- debugValues: options.debugValues
1509
- })
1510
- );
1511
- continue;
1512
- }
1513
- const validation = validateValueAgainstExpected(variable.expected, entry.value);
1514
- if (!validation.isValid) {
1515
- issues.push(
1516
- createIssue({
1517
- type: validation.issueType,
1518
- severity: "error",
1519
- key,
1520
- environment: entry.sourceName,
1521
- message: validation.issueType === "invalid_type" ? `Variable ${key} has invalid type in ${entry.sourceName}.` : `Variable ${key} has invalid value in ${entry.sourceName}.`,
1522
- value: entry.value,
1523
- debugValues: options.debugValues,
1524
- expected: variable.expected,
1525
- receivedType: validation.receivedType
1526
- })
1527
- );
1528
- }
1529
- if (isClientSecret(variable, key)) {
1530
- issues.push(
1531
- createIssue({
1532
- type: "secret_exposed",
1533
- severity: "error",
1534
- key,
1535
- environment: entry.sourceName,
1536
- message: `Secret variable ${key} is marked as client-side.`,
1537
- value: entry.value,
1538
- debugValues: options.debugValues,
1539
- expected: variable.expected
1540
- })
1541
- );
1542
- }
1626
+ diffPresentEntry(key, entry, ctx);
1543
1627
  }
1544
1628
  }
1545
1629
  return buildReport("diff", issues);
@@ -1588,7 +1672,7 @@ function parseEnvSourceContent(content) {
1588
1672
  for (const line of lines) {
1589
1673
  const trimmed = line.trim();
1590
1674
  if (trimmed.length === 0 || trimmed.startsWith("#")) continue;
1591
- const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.exec(trimmed);
1675
+ const match = /^(?:export\s+)?([A-Za-z_]\w*)=(.*)$/.exec(trimmed);
1592
1676
  if (match === null) continue;
1593
1677
  const key = match[1] ?? "";
1594
1678
  const rawValue = match[2] ?? "";
@@ -1601,11 +1685,11 @@ async function loadEnvSource(options) {
1601
1685
  try {
1602
1686
  const content = await readFile(resolvedPath, "utf8");
1603
1687
  return parseEnvSourceContent(content);
1604
- } catch (errorValue) {
1605
- if (options.allowMissing === true && errorValue instanceof Error && "code" in errorValue && errorValue.code === "ENOENT") {
1688
+ } catch (error_) {
1689
+ if (options.allowMissing === true && error_ instanceof Error && "code" in error_ && error_.code === "ENOENT") {
1606
1690
  return {};
1607
1691
  }
1608
- throw errorValue;
1692
+ throw error_;
1609
1693
  }
1610
1694
  }
1611
1695
  function toJsonString(report, mode) {
@@ -1615,8 +1699,8 @@ function toJsonString(report, mode) {
1615
1699
  `;
1616
1700
  }
1617
1701
  function formatIssue(issue) {
1618
- const expected = issue.expected !== void 0 ? ` expected=${issue.expected.type}` : "";
1619
- const received = issue.receivedType !== void 0 ? ` received=${issue.receivedType}` : "";
1702
+ const expected = issue.expected === void 0 ? "" : ` expected=${issue.expected.type}`;
1703
+ const received = issue.receivedType === void 0 ? "" : ` received=${issue.receivedType}`;
1620
1704
  return `${issue.severity.toUpperCase()} [${issue.code}] ${issue.environment}:${issue.key} ${issue.message}${expected}${received}`;
1621
1705
  }
1622
1706
  function formatHumanReport(report) {
@@ -1625,15 +1709,13 @@ function formatHumanReport(report) {
1625
1709
  `Status: ${report.status.toUpperCase()} (errors=${report.summary.errors}, warnings=${report.summary.warnings}, total=${report.summary.total})`
1626
1710
  );
1627
1711
  if (report.issues.length > 0) {
1628
- lines.push("");
1629
- lines.push("Issues:");
1712
+ lines.push("", "Issues:");
1630
1713
  for (const issue of report.issues) {
1631
1714
  lines.push(`- ${formatIssue(issue)}`);
1632
1715
  }
1633
1716
  }
1634
1717
  if (report.recommendations !== void 0 && report.recommendations.length > 0) {
1635
- lines.push("");
1636
- lines.push("Recommendations:");
1718
+ lines.push("", "Recommendations:");
1637
1719
  for (const recommendation of report.recommendations) {
1638
1720
  lines.push(`- ${recommendation}`);
1639
1721
  }
@@ -1678,7 +1760,11 @@ var HELP_TEXT = {
1678
1760
  " --cloud-file <path> Cloud snapshot JSON file",
1679
1761
  " --plugin <path> Plugin module path (repeatable)",
1680
1762
  " -c, --config <path> Config file path",
1681
- " -h, --help Show this help"
1763
+ " -h, --help Show this help",
1764
+ "",
1765
+ "Exit codes:",
1766
+ " 0 All checks passed (status: ok or warn)",
1767
+ " 1 One or more checks failed (status: fail) or invalid usage"
1682
1768
  ].join("\n"),
1683
1769
  diff: [
1684
1770
  "Usage: env-typegen diff [options]",
@@ -1697,7 +1783,11 @@ var HELP_TEXT = {
1697
1783
  " --cloud-file <path> Cloud snapshot JSON file added to diff sources",
1698
1784
  " --plugin <path> Plugin module path (repeatable)",
1699
1785
  " -c, --config <path> Config file path",
1700
- " -h, --help Show this help"
1786
+ " -h, --help Show this help",
1787
+ "",
1788
+ "Exit codes:",
1789
+ " 0 All checks passed (status: ok or warn)",
1790
+ " 1 One or more checks failed (status: fail) or invalid usage"
1701
1791
  ].join("\n"),
1702
1792
  doctor: [
1703
1793
  "Usage: env-typegen doctor [options]",
@@ -1717,7 +1807,11 @@ var HELP_TEXT = {
1717
1807
  " --cloud-file <path> Cloud snapshot JSON file",
1718
1808
  " --plugin <path> Plugin module path (repeatable)",
1719
1809
  " -c, --config <path> Config file path",
1720
- " -h, --help Show this help"
1810
+ " -h, --help Show this help",
1811
+ "",
1812
+ "Exit codes:",
1813
+ " 0 All checks passed (status: ok or warn)",
1814
+ " 1 One or more checks failed (status: fail) or invalid usage"
1721
1815
  ].join("\n")
1722
1816
  };
1723
1817
  function resolveConfigRelative(value, configDir) {
@@ -1759,6 +1853,9 @@ async function loadCommandConfig(configPath) {
1759
1853
  return loadConfig(process.cwd());
1760
1854
  }
1761
1855
  const resolvedPath = path6.resolve(configPath);
1856
+ if (!existsSync(resolvedPath)) {
1857
+ throw new Error(`Config file not found: ${configPath}`);
1858
+ }
1762
1859
  const configDir = path6.dirname(resolvedPath);
1763
1860
  const moduleValue = await import(pathToFileURL(resolvedPath).href);
1764
1861
  if (moduleValue.default === void 0) return void 0;
@@ -1804,7 +1901,10 @@ function parseValidationArgs(argv) {
1804
1901
  }
1805
1902
  });
1806
1903
  const castValues = values;
1807
- const jsonMode = castValues.json === true ? assignedMode === "off" ? "compact" : assignedMode : "off";
1904
+ let jsonMode = "off";
1905
+ if (castValues.json === true) {
1906
+ jsonMode = assignedMode === "off" ? "compact" : assignedMode;
1907
+ }
1808
1908
  return { values: castValues, jsonMode };
1809
1909
  }
1810
1910
  function resolveStrict(values, fileConfig) {
@@ -1877,12 +1977,12 @@ async function runCheckCommand(args) {
1877
1977
  const provider = context.cloudProvider;
1878
1978
  let environment = args.values.env?.[0] ?? ".env";
1879
1979
  let sourceValues;
1880
- if (provider !== void 0) {
1980
+ if (provider === void 0) {
1981
+ sourceValues = await loadEnvSource({ filePath: environment, allowMissing: true });
1982
+ } else {
1881
1983
  const cloudFile = context.cloudFile ?? `${provider}.env.json`;
1882
1984
  sourceValues = await loadCloudSource({ provider, filePath: cloudFile });
1883
1985
  environment = `cloud:${provider}`;
1884
- } else {
1885
- sourceValues = await loadEnvSource({ filePath: environment, allowMissing: true });
1886
1986
  }
1887
1987
  sourceValues = applySourcePlugins({ environment, values: sourceValues }, context.plugins);
1888
1988
  const report = applyReportPlugins(