@xlameiro/env-typegen 0.1.1 → 0.1.3

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.cjs CHANGED
@@ -1,15 +1,16 @@
1
1
  'use strict';
2
2
 
3
3
  var fs = require('fs');
4
- var path4 = require('path');
4
+ var path6 = require('path');
5
5
  var url = require('url');
6
6
  var promises = require('fs/promises');
7
7
  var prettier = require('prettier');
8
8
  var picocolors = require('picocolors');
9
+ var util = require('util');
9
10
 
10
11
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
12
 
12
- var path4__default = /*#__PURE__*/_interopDefault(path4);
13
+ var path6__default = /*#__PURE__*/_interopDefault(path6);
13
14
 
14
15
  // src/parser/comment-parser.ts
15
16
  var VALID_ENV_VAR_TYPES = /* @__PURE__ */ new Set([
@@ -25,28 +26,73 @@ var VALID_ENV_VAR_TYPES = /* @__PURE__ */ new Set([
25
26
  function isEnvVarType(value) {
26
27
  return VALID_ENV_VAR_TYPES.has(value);
27
28
  }
29
+ function applyTypeAnnotation(state, content) {
30
+ const typeStr = content.slice("@type ".length).trim();
31
+ if (isEnvVarType(typeStr)) state.annotatedType = typeStr;
32
+ }
33
+ function applyEnumAnnotation(state, content) {
34
+ const values = content.slice("@enum ".length).trim().split(",").map((v) => v.trim()).filter((v) => v.length > 0);
35
+ if (values.length > 0) state.enumValues = values;
36
+ }
37
+ function applyMinAnnotation(state, content) {
38
+ const num = Number(content.slice("@min ".length).trim());
39
+ if (Number.isFinite(num)) state.minConstraint = num;
40
+ }
41
+ function applyMaxAnnotation(state, content) {
42
+ const num = Number(content.slice("@max ".length).trim());
43
+ if (Number.isFinite(num)) state.maxConstraint = num;
44
+ }
45
+ function applyRuntimeAnnotation(state, content) {
46
+ const scope = content.slice("@runtime ".length).trim();
47
+ if (scope === "server" || scope === "client" || scope === "edge") {
48
+ state.runtime = scope;
49
+ }
50
+ }
51
+ function processAnnotationContent(state, content) {
52
+ const trimmed = content.trim();
53
+ if (trimmed === "@required") {
54
+ state.isRequired = true;
55
+ return;
56
+ }
57
+ if (trimmed === "@secret") {
58
+ state.isSecret = true;
59
+ return;
60
+ }
61
+ if (trimmed === "@optional") return;
62
+ if (content.startsWith("@description ")) {
63
+ state.description = content.slice("@description ".length).trim();
64
+ } else if (content.startsWith("@type ")) {
65
+ applyTypeAnnotation(state, content);
66
+ } else if (content.startsWith("@enum ")) {
67
+ applyEnumAnnotation(state, content);
68
+ } else if (content.startsWith("@min ")) {
69
+ applyMinAnnotation(state, content);
70
+ } else if (content.startsWith("@max ")) {
71
+ applyMaxAnnotation(state, content);
72
+ } else if (content.startsWith("@runtime ")) {
73
+ applyRuntimeAnnotation(state, content);
74
+ } else if (state.description === void 0 && trimmed.length > 0) {
75
+ state.description = trimmed;
76
+ }
77
+ }
28
78
  function parseCommentBlock(lines) {
29
- let annotatedType;
30
- let description;
31
- let isRequired = false;
79
+ const state = { isRequired: false };
32
80
  for (const line of lines) {
33
- const content = line.replace(/^#\s*/, "").trimEnd();
34
- if (content.startsWith("@description ")) {
35
- description = content.slice("@description ".length).trim();
36
- } else if (content.startsWith("@type ")) {
37
- const typeStr = content.slice("@type ".length).trim();
38
- if (isEnvVarType(typeStr)) {
39
- annotatedType = typeStr;
40
- }
41
- } else if (content.trim() === "@required") {
42
- isRequired = true;
43
- } else if (content.trim() === "@optional") ; else if (description === void 0 && content.trim().length > 0) {
44
- description = content.trim();
45
- }
81
+ processAnnotationContent(state, line.replace(/^#\s*/, "").trimEnd());
46
82
  }
47
- const result = { isRequired };
48
- if (annotatedType !== void 0) result.annotatedType = annotatedType;
49
- if (description !== void 0) result.description = description;
83
+ let constraints;
84
+ if (state.minConstraint !== void 0 || state.maxConstraint !== void 0) {
85
+ constraints = {};
86
+ if (state.minConstraint !== void 0) constraints.min = state.minConstraint;
87
+ if (state.maxConstraint !== void 0) constraints.max = state.maxConstraint;
88
+ }
89
+ const result = { isRequired: state.isRequired };
90
+ if (state.annotatedType !== void 0) result.annotatedType = state.annotatedType;
91
+ if (state.description !== void 0) result.description = state.description;
92
+ if (state.enumValues !== void 0) result.enumValues = state.enumValues;
93
+ if (constraints !== void 0) result.constraints = constraints;
94
+ if (state.runtime !== void 0) result.runtime = state.runtime;
95
+ if (state.isSecret !== void 0) result.isSecret = state.isSecret;
50
96
  return result;
51
97
  }
52
98
 
@@ -142,7 +188,9 @@ var inferenceRules = [
142
188
  // src/inferrer/type-inferrer.ts
143
189
  var sortedRules = [...inferenceRules].sort((left, right) => left.priority - right.priority);
144
190
  function inferType(key, value, options) {
145
- for (const rule of sortedRules) {
191
+ const extra = options?.extraRules;
192
+ const rules = extra && extra.length > 0 ? [...extra].sort((a, b) => a.priority - b.priority).concat(sortedRules) : sortedRules;
193
+ for (const rule of rules) {
146
194
  if (rule.match(key, value)) {
147
195
  return rule.type;
148
196
  }
@@ -154,7 +202,7 @@ function inferTypesFromParsedVars(parsed, options) {
154
202
  }
155
203
  var ENV_VAR_RE = /^([A-Z_][A-Z0-9_]*)=(.*)$/;
156
204
  var SECTION_HEADER_RE = /^#\s+[-=]{3,}\s+(.+?)\s+[-=]{3,}\s*$/;
157
- function parseEnvFileContent(content, filePath) {
205
+ function parseEnvFileContent(content, filePath, options) {
158
206
  const lines = content.split("\n");
159
207
  const vars = [];
160
208
  const groups = [];
@@ -187,7 +235,11 @@ function parseEnvFileContent(content, filePath) {
187
235
  const key = envMatch[1] ?? "";
188
236
  const rawValue = envMatch[2] ?? "";
189
237
  const annotations = parseCommentBlock(commentBlock);
190
- const inferredType = inferType(key, rawValue);
238
+ const inferredType = inferType(
239
+ key,
240
+ rawValue,
241
+ ...options?.inferenceRules !== void 0 ? [{ extraRules: options.inferenceRules }] : []
242
+ );
191
243
  const isRequired = rawValue.length > 0 || annotations.isRequired;
192
244
  const isOptional = rawValue.length === 0 && !annotations.isRequired;
193
245
  const isClientSide = key.startsWith("NEXT_PUBLIC_");
@@ -209,6 +261,18 @@ function parseEnvFileContent(content, filePath) {
209
261
  if (currentGroup !== void 0) {
210
262
  parsedVar.group = currentGroup;
211
263
  }
264
+ if (annotations.enumValues !== void 0) {
265
+ parsedVar.enumValues = annotations.enumValues;
266
+ }
267
+ if (annotations.constraints !== void 0) {
268
+ parsedVar.constraints = annotations.constraints;
269
+ }
270
+ if (annotations.runtime !== void 0) {
271
+ parsedVar.runtime = annotations.runtime;
272
+ }
273
+ if (annotations.isSecret !== void 0) {
274
+ parsedVar.isSecret = annotations.isSecret;
275
+ }
212
276
  vars.push(parsedVar);
213
277
  commentBlock = [];
214
278
  } else {
@@ -229,7 +293,7 @@ function toTsType(envVarType) {
229
293
  function generateTypeScriptTypes(parsed) {
230
294
  const clientVars = parsed.vars.filter((v) => v.isClientSide);
231
295
  const hasClientVars = clientVars.length > 0;
232
- const fileName = path4__default.default.basename(parsed.filePath);
296
+ const fileName = path6__default.default.basename(parsed.filePath);
233
297
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
234
298
  const lines = [];
235
299
  lines.push("// Generated by env-typegen \u2014 do not edit manually");
@@ -328,7 +392,7 @@ function generateZodSchema(parsed) {
328
392
  return lines.join("\n") + "\n";
329
393
  }
330
394
  function generateDeclaration(parsed) {
331
- const fileName = path4__default.default.basename(parsed.filePath);
395
+ const fileName = path6__default.default.basename(parsed.filePath);
332
396
  const lines = [];
333
397
  lines.push("// Generated by env-typegen \u2014 do not edit manually");
334
398
  lines.push(`// Source: ${fileName}`);
@@ -377,7 +441,7 @@ function generateT3Env(parsed) {
377
441
  const effectiveType = variable.annotatedType ?? variable.inferredType;
378
442
  let zodExpr = toT3ZodType(effectiveType);
379
443
  if (variable.description !== void 0) {
380
- zodExpr += `.describe("${variable.description}")`;
444
+ zodExpr += `.describe("${variable.description.replace(/"/g, '\\"')}")`;
381
445
  }
382
446
  if (variable.isOptional) {
383
447
  zodExpr += ".optional()";
@@ -392,7 +456,7 @@ function generateT3Env(parsed) {
392
456
  const effectiveType = variable.annotatedType ?? variable.inferredType;
393
457
  let zodExpr = toT3ZodType(effectiveType);
394
458
  if (variable.description !== void 0) {
395
- zodExpr += `.describe("${variable.description}")`;
459
+ zodExpr += `.describe("${variable.description.replace(/"/g, '\\"')}")`;
396
460
  }
397
461
  if (variable.isOptional) {
398
462
  zodExpr += ".optional()";
@@ -409,6 +473,25 @@ function generateT3Env(parsed) {
409
473
  lines.push("});");
410
474
  return lines.join("\n") + "\n";
411
475
  }
476
+ var CONTRACT_FILE_NAMES = [
477
+ "env.contract.ts",
478
+ "env.contract.mjs",
479
+ "env.contract.js"
480
+ ];
481
+ function defineContract(contract) {
482
+ return contract;
483
+ }
484
+ async function loadContract(cwd = process.cwd()) {
485
+ for (const name of CONTRACT_FILE_NAMES) {
486
+ const filePath = path6__default.default.resolve(cwd, name);
487
+ if (fs.existsSync(filePath)) {
488
+ const fileUrl = url.pathToFileURL(filePath).href;
489
+ const mod = await import(fileUrl);
490
+ return mod.default;
491
+ }
492
+ }
493
+ return void 0;
494
+ }
412
495
  var CONFIG_FILE_NAMES = [
413
496
  "env-typegen.config.ts",
414
497
  "env-typegen.config.mjs",
@@ -419,7 +502,7 @@ function defineConfig(config) {
419
502
  }
420
503
  async function loadConfig(cwd = process.cwd()) {
421
504
  for (const name of CONFIG_FILE_NAMES) {
422
- const filePath = path4__default.default.resolve(cwd, name);
505
+ const filePath = path6__default.default.resolve(cwd, name);
423
506
  if (fs.existsSync(filePath)) {
424
507
  const fileUrl = url.pathToFileURL(filePath).href;
425
508
  const mod = await import(fileUrl);
@@ -429,20 +512,33 @@ async function loadConfig(cwd = process.cwd()) {
429
512
  return void 0;
430
513
  }
431
514
  async function readEnvFile(filePath) {
432
- return promises.readFile(path4__default.default.resolve(filePath), "utf8");
515
+ return promises.readFile(path6__default.default.resolve(filePath), "utf8");
433
516
  }
434
517
  async function writeOutput(filePath, content) {
435
- const resolved = path4__default.default.resolve(filePath);
436
- await promises.mkdir(path4__default.default.dirname(resolved), { recursive: true });
518
+ const resolved = path6__default.default.resolve(filePath);
519
+ await promises.mkdir(path6__default.default.dirname(resolved), { recursive: true });
437
520
  await promises.writeFile(resolved, content, "utf8");
438
521
  }
439
522
  async function formatOutput(content, parser = "typescript") {
440
523
  try {
441
524
  return await prettier.format(content, { parser });
442
- } catch {
525
+ } catch (err) {
526
+ console.warn(
527
+ "env-typegen: Prettier formatting failed, writing unformatted output.",
528
+ err instanceof Error ? err.message : String(err)
529
+ );
443
530
  return content;
444
531
  }
445
532
  }
533
+ function log(message) {
534
+ console.log(message);
535
+ }
536
+ function warn(message) {
537
+ console.warn(picocolors.yellow(`\u26A0 ${message}`));
538
+ }
539
+ function error(message) {
540
+ console.error(picocolors.red(`\u2716 ${message}`));
541
+ }
446
542
  function success(message) {
447
543
  console.log(picocolors.green(`\u2714 ${message}`));
448
544
  }
@@ -450,12 +546,18 @@ function success(message) {
450
546
  // src/pipeline.ts
451
547
  function deriveOutputPath(base, generator, isSingle) {
452
548
  if (isSingle) return base;
453
- const ext = path4__default.default.extname(base);
549
+ const ext = path6__default.default.extname(base);
454
550
  const noExt = ext.length > 0 ? base.slice(0, -ext.length) : base;
455
551
  const baseExt = ext.length > 0 ? ext : ".ts";
456
552
  const outExt = generator === "declaration" ? ".d.ts" : baseExt;
457
553
  return `${noExt}.${generator}${outExt}`;
458
554
  }
555
+ function deriveOutputBaseForInput(output, inputPath) {
556
+ const dir = path6__default.default.dirname(output);
557
+ const ext = path6__default.default.extname(output);
558
+ const stem = path6__default.default.basename(inputPath, path6__default.default.extname(inputPath));
559
+ return path6__default.default.join(dir, `${stem}${ext}`);
560
+ }
459
561
  function buildOutput(generator, parsed) {
460
562
  switch (generator) {
461
563
  case "typescript":
@@ -481,7 +583,11 @@ async function persistOutput(params) {
481
583
  }
482
584
  if (dryRun) {
483
585
  if (!silent) {
484
- success(`Dry run: ${outputPath}`);
586
+ if (!isSingle) {
587
+ console.log(`// --- ${generator}: ${outputPath} ---`);
588
+ }
589
+ console.log(generated);
590
+ success(`Dry run: would write ${outputPath}`);
485
591
  }
486
592
  return;
487
593
  }
@@ -498,44 +604,1447 @@ async function runGenerate(options) {
498
604
  format: shouldFormat,
499
605
  stdout = false,
500
606
  dryRun = false,
501
- silent = false
607
+ silent = false,
608
+ inferenceRules: inferenceRules2
502
609
  } = options;
610
+ const inputs = Array.isArray(input) ? input : [input];
611
+ const hasMultipleInputs = inputs.length > 1;
503
612
  const isSingle = generators.length === 1;
504
- const content = await readEnvFile(input);
505
- const parsed = parseEnvFileContent(content, input);
506
- for (const generator of generators) {
507
- let generated = buildOutput(generator, parsed);
508
- if (shouldFormat) {
509
- generated = await formatOutput(generated);
510
- }
511
- const outputPath = deriveOutputPath(output, generator, isSingle);
512
- await persistOutput({
513
- generated,
514
- generator,
515
- outputPath,
516
- isSingle,
517
- stdout,
518
- dryRun,
519
- silent
613
+ for (const inputPath of inputs) {
614
+ const outputBase = hasMultipleInputs ? deriveOutputBaseForInput(output, inputPath) : output;
615
+ const content = await readEnvFile(inputPath);
616
+ const parsed = parseEnvFileContent(
617
+ content,
618
+ inputPath,
619
+ inferenceRules2 !== void 0 ? { inferenceRules: inferenceRules2 } : void 0
620
+ );
621
+ for (const generator of generators) {
622
+ let generated = buildOutput(generator, parsed);
623
+ if (shouldFormat && !dryRun) {
624
+ generated = await formatOutput(generated);
625
+ }
626
+ const outputPath = deriveOutputPath(outputBase, generator, isSingle);
627
+ await persistOutput({
628
+ generated,
629
+ generator,
630
+ outputPath,
631
+ isSingle,
632
+ stdout,
633
+ dryRun,
634
+ silent
635
+ });
636
+ }
637
+ }
638
+ }
639
+
640
+ // src/validator/contract-validator.ts
641
+ var SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?(?:\+[0-9A-Za-z-.]+)?$/;
642
+ function buildExpected(entry) {
643
+ if (entry.enumValues !== void 0 && entry.enumValues.length > 0) {
644
+ return { type: "enum", values: entry.enumValues };
645
+ }
646
+ switch (entry.expectedType) {
647
+ case "number":
648
+ return {
649
+ type: "number",
650
+ ...entry.constraints?.min !== void 0 && { min: entry.constraints.min },
651
+ ...entry.constraints?.max !== void 0 && { max: entry.constraints.max }
652
+ };
653
+ case "boolean":
654
+ return { type: "boolean" };
655
+ case "url":
656
+ return { type: "url" };
657
+ case "email":
658
+ return { type: "email" };
659
+ case "json":
660
+ return { type: "json" };
661
+ case "semver":
662
+ return { type: "semver" };
663
+ case "unknown":
664
+ return { type: "unknown" };
665
+ default:
666
+ return { type: "string" };
667
+ }
668
+ }
669
+ function checkSecretExposed(variable, entry, environment) {
670
+ if (entry.isSecret !== true) return void 0;
671
+ if (variable.rawValue === "") return void 0;
672
+ return {
673
+ code: "ENV_SECRET_EXPOSED",
674
+ key: variable.key,
675
+ expected: buildExpected(entry),
676
+ environment,
677
+ severity: "error"
678
+ };
679
+ }
680
+ function checkInvalidType(variable, entry, environment) {
681
+ const value = variable.rawValue;
682
+ const expected = buildExpected(entry);
683
+ switch (entry.expectedType) {
684
+ case "number": {
685
+ if (!Number.isFinite(Number(value))) {
686
+ return {
687
+ code: "ENV_INVALID_TYPE",
688
+ key: variable.key,
689
+ expected,
690
+ environment,
691
+ severity: "error"
692
+ };
693
+ }
694
+ break;
695
+ }
696
+ case "boolean": {
697
+ if (value !== "true" && value !== "false") {
698
+ return {
699
+ code: "ENV_INVALID_TYPE",
700
+ key: variable.key,
701
+ expected,
702
+ environment,
703
+ severity: "error"
704
+ };
705
+ }
706
+ break;
707
+ }
708
+ case "url": {
709
+ try {
710
+ new URL(value);
711
+ } catch {
712
+ return {
713
+ code: "ENV_INVALID_TYPE",
714
+ key: variable.key,
715
+ expected,
716
+ environment,
717
+ severity: "error"
718
+ };
719
+ }
720
+ break;
721
+ }
722
+ case "email": {
723
+ if (!value.includes("@") || !value.includes(".")) {
724
+ return {
725
+ code: "ENV_INVALID_TYPE",
726
+ key: variable.key,
727
+ expected,
728
+ environment,
729
+ severity: "error"
730
+ };
731
+ }
732
+ break;
733
+ }
734
+ case "semver": {
735
+ if (!SEMVER_RE.test(value)) {
736
+ return {
737
+ code: "ENV_INVALID_TYPE",
738
+ key: variable.key,
739
+ expected,
740
+ environment,
741
+ severity: "error"
742
+ };
743
+ }
744
+ break;
745
+ }
746
+ }
747
+ return void 0;
748
+ }
749
+ function checkNumericConstraints(variable, entry, environment) {
750
+ if (entry.expectedType !== "number" || entry.constraints === void 0) return void 0;
751
+ const num = Number(variable.rawValue);
752
+ if (!Number.isFinite(num)) return void 0;
753
+ const { min, max } = entry.constraints;
754
+ const outsideMin = min !== void 0 && num < min;
755
+ const outsideMax = max !== void 0 && num > max;
756
+ if (outsideMin || outsideMax) {
757
+ return {
758
+ code: "ENV_INVALID_VALUE",
759
+ key: variable.key,
760
+ expected: buildExpected(entry),
761
+ environment,
762
+ severity: "error"
763
+ };
764
+ }
765
+ return void 0;
766
+ }
767
+ function checkInvalidValue(variable, entry, environment) {
768
+ const value = variable.rawValue;
769
+ if (entry.enumValues !== void 0 && entry.enumValues.length > 0) {
770
+ if (!entry.enumValues.includes(value)) {
771
+ return {
772
+ code: "ENV_INVALID_VALUE",
773
+ key: variable.key,
774
+ expected: { type: "enum", values: entry.enumValues },
775
+ environment,
776
+ severity: "error"
777
+ };
778
+ }
779
+ return void 0;
780
+ }
781
+ return checkNumericConstraints(variable, entry, environment);
782
+ }
783
+ function checkVariable(variable, context) {
784
+ const { contractByName, environment, strict } = context;
785
+ const entry = contractByName.get(variable.key);
786
+ if (entry === void 0) {
787
+ return [
788
+ {
789
+ code: "ENV_EXTRA",
790
+ key: variable.key,
791
+ expected: { type: "string" },
792
+ environment,
793
+ severity: strict ? "error" : "warning"
794
+ }
795
+ ];
796
+ }
797
+ const found = [];
798
+ const secretIssue = checkSecretExposed(variable, entry, environment);
799
+ if (secretIssue !== void 0) found.push(secretIssue);
800
+ if (variable.rawValue === "") return found;
801
+ const typeIssue = checkInvalidType(variable, entry, environment);
802
+ if (typeIssue !== void 0) {
803
+ found.push(typeIssue);
804
+ return found;
805
+ }
806
+ const valueIssue = checkInvalidValue(variable, entry, environment);
807
+ if (valueIssue !== void 0) found.push(valueIssue);
808
+ return found;
809
+ }
810
+ function validateContract(parsed, contract, opts = {}) {
811
+ const environment = opts.environment ?? "local";
812
+ const strict = opts.strict ?? true;
813
+ const issues = [];
814
+ const contractByName = new Map(
815
+ contract.vars.map((entry) => [entry.name, entry])
816
+ );
817
+ const parsedByKey = new Map(parsed.vars.map((v) => [v.key, v]));
818
+ for (const entry of contract.vars) {
819
+ if (!entry.required) continue;
820
+ if (parsedByKey.has(entry.name)) continue;
821
+ issues.push({
822
+ code: "ENV_MISSING",
823
+ key: entry.name,
824
+ expected: buildExpected(entry),
825
+ environment,
826
+ severity: "error"
827
+ });
828
+ }
829
+ const context = { contractByName, environment, strict };
830
+ for (const variable of parsed.vars) {
831
+ const perVarIssues = checkVariable(variable, context);
832
+ for (const issue of perVarIssues) {
833
+ issues.push(issue);
834
+ }
835
+ }
836
+ return { issues };
837
+ }
838
+
839
+ // src/reporting/ci-reporter.ts
840
+ var ISSUE_TYPE_MAP = {
841
+ ENV_MISSING: "Required variable is missing from the env file",
842
+ ENV_EXTRA: "Variable is present in the env file but not declared in the contract",
843
+ ENV_INVALID_TYPE: "Value cannot be coerced to the declared type",
844
+ ENV_INVALID_VALUE: "Value fails a constraint check (enum, min, max)",
845
+ ENV_CONFLICT: "Variable has inconsistent values across environments",
846
+ ENV_SECRET_EXPOSED: "Secret variable has a non-empty value in a public env file"
847
+ };
848
+ function toIssue(vi) {
849
+ const issue = {
850
+ code: vi.code,
851
+ type: ISSUE_TYPE_MAP[vi.code],
852
+ key: vi.key,
853
+ expected: vi.expected,
854
+ environment: vi.environment,
855
+ severity: vi.severity,
856
+ value: null
857
+ };
858
+ if (vi.received !== void 0) {
859
+ issue.received = vi.received;
860
+ }
861
+ return issue;
862
+ }
863
+ function buildCiReport(result, meta) {
864
+ const issues = result.issues.map(toIssue);
865
+ const errors = issues.filter((i) => i.severity === "error").length;
866
+ const warnings = issues.filter((i) => i.severity === "warning").length;
867
+ return {
868
+ schemaVersion: 1,
869
+ status: errors > 0 ? "fail" : "ok",
870
+ summary: { errors, warnings },
871
+ issues,
872
+ meta
873
+ };
874
+ }
875
+ function formatCiReport(report, opts) {
876
+ return JSON.stringify(report, null, opts?.pretty === true ? 2 : void 0);
877
+ }
878
+ async function resolveContract(contractPath, cwd) {
879
+ if (contractPath !== void 0) {
880
+ const absPath = path6__default.default.resolve(cwd ?? process.cwd(), contractPath);
881
+ if (!fs.existsSync(absPath)) {
882
+ throw new Error(`Contract file not found: ${absPath}`);
883
+ }
884
+ const mod = await import(url.pathToFileURL(absPath).href);
885
+ if (mod.default === void 0) {
886
+ throw new Error(`Contract file has no default export: ${absPath}`);
887
+ }
888
+ return mod.default;
889
+ }
890
+ const contract = await loadContract(cwd);
891
+ if (contract === void 0) {
892
+ throw new Error("No contract file found");
893
+ }
894
+ return contract;
895
+ }
896
+ function outputJson(result, input, pretty) {
897
+ const report = buildCiReport(result, {
898
+ env: input,
899
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
900
+ });
901
+ const formatOpts = {};
902
+ if (pretty) formatOpts.pretty = true;
903
+ log(formatCiReport(report, formatOpts));
904
+ }
905
+ function outputHuman(result) {
906
+ if (result.issues.length === 0) {
907
+ success("No issues found");
908
+ return;
909
+ }
910
+ for (const issue of result.issues) {
911
+ const msg = `[${issue.code}] ${issue.key}`;
912
+ if (issue.severity === "error") {
913
+ error(msg);
914
+ } else {
915
+ warn(msg);
916
+ }
917
+ }
918
+ }
919
+ async function runCheck(opts) {
920
+ const contract = await resolveContract(opts.contract, opts.cwd);
921
+ const parsed = parseEnvFile(opts.input);
922
+ const validatorOpts = {};
923
+ if (opts.environment !== void 0) validatorOpts.environment = opts.environment;
924
+ if (opts.strict !== void 0) validatorOpts.strict = opts.strict;
925
+ const result = validateContract(parsed, contract, validatorOpts);
926
+ const hasErrors = result.issues.some((issue) => issue.severity === "error");
927
+ const status = hasErrors ? "fail" : "ok";
928
+ if (!opts.silent) {
929
+ if (opts.json) {
930
+ outputJson(result, opts.input, opts.pretty);
931
+ } else {
932
+ outputHuman(result);
933
+ }
934
+ }
935
+ return status;
936
+ }
937
+ function isRecord(value) {
938
+ return typeof value === "object" && value !== null && !Array.isArray(value);
939
+ }
940
+ function readEntryValue(entry, keys) {
941
+ for (const key of keys) {
942
+ const value = entry[key];
943
+ if (typeof value === "string") return value;
944
+ }
945
+ return void 0;
946
+ }
947
+ function parseVercelPayload(value) {
948
+ const entries = Array.isArray(value) ? value : isRecord(value) && Array.isArray(value.envs) ? value.envs : [];
949
+ const result = {};
950
+ for (const entry of entries) {
951
+ if (!isRecord(entry)) continue;
952
+ const key = readEntryValue(entry, ["key", "name"]);
953
+ if (key === void 0) continue;
954
+ const envValue = readEntryValue(entry, ["value", "targetValue", "content"]) ?? "";
955
+ result[key] = envValue;
956
+ }
957
+ return result;
958
+ }
959
+ function parseCloudflarePayload(value) {
960
+ const entries = Array.isArray(value) ? value : isRecord(value) && Array.isArray(value.result) ? value.result : [];
961
+ const result = {};
962
+ for (const entry of entries) {
963
+ if (!isRecord(entry)) continue;
964
+ const key = readEntryValue(entry, ["name", "key"]);
965
+ if (key === void 0) continue;
966
+ const envValue = readEntryValue(entry, ["text", "value", "secret"]) ?? "";
967
+ result[key] = envValue;
968
+ }
969
+ return result;
970
+ }
971
+ function parseAwsPayload(value) {
972
+ const entries = isRecord(value) && Array.isArray(value.Parameters) ? value.Parameters : [];
973
+ const result = {};
974
+ for (const entry of entries) {
975
+ if (!isRecord(entry)) continue;
976
+ const name = readEntryValue(entry, ["Name", "name"]);
977
+ if (name === void 0) continue;
978
+ const key = name.split("/").filter((part) => part.length > 0).pop() ?? name;
979
+ const envValue = readEntryValue(entry, ["Value", "value"]) ?? "";
980
+ result[key] = envValue;
981
+ }
982
+ return result;
983
+ }
984
+ function parseProviderPayload(provider, value) {
985
+ if (provider === "vercel") return parseVercelPayload(value);
986
+ if (provider === "cloudflare") return parseCloudflarePayload(value);
987
+ return parseAwsPayload(value);
988
+ }
989
+ async function loadCloudSource(options) {
990
+ const resolvedPath = path6__default.default.resolve(options.filePath);
991
+ const raw = await promises.readFile(resolvedPath, "utf8");
992
+ const parsed = JSON.parse(raw);
993
+ return parseProviderPayload(options.provider, parsed);
994
+ }
995
+ var SECRET_KEY_RE = /(SECRET|TOKEN|PASSWORD|PRIVATE|API_KEY|ACCESS_KEY|CLIENT_SECRET)/i;
996
+ var CONTRACT_FILE_NAMES2 = ["env.contract.ts", "env.contract.mjs", "env.contract.js"];
997
+ function isRecord2(value) {
998
+ return typeof value === "object" && value !== null && !Array.isArray(value);
999
+ }
1000
+ function isExpected(value) {
1001
+ if (!isRecord2(value)) return false;
1002
+ const typeValue = value.type;
1003
+ if (typeof typeValue !== "string") return false;
1004
+ const validTypes = /* @__PURE__ */ new Set([
1005
+ "string",
1006
+ "number",
1007
+ "boolean",
1008
+ "enum",
1009
+ "url",
1010
+ "email",
1011
+ "json",
1012
+ "semver",
1013
+ "unknown"
1014
+ ]);
1015
+ if (!validTypes.has(typeValue)) return false;
1016
+ if (typeValue === "enum") {
1017
+ return Array.isArray(value.values) && value.values.every((item) => typeof item === "string");
1018
+ }
1019
+ return true;
1020
+ }
1021
+ function isEnvContractVariable(value) {
1022
+ if (!isRecord2(value)) return false;
1023
+ if (!isExpected(value.expected)) return false;
1024
+ if (typeof value.required !== "boolean") return false;
1025
+ if (typeof value.clientSide !== "boolean") return false;
1026
+ if (value.description !== void 0 && typeof value.description !== "string") return false;
1027
+ if (value.secret !== void 0 && typeof value.secret !== "boolean") return false;
1028
+ return true;
1029
+ }
1030
+ function isEnvContract(value) {
1031
+ if (!isRecord2(value)) return false;
1032
+ if (value.schemaVersion !== 1) return false;
1033
+ if (!isRecord2(value.variables)) return false;
1034
+ return Object.values(value.variables).every((item) => isEnvContractVariable(item));
1035
+ }
1036
+ function isLegacyContract(value) {
1037
+ if (!isRecord2(value)) return false;
1038
+ if (!Array.isArray(value.vars)) return false;
1039
+ return value.vars.every((entry) => isRecord2(entry) && typeof entry.name === "string");
1040
+ }
1041
+ function mapEnvVarTypeToExpected(type) {
1042
+ if (type === "number") return { type: "number" };
1043
+ if (type === "boolean") return { type: "boolean" };
1044
+ if (type === "url") return { type: "url" };
1045
+ if (type === "email") return { type: "email" };
1046
+ if (type === "json") return { type: "json" };
1047
+ if (type === "semver") return { type: "semver" };
1048
+ if (type === "unknown") return { type: "unknown" };
1049
+ return { type: "string" };
1050
+ }
1051
+ function shouldTreatAsSecret(key) {
1052
+ return SECRET_KEY_RE.test(key);
1053
+ }
1054
+ function toExpectedFromLegacyEntry(entry) {
1055
+ if (entry.enumValues !== void 0 && entry.enumValues.length > 0) {
1056
+ return { type: "enum", values: entry.enumValues };
1057
+ }
1058
+ if (entry.expectedType === "number") {
1059
+ return {
1060
+ type: "number",
1061
+ ...entry.constraints?.min !== void 0 && { min: entry.constraints.min },
1062
+ ...entry.constraints?.max !== void 0 && { max: entry.constraints.max }
1063
+ };
1064
+ }
1065
+ if (entry.expectedType === "boolean") return { type: "boolean" };
1066
+ if (entry.expectedType === "url") return { type: "url" };
1067
+ if (entry.expectedType === "email") return { type: "email" };
1068
+ if (entry.expectedType === "json") return { type: "json" };
1069
+ if (entry.expectedType === "semver") return { type: "semver" };
1070
+ if (entry.expectedType === "unknown") return { type: "unknown" };
1071
+ return { type: "string" };
1072
+ }
1073
+ function convertLegacyContract(contract) {
1074
+ const variables = {};
1075
+ for (const entry of contract.vars) {
1076
+ const clientSide = entry.runtime === "client" || entry.name.startsWith("NEXT_PUBLIC_");
1077
+ variables[entry.name] = {
1078
+ expected: toExpectedFromLegacyEntry(entry),
1079
+ required: entry.required,
1080
+ clientSide,
1081
+ ...entry.description !== void 0 && { description: entry.description },
1082
+ ...(entry.isSecret ?? shouldTreatAsSecret(entry.name)) && { secret: true }
1083
+ };
1084
+ }
1085
+ return {
1086
+ schemaVersion: 1,
1087
+ variables
1088
+ };
1089
+ }
1090
+ function buildContractFromExample(examplePath) {
1091
+ const parsed = parseEnvFile(examplePath);
1092
+ const variables = {};
1093
+ for (const variable of parsed.vars) {
1094
+ const effectiveType = variable.annotatedType ?? variable.inferredType;
1095
+ variables[variable.key] = {
1096
+ expected: mapEnvVarTypeToExpected(effectiveType),
1097
+ required: variable.isRequired,
1098
+ clientSide: variable.isClientSide,
1099
+ ...variable.description !== void 0 && { description: variable.description },
1100
+ ...shouldTreatAsSecret(variable.key) && { secret: true }
1101
+ };
1102
+ }
1103
+ return {
1104
+ schemaVersion: 1,
1105
+ variables
1106
+ };
1107
+ }
1108
+ function findDefaultContractPath(cwd) {
1109
+ for (const fileName of CONTRACT_FILE_NAMES2) {
1110
+ const candidatePath = path6__default.default.resolve(cwd, fileName);
1111
+ if (fs.existsSync(candidatePath)) return candidatePath;
1112
+ }
1113
+ return void 0;
1114
+ }
1115
+ async function loadValidationContract(options) {
1116
+ const { fallbackExamplePath, contractPath, cwd = process.cwd() } = options;
1117
+ const discoveredContractPath = findDefaultContractPath(cwd);
1118
+ const resolvedContractPath = contractPath !== void 0 ? path6__default.default.resolve(cwd, contractPath) : discoveredContractPath;
1119
+ if (resolvedContractPath !== void 0 && fs.existsSync(resolvedContractPath)) {
1120
+ const moduleUrl = url.pathToFileURL(resolvedContractPath).href;
1121
+ const moduleValue = await import(moduleUrl);
1122
+ const candidate = moduleValue.default ?? moduleValue.contract;
1123
+ if (isEnvContract(candidate)) {
1124
+ return candidate;
1125
+ }
1126
+ if (isLegacyContract(candidate)) {
1127
+ return convertLegacyContract(candidate);
1128
+ }
1129
+ throw new Error(
1130
+ `Invalid contract at ${resolvedContractPath}. Export default must match EnvContract.`
1131
+ );
1132
+ }
1133
+ return buildContractFromExample(path6__default.default.resolve(cwd, fallbackExamplePath));
1134
+ }
1135
+ function isRecord3(value) {
1136
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1137
+ }
1138
+ function isPlugin(value) {
1139
+ if (!isRecord3(value)) return false;
1140
+ if (typeof value.name !== "string") return false;
1141
+ if (value.transformContract !== void 0 && typeof value.transformContract !== "function") {
1142
+ return false;
1143
+ }
1144
+ if (value.transformSource !== void 0 && typeof value.transformSource !== "function") {
1145
+ return false;
1146
+ }
1147
+ if (value.transformReport !== void 0 && typeof value.transformReport !== "function") {
1148
+ return false;
1149
+ }
1150
+ return true;
1151
+ }
1152
+ async function loadPluginFromPath(pluginPath, cwd) {
1153
+ const resolvedPath = path6__default.default.resolve(cwd, pluginPath);
1154
+ const moduleValue = await import(url.pathToFileURL(resolvedPath).href);
1155
+ const candidate = moduleValue.default ?? moduleValue.plugin;
1156
+ if (isPlugin(candidate)) return candidate;
1157
+ throw new Error(`Invalid plugin at ${resolvedPath}. Expected a plugin object export.`);
1158
+ }
1159
+ async function loadPlugins(options) {
1160
+ const cwd = options.cwd ?? process.cwd();
1161
+ const plugins = [];
1162
+ const references = [...options.configPlugins ?? [], ...options.pluginPaths];
1163
+ for (const reference of references) {
1164
+ if (typeof reference === "string") {
1165
+ plugins.push(await loadPluginFromPath(reference, cwd));
1166
+ continue;
1167
+ }
1168
+ if (isPlugin(reference)) {
1169
+ plugins.push(reference);
1170
+ continue;
1171
+ }
1172
+ throw new Error("Invalid plugin reference in configuration.");
1173
+ }
1174
+ return plugins;
1175
+ }
1176
+ function applyContractPlugins(contract, plugins) {
1177
+ let next = contract;
1178
+ for (const plugin of plugins) {
1179
+ if (plugin.transformContract === void 0) continue;
1180
+ next = plugin.transformContract(next);
1181
+ }
1182
+ return next;
1183
+ }
1184
+ function applySourcePlugins(params, plugins) {
1185
+ let next = params.values;
1186
+ for (const plugin of plugins) {
1187
+ if (plugin.transformSource === void 0) continue;
1188
+ next = plugin.transformSource({ environment: params.environment, values: next });
1189
+ }
1190
+ return next;
1191
+ }
1192
+ function applyReportPlugins(report, plugins) {
1193
+ let next = report;
1194
+ for (const plugin of plugins) {
1195
+ if (plugin.transformReport === void 0) continue;
1196
+ next = plugin.transformReport(next);
1197
+ }
1198
+ return next;
1199
+ }
1200
+
1201
+ // src/validation/engine.ts
1202
+ var EMAIL_RE = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
1203
+ var SEMVER_RE2 = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?(?:\+[0-9A-Za-z-.]+)?$/;
1204
+ function detectReceivedType(value) {
1205
+ const normalized = value.trim();
1206
+ if (normalized.length === 0) return "unknown";
1207
+ if (["true", "false", "1", "0", "yes", "no"].includes(normalized.toLowerCase())) return "boolean";
1208
+ if (!Number.isNaN(Number(normalized)) && Number.isFinite(Number(normalized))) return "number";
1209
+ if (SEMVER_RE2.test(normalized)) return "semver";
1210
+ try {
1211
+ const url = new URL(normalized);
1212
+ if (url.protocol.length > 0) return "url";
1213
+ } catch {
1214
+ }
1215
+ if (EMAIL_RE.test(normalized)) return "email";
1216
+ try {
1217
+ const parsed = JSON.parse(normalized);
1218
+ if (typeof parsed === "object" && parsed !== null) return "json";
1219
+ } catch {
1220
+ }
1221
+ return "string";
1222
+ }
1223
+ function validateValueAgainstExpected(expected, rawValue) {
1224
+ const normalized = rawValue.trim();
1225
+ const receivedType = detectReceivedType(normalized);
1226
+ if (expected.type === "unknown") return { isValid: true, receivedType };
1227
+ if (expected.type === "string") return { isValid: true, receivedType };
1228
+ if (expected.type === "number") {
1229
+ const parsed = Number(normalized);
1230
+ if (Number.isNaN(parsed) || !Number.isFinite(parsed)) {
1231
+ return { isValid: false, receivedType, issueType: "invalid_type" };
1232
+ }
1233
+ if (expected.min !== void 0 && parsed < expected.min) {
1234
+ return { isValid: false, receivedType, issueType: "invalid_value" };
1235
+ }
1236
+ if (expected.max !== void 0 && parsed > expected.max) {
1237
+ return { isValid: false, receivedType, issueType: "invalid_value" };
1238
+ }
1239
+ return { isValid: true, receivedType };
1240
+ }
1241
+ if (expected.type === "boolean") {
1242
+ if (!["true", "false", "1", "0", "yes", "no"].includes(normalized.toLowerCase())) {
1243
+ return { isValid: false, receivedType, issueType: "invalid_type" };
1244
+ }
1245
+ return { isValid: true, receivedType };
1246
+ }
1247
+ if (expected.type === "enum") {
1248
+ if (!expected.values.includes(normalized)) {
1249
+ return { isValid: false, receivedType, issueType: "invalid_value" };
1250
+ }
1251
+ return { isValid: true, receivedType };
1252
+ }
1253
+ if (expected.type === "url") {
1254
+ try {
1255
+ const value = new URL(normalized);
1256
+ if (value.protocol.length === 0)
1257
+ return { isValid: false, receivedType, issueType: "invalid_type" };
1258
+ return { isValid: true, receivedType };
1259
+ } catch {
1260
+ return { isValid: false, receivedType, issueType: "invalid_type" };
1261
+ }
1262
+ }
1263
+ if (expected.type === "email") {
1264
+ if (!EMAIL_RE.test(normalized))
1265
+ return { isValid: false, receivedType, issueType: "invalid_type" };
1266
+ return { isValid: true, receivedType };
1267
+ }
1268
+ if (expected.type === "json") {
1269
+ try {
1270
+ const parsed = JSON.parse(normalized);
1271
+ if (typeof parsed === "object" && parsed !== null) return { isValid: true, receivedType };
1272
+ return { isValid: false, receivedType, issueType: "invalid_type" };
1273
+ } catch {
1274
+ return { isValid: false, receivedType, issueType: "invalid_type" };
1275
+ }
1276
+ }
1277
+ if (expected.type === "semver") {
1278
+ if (!SEMVER_RE2.test(normalized))
1279
+ return { isValid: false, receivedType, issueType: "invalid_value" };
1280
+ return { isValid: true, receivedType };
1281
+ }
1282
+ return { isValid: true, receivedType };
1283
+ }
1284
+ function toIssueCode(issueType) {
1285
+ if (issueType === "missing") return "ENV_MISSING";
1286
+ if (issueType === "extra") return "ENV_EXTRA";
1287
+ if (issueType === "invalid_type") return "ENV_INVALID_TYPE";
1288
+ if (issueType === "invalid_value") return "ENV_INVALID_VALUE";
1289
+ if (issueType === "conflict") return "ENV_CONFLICT";
1290
+ return "ENV_SECRET_EXPOSED";
1291
+ }
1292
+ function toIssueValue(value, debugValues) {
1293
+ if (!debugValues) return null;
1294
+ if (value === void 0) return null;
1295
+ return value;
1296
+ }
1297
+ function createIssue(params) {
1298
+ return {
1299
+ code: toIssueCode(params.type),
1300
+ type: params.type,
1301
+ severity: params.severity,
1302
+ key: params.key,
1303
+ environment: params.environment,
1304
+ message: params.message,
1305
+ value: toIssueValue(params.value, params.debugValues),
1306
+ ...params.expected !== void 0 && { expected: params.expected },
1307
+ ...params.receivedType !== void 0 && { receivedType: params.receivedType }
1308
+ };
1309
+ }
1310
+ function dedupeIssues(issues) {
1311
+ const seen = /* @__PURE__ */ new Set();
1312
+ const unique = [];
1313
+ for (const issue of issues) {
1314
+ const token = [
1315
+ issue.code,
1316
+ issue.type,
1317
+ issue.severity,
1318
+ issue.environment,
1319
+ issue.key,
1320
+ issue.message,
1321
+ issue.receivedType ?? ""
1322
+ ].join("|");
1323
+ if (seen.has(token)) continue;
1324
+ seen.add(token);
1325
+ unique.push(issue);
1326
+ }
1327
+ return unique;
1328
+ }
1329
+ function buildReport(env, issues, recommendations) {
1330
+ const dedupedIssues = dedupeIssues(issues);
1331
+ const errors = dedupedIssues.filter((item) => item.severity === "error").length;
1332
+ const warnings = dedupedIssues.filter((item) => item.severity === "warning").length;
1333
+ const status = errors > 0 ? "fail" : "ok";
1334
+ return {
1335
+ schemaVersion: 1,
1336
+ status,
1337
+ summary: {
1338
+ errors,
1339
+ warnings,
1340
+ total: dedupedIssues.length
1341
+ },
1342
+ issues: dedupedIssues,
1343
+ meta: {
1344
+ env,
1345
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1346
+ },
1347
+ ...recommendations !== void 0 && recommendations.length > 0 && { recommendations }
1348
+ };
1349
+ }
1350
+ function isClientSecret(variable, key) {
1351
+ return variable.secret === true && (variable.clientSide || key.startsWith("NEXT_PUBLIC_"));
1352
+ }
1353
+ function validateAgainstContract(options) {
1354
+ const issues = [];
1355
+ const contractKeys = new Set(Object.keys(options.contract.variables));
1356
+ for (const [key, variable] of Object.entries(options.contract.variables)) {
1357
+ const value = options.values[key];
1358
+ const hasValue = value !== void 0 && value.trim().length > 0;
1359
+ if (variable.required && !hasValue) {
1360
+ issues.push(
1361
+ createIssue({
1362
+ type: "missing",
1363
+ severity: "error",
1364
+ key,
1365
+ environment: options.environment,
1366
+ message: `Required variable ${key} is missing.`,
1367
+ debugValues: options.debugValues,
1368
+ expected: variable.expected
1369
+ })
1370
+ );
1371
+ continue;
1372
+ }
1373
+ if (!hasValue) continue;
1374
+ const validation = validateValueAgainstExpected(variable.expected, value);
1375
+ if (!validation.isValid) {
1376
+ issues.push(
1377
+ createIssue({
1378
+ type: validation.issueType,
1379
+ severity: "error",
1380
+ key,
1381
+ environment: options.environment,
1382
+ message: validation.issueType === "invalid_type" ? `Variable ${key} has invalid type.` : `Variable ${key} has invalid value.`,
1383
+ value,
1384
+ debugValues: options.debugValues,
1385
+ expected: variable.expected,
1386
+ receivedType: validation.receivedType
1387
+ })
1388
+ );
1389
+ }
1390
+ if (isClientSecret(variable, key)) {
1391
+ issues.push(
1392
+ createIssue({
1393
+ type: "secret_exposed",
1394
+ severity: "error",
1395
+ key,
1396
+ environment: options.environment,
1397
+ message: `Secret variable ${key} is marked as client-side.`,
1398
+ value,
1399
+ debugValues: options.debugValues,
1400
+ expected: variable.expected
1401
+ })
1402
+ );
1403
+ }
1404
+ }
1405
+ for (const [key, value] of Object.entries(options.values)) {
1406
+ if (contractKeys.has(key)) continue;
1407
+ const severity = options.strict ? "error" : "warning";
1408
+ issues.push(
1409
+ createIssue({
1410
+ type: "extra",
1411
+ severity,
1412
+ key,
1413
+ environment: options.environment,
1414
+ message: `Variable ${key} is not defined in the contract.`,
1415
+ value,
1416
+ debugValues: options.debugValues
1417
+ })
1418
+ );
1419
+ }
1420
+ return buildReport(options.environment, issues);
1421
+ }
1422
+ function collectUnionKeys(contract, sources) {
1423
+ const union = new Set(Object.keys(contract.variables));
1424
+ for (const source of Object.values(sources)) {
1425
+ for (const key of Object.keys(source)) {
1426
+ union.add(key);
1427
+ }
1428
+ }
1429
+ return union;
1430
+ }
1431
+ function diffEnvironmentSources(options) {
1432
+ const issues = [];
1433
+ const sourceNames = Object.keys(options.sources);
1434
+ const unionKeys = collectUnionKeys(options.contract, options.sources);
1435
+ for (const key of unionKeys) {
1436
+ const variable = options.contract.variables[key];
1437
+ const valuesBySource = sourceNames.map((sourceName) => ({
1438
+ sourceName,
1439
+ value: options.sources[sourceName]?.[key]
1440
+ }));
1441
+ const present = valuesBySource.filter(
1442
+ (entry) => entry.value !== void 0 && entry.value !== ""
1443
+ );
1444
+ const missing = valuesBySource.filter(
1445
+ (entry) => entry.value === void 0 || entry.value === ""
1446
+ );
1447
+ if (present.length === 0 && variable?.required === true) {
1448
+ for (const entry of missing) {
1449
+ issues.push(
1450
+ createIssue({
1451
+ type: "missing",
1452
+ severity: "error",
1453
+ key,
1454
+ environment: entry.sourceName,
1455
+ message: `Required variable ${key} is missing in ${entry.sourceName}.`,
1456
+ debugValues: options.debugValues,
1457
+ expected: variable.expected
1458
+ })
1459
+ );
1460
+ }
1461
+ continue;
1462
+ }
1463
+ if (present.length > 0) {
1464
+ for (const entry of missing) {
1465
+ issues.push(
1466
+ createIssue({
1467
+ type: "missing",
1468
+ severity: "error",
1469
+ key,
1470
+ environment: entry.sourceName,
1471
+ message: `Variable ${key} is missing in ${entry.sourceName}.`,
1472
+ debugValues: options.debugValues,
1473
+ ...variable !== void 0 && { expected: variable.expected }
1474
+ })
1475
+ );
1476
+ }
1477
+ }
1478
+ const typeBySource = /* @__PURE__ */ new Map();
1479
+ for (const entry of present) {
1480
+ const detected = detectReceivedType(entry.value ?? "");
1481
+ typeBySource.set(entry.sourceName, detected);
1482
+ }
1483
+ if (new Set(typeBySource.values()).size > 1) {
1484
+ for (const [sourceName, detectedType] of typeBySource.entries()) {
1485
+ issues.push(
1486
+ createIssue({
1487
+ type: "conflict",
1488
+ severity: "error",
1489
+ key,
1490
+ environment: sourceName,
1491
+ message: `Variable ${key} has conflicting inferred type across environments.`,
1492
+ debugValues: options.debugValues,
1493
+ receivedType: detectedType,
1494
+ ...options.sources[sourceName]?.[key] !== void 0 && {
1495
+ value: options.sources[sourceName]?.[key]
1496
+ },
1497
+ ...variable !== void 0 && { expected: variable.expected }
1498
+ })
1499
+ );
1500
+ }
1501
+ }
1502
+ for (const entry of present) {
1503
+ if (entry.value === void 0) continue;
1504
+ if (variable === void 0) {
1505
+ const severity = options.strict ? "error" : "warning";
1506
+ issues.push(
1507
+ createIssue({
1508
+ type: "extra",
1509
+ severity,
1510
+ key,
1511
+ environment: entry.sourceName,
1512
+ message: `Variable ${key} is not defined in the contract.`,
1513
+ value: entry.value,
1514
+ debugValues: options.debugValues
1515
+ })
1516
+ );
1517
+ continue;
1518
+ }
1519
+ const validation = validateValueAgainstExpected(variable.expected, entry.value);
1520
+ if (!validation.isValid) {
1521
+ issues.push(
1522
+ createIssue({
1523
+ type: validation.issueType,
1524
+ severity: "error",
1525
+ key,
1526
+ environment: entry.sourceName,
1527
+ message: validation.issueType === "invalid_type" ? `Variable ${key} has invalid type in ${entry.sourceName}.` : `Variable ${key} has invalid value in ${entry.sourceName}.`,
1528
+ value: entry.value,
1529
+ debugValues: options.debugValues,
1530
+ expected: variable.expected,
1531
+ receivedType: validation.receivedType
1532
+ })
1533
+ );
1534
+ }
1535
+ if (isClientSecret(variable, key)) {
1536
+ issues.push(
1537
+ createIssue({
1538
+ type: "secret_exposed",
1539
+ severity: "error",
1540
+ key,
1541
+ environment: entry.sourceName,
1542
+ message: `Secret variable ${key} is marked as client-side.`,
1543
+ value: entry.value,
1544
+ debugValues: options.debugValues,
1545
+ expected: variable.expected
1546
+ })
1547
+ );
1548
+ }
1549
+ }
1550
+ }
1551
+ return buildReport("diff", issues);
1552
+ }
1553
+ function buildRecommendations(issues) {
1554
+ const codes = new Set(issues.map((item) => item.code));
1555
+ const recommendations = [];
1556
+ if (codes.has("ENV_MISSING")) {
1557
+ recommendations.push("Add missing required variables to each target environment.");
1558
+ }
1559
+ if (codes.has("ENV_EXTRA")) {
1560
+ recommendations.push(
1561
+ "Remove undeclared variables or add them to env.contract.ts intentionally."
1562
+ );
1563
+ }
1564
+ if (codes.has("ENV_INVALID_TYPE") || codes.has("ENV_INVALID_VALUE")) {
1565
+ recommendations.push(
1566
+ "Normalize variable values so they match the expected contract types and constraints."
1567
+ );
1568
+ }
1569
+ if (codes.has("ENV_CONFLICT")) {
1570
+ recommendations.push("Align variable semantics across environments to avoid drift.");
1571
+ }
1572
+ if (codes.has("ENV_SECRET_EXPOSED")) {
1573
+ recommendations.push(
1574
+ "Move secret variables to server-only scope and avoid NEXT_PUBLIC_ exposure for secrets."
1575
+ );
1576
+ }
1577
+ return recommendations;
1578
+ }
1579
+ function buildDoctorReport(options) {
1580
+ const merged = [...options.checkReport.issues, ...options.diffReport.issues];
1581
+ const recommendations = buildRecommendations(merged);
1582
+ return buildReport("doctor", merged, recommendations);
1583
+ }
1584
+ function stripWrappingQuotes(value) {
1585
+ if (value.length < 2) return value;
1586
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1587
+ return value.slice(1, -1);
1588
+ }
1589
+ return value;
1590
+ }
1591
+ function parseEnvSourceContent(content) {
1592
+ const result = {};
1593
+ const lines = content.split("\n");
1594
+ for (const line of lines) {
1595
+ const trimmed = line.trim();
1596
+ if (trimmed.length === 0 || trimmed.startsWith("#")) continue;
1597
+ const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.exec(trimmed);
1598
+ if (match === null) continue;
1599
+ const key = match[1] ?? "";
1600
+ const rawValue = match[2] ?? "";
1601
+ result[key] = stripWrappingQuotes(rawValue.trim());
1602
+ }
1603
+ return result;
1604
+ }
1605
+ async function loadEnvSource(options) {
1606
+ const resolvedPath = path6__default.default.resolve(options.filePath);
1607
+ try {
1608
+ const content = await promises.readFile(resolvedPath, "utf8");
1609
+ return parseEnvSourceContent(content);
1610
+ } catch (errorValue) {
1611
+ if (options.allowMissing === true && errorValue instanceof Error && "code" in errorValue && errorValue.code === "ENOENT") {
1612
+ return {};
1613
+ }
1614
+ throw errorValue;
1615
+ }
1616
+ }
1617
+ function toJsonString(report, mode) {
1618
+ if (mode === "pretty") return `${JSON.stringify(report, null, 2)}
1619
+ `;
1620
+ return `${JSON.stringify(report)}
1621
+ `;
1622
+ }
1623
+ function formatIssue(issue) {
1624
+ const expected = issue.expected !== void 0 ? ` expected=${issue.expected.type}` : "";
1625
+ const received = issue.receivedType !== void 0 ? ` received=${issue.receivedType}` : "";
1626
+ return `${issue.severity.toUpperCase()} [${issue.code}] ${issue.environment}:${issue.key} ${issue.message}${expected}${received}`;
1627
+ }
1628
+ function formatHumanReport(report) {
1629
+ const lines = [];
1630
+ lines.push(
1631
+ `Status: ${report.status.toUpperCase()} (errors=${report.summary.errors}, warnings=${report.summary.warnings}, total=${report.summary.total})`
1632
+ );
1633
+ if (report.issues.length > 0) {
1634
+ lines.push("");
1635
+ lines.push("Issues:");
1636
+ for (const issue of report.issues) {
1637
+ lines.push(`- ${formatIssue(issue)}`);
1638
+ }
1639
+ }
1640
+ if (report.recommendations !== void 0 && report.recommendations.length > 0) {
1641
+ lines.push("");
1642
+ lines.push("Recommendations:");
1643
+ for (const recommendation of report.recommendations) {
1644
+ lines.push(`- ${recommendation}`);
1645
+ }
1646
+ }
1647
+ return `${lines.join("\n")}
1648
+ `;
1649
+ }
1650
+ async function persistJsonOutput(outputFile, report) {
1651
+ const resolvedPath = path6__default.default.resolve(outputFile);
1652
+ await promises.mkdir(path6__default.default.dirname(resolvedPath), { recursive: true });
1653
+ await promises.writeFile(resolvedPath, `${JSON.stringify(report, null, 2)}
1654
+ `, "utf8");
1655
+ }
1656
+ async function emitValidationReport(options) {
1657
+ const { report, outputFile, jsonMode } = options;
1658
+ if (outputFile !== void 0) {
1659
+ await persistJsonOutput(outputFile, report);
1660
+ }
1661
+ if (jsonMode === "off") {
1662
+ process.stdout.write(formatHumanReport(report));
1663
+ return;
1664
+ }
1665
+ process.stdout.write(toJsonString(report, jsonMode));
1666
+ }
1667
+
1668
+ // src/validation-command.ts
1669
+ var HELP_TEXT = {
1670
+ check: [
1671
+ "Usage: env-typegen check [options]",
1672
+ "",
1673
+ "Options:",
1674
+ " --env <path> Environment file to validate (default: .env)",
1675
+ " --contract <path> Contract file path (default: env.contract.ts)",
1676
+ " --example <path> Fallback .env.example used to bootstrap contract",
1677
+ " --strict Validate extras as errors (default: true)",
1678
+ " --no-strict Downgrade extras to warnings",
1679
+ " --json Emit machine-readable JSON report",
1680
+ " --json=pretty Emit pretty JSON report",
1681
+ " --output-file <path> Persist JSON report to a file",
1682
+ " --debug-values Include raw values in issues (unsafe for CI logs)",
1683
+ " --cloud-provider <name> vercel | cloudflare | aws",
1684
+ " --cloud-file <path> Cloud snapshot JSON file",
1685
+ " --plugin <path> Plugin module path (repeatable)",
1686
+ " -c, --config <path> Config file path",
1687
+ " -h, --help Show this help"
1688
+ ].join("\n"),
1689
+ diff: [
1690
+ "Usage: env-typegen diff [options]",
1691
+ "",
1692
+ "Options:",
1693
+ " --targets <list> Comma-separated targets (default: .env,.env.example,.env.production)",
1694
+ " --contract <path> Contract file path (default: env.contract.ts)",
1695
+ " --example <path> Fallback .env.example used to bootstrap contract",
1696
+ " --strict Validate extras as errors (default: true)",
1697
+ " --no-strict Downgrade extras to warnings",
1698
+ " --json Emit machine-readable JSON report",
1699
+ " --json=pretty Emit pretty JSON report",
1700
+ " --output-file <path> Persist JSON report to a file",
1701
+ " --debug-values Include raw values in issues (unsafe for CI logs)",
1702
+ " --cloud-provider <name> vercel | cloudflare | aws",
1703
+ " --cloud-file <path> Cloud snapshot JSON file added to diff sources",
1704
+ " --plugin <path> Plugin module path (repeatable)",
1705
+ " -c, --config <path> Config file path",
1706
+ " -h, --help Show this help"
1707
+ ].join("\n"),
1708
+ doctor: [
1709
+ "Usage: env-typegen doctor [options]",
1710
+ "",
1711
+ "Options:",
1712
+ " --env <path> Environment file to validate (default: .env)",
1713
+ " --targets <list> Comma-separated targets for drift analysis",
1714
+ " --contract <path> Contract file path (default: env.contract.ts)",
1715
+ " --example <path> Fallback .env.example used to bootstrap contract",
1716
+ " --strict Validate extras as errors (default: true)",
1717
+ " --no-strict Downgrade extras to warnings",
1718
+ " --json Emit machine-readable JSON report",
1719
+ " --json=pretty Emit pretty JSON report",
1720
+ " --output-file <path> Persist JSON report to a file",
1721
+ " --debug-values Include raw values in issues (unsafe for CI logs)",
1722
+ " --cloud-provider <name> vercel | cloudflare | aws",
1723
+ " --cloud-file <path> Cloud snapshot JSON file",
1724
+ " --plugin <path> Plugin module path (repeatable)",
1725
+ " -c, --config <path> Config file path",
1726
+ " -h, --help Show this help"
1727
+ ].join("\n")
1728
+ };
1729
+ function resolveConfigRelative(value, configDir) {
1730
+ if (value === void 0 || path6__default.default.isAbsolute(value)) return value;
1731
+ return path6__default.default.resolve(configDir, value);
1732
+ }
1733
+ function resolvePluginReference(reference, configDir) {
1734
+ if (typeof reference === "string" && !path6__default.default.isAbsolute(reference)) {
1735
+ return path6__default.default.resolve(configDir, reference);
1736
+ }
1737
+ return reference;
1738
+ }
1739
+ function applyConfigPaths(config, configDir) {
1740
+ let input;
1741
+ if (Array.isArray(config.input)) {
1742
+ input = config.input.map((item) => resolveConfigRelative(item, configDir) ?? item);
1743
+ } else {
1744
+ input = resolveConfigRelative(config.input, configDir);
1745
+ }
1746
+ const output = resolveConfigRelative(config.output, configDir);
1747
+ const schemaFile = resolveConfigRelative(config.schemaFile, configDir);
1748
+ return {
1749
+ ...config,
1750
+ ...input !== void 0 && { input },
1751
+ ...output !== void 0 && { output },
1752
+ ...schemaFile !== void 0 && { schemaFile },
1753
+ ...config.diffTargets !== void 0 && {
1754
+ diffTargets: config.diffTargets.map(
1755
+ (target) => resolveConfigRelative(target, configDir) ?? target
1756
+ )
1757
+ },
1758
+ ...config.plugins !== void 0 && {
1759
+ plugins: config.plugins.map((reference) => resolvePluginReference(reference, configDir))
1760
+ }
1761
+ };
1762
+ }
1763
+ async function loadCommandConfig(configPath) {
1764
+ if (configPath === void 0) {
1765
+ return loadConfig(process.cwd());
1766
+ }
1767
+ const resolvedPath = path6__default.default.resolve(configPath);
1768
+ const configDir = path6__default.default.dirname(resolvedPath);
1769
+ const moduleValue = await import(url.pathToFileURL(resolvedPath).href);
1770
+ if (moduleValue.default === void 0) return void 0;
1771
+ return applyConfigPaths(moduleValue.default, configDir);
1772
+ }
1773
+ function preprocessJsonArguments(argv) {
1774
+ const normalizedArgs = [];
1775
+ let assignedMode = "off";
1776
+ for (const item of argv) {
1777
+ if (item === "--json=pretty") {
1778
+ normalizedArgs.push("--json");
1779
+ assignedMode = "pretty";
1780
+ continue;
1781
+ }
1782
+ if (item === "--json=compact") {
1783
+ normalizedArgs.push("--json");
1784
+ assignedMode = "compact";
1785
+ continue;
1786
+ }
1787
+ normalizedArgs.push(item);
1788
+ }
1789
+ return { normalizedArgs, assignedMode };
1790
+ }
1791
+ function parseValidationArgs(argv) {
1792
+ const { normalizedArgs, assignedMode } = preprocessJsonArguments(argv);
1793
+ const { values } = util.parseArgs({
1794
+ args: normalizedArgs,
1795
+ options: {
1796
+ env: { type: "string", multiple: true },
1797
+ targets: { type: "string" },
1798
+ contract: { type: "string" },
1799
+ example: { type: "string" },
1800
+ strict: { type: "boolean" },
1801
+ "no-strict": { type: "boolean" },
1802
+ json: { type: "boolean" },
1803
+ "output-file": { type: "string" },
1804
+ "debug-values": { type: "boolean" },
1805
+ "cloud-provider": { type: "string" },
1806
+ "cloud-file": { type: "string" },
1807
+ plugin: { type: "string", multiple: true },
1808
+ config: { type: "string", short: "c" },
1809
+ help: { type: "boolean", short: "h" }
1810
+ }
1811
+ });
1812
+ const castValues = values;
1813
+ const jsonMode = castValues.json === true ? assignedMode === "off" ? "compact" : assignedMode : "off";
1814
+ return { values: castValues, jsonMode };
1815
+ }
1816
+ function resolveStrict(values, fileConfig) {
1817
+ if (values["no-strict"] === true) return false;
1818
+ if (values.strict !== void 0) return values.strict;
1819
+ if (fileConfig?.strict !== void 0) return fileConfig.strict;
1820
+ return true;
1821
+ }
1822
+ function parseCloudProvider(value) {
1823
+ if (value === void 0) return void 0;
1824
+ if (value === "vercel" || value === "cloudflare" || value === "aws") return value;
1825
+ throw new Error(`Unknown cloud provider: ${value}. Valid: vercel, cloudflare, aws`);
1826
+ }
1827
+ function parseTargets(values, fileConfig) {
1828
+ const fromCli = values.targets;
1829
+ if (fromCli !== void 0) {
1830
+ return fromCli.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
1831
+ }
1832
+ if (fileConfig?.diffTargets !== void 0 && fileConfig.diffTargets.length > 0) {
1833
+ return fileConfig.diffTargets;
1834
+ }
1835
+ return [".env", ".env.example", ".env.production"];
1836
+ }
1837
+ async function prepareCommonContext(values) {
1838
+ const fileConfig = await loadCommandConfig(values.config);
1839
+ const strict = resolveStrict(values, fileConfig);
1840
+ const debugValues = values["debug-values"] ?? false;
1841
+ const contractPath = values.contract ?? fileConfig?.schemaFile;
1842
+ const fallbackExamplePath = values.example ?? ".env.example";
1843
+ const cloudProvider = parseCloudProvider(values["cloud-provider"]);
1844
+ const cloudFile = values["cloud-file"];
1845
+ const pluginLoadOptions = {
1846
+ pluginPaths: values.plugin ?? [],
1847
+ cwd: process.cwd(),
1848
+ ...fileConfig?.plugins !== void 0 && { configPlugins: fileConfig.plugins }
1849
+ };
1850
+ const plugins = await loadPlugins(pluginLoadOptions);
1851
+ return {
1852
+ fileConfig,
1853
+ strict,
1854
+ debugValues,
1855
+ outputFile: values["output-file"],
1856
+ contractPath,
1857
+ fallbackExamplePath,
1858
+ cloudProvider,
1859
+ cloudFile,
1860
+ plugins
1861
+ };
1862
+ }
1863
+ async function emitAndReturnExitCode(report, params) {
1864
+ const emitOptions = {
1865
+ report,
1866
+ jsonMode: params.jsonMode,
1867
+ ...params.outputFile !== void 0 && { outputFile: params.outputFile }
1868
+ };
1869
+ await emitValidationReport(emitOptions);
1870
+ return report.status === "fail" ? 1 : 0;
1871
+ }
1872
+ async function runCheckCommand(args) {
1873
+ const context = await prepareCommonContext(args.values);
1874
+ const loadContractOptions = {
1875
+ fallbackExamplePath: context.fallbackExamplePath,
1876
+ cwd: process.cwd(),
1877
+ ...context.contractPath !== void 0 && { contractPath: context.contractPath }
1878
+ };
1879
+ const contract = applyContractPlugins(
1880
+ await loadValidationContract(loadContractOptions),
1881
+ context.plugins
1882
+ );
1883
+ const provider = context.cloudProvider;
1884
+ let environment = args.values.env?.[0] ?? ".env";
1885
+ let sourceValues;
1886
+ if (provider !== void 0) {
1887
+ const cloudFile = context.cloudFile ?? `${provider}.env.json`;
1888
+ sourceValues = await loadCloudSource({ provider, filePath: cloudFile });
1889
+ environment = `cloud:${provider}`;
1890
+ } else {
1891
+ sourceValues = await loadEnvSource({ filePath: environment, allowMissing: true });
1892
+ }
1893
+ sourceValues = applySourcePlugins({ environment, values: sourceValues }, context.plugins);
1894
+ const report = applyReportPlugins(
1895
+ validateAgainstContract({
1896
+ contract,
1897
+ values: sourceValues,
1898
+ environment,
1899
+ strict: context.strict,
1900
+ debugValues: context.debugValues
1901
+ }),
1902
+ context.plugins
1903
+ );
1904
+ return emitAndReturnExitCode(report, {
1905
+ jsonMode: args.jsonMode,
1906
+ ...context.outputFile !== void 0 && { outputFile: context.outputFile }
1907
+ });
1908
+ }
1909
+ async function runDiffCommand(args) {
1910
+ const context = await prepareCommonContext(args.values);
1911
+ const loadContractOptions = {
1912
+ fallbackExamplePath: context.fallbackExamplePath,
1913
+ cwd: process.cwd(),
1914
+ ...context.contractPath !== void 0 && { contractPath: context.contractPath }
1915
+ };
1916
+ const contract = applyContractPlugins(
1917
+ await loadValidationContract(loadContractOptions),
1918
+ context.plugins
1919
+ );
1920
+ const sources = {};
1921
+ for (const target of parseTargets(args.values, context.fileConfig)) {
1922
+ const values = await loadEnvSource({ filePath: target, allowMissing: true });
1923
+ sources[target] = applySourcePlugins({ environment: target, values }, context.plugins);
1924
+ }
1925
+ if (context.cloudProvider !== void 0) {
1926
+ const cloudFile = context.cloudFile ?? `${context.cloudProvider}.env.json`;
1927
+ const cloudEnvironment = `cloud:${context.cloudProvider}`;
1928
+ const cloudValues = await loadCloudSource({
1929
+ provider: context.cloudProvider,
1930
+ filePath: cloudFile
1931
+ });
1932
+ sources[cloudEnvironment] = applySourcePlugins(
1933
+ { environment: cloudEnvironment, values: cloudValues },
1934
+ context.plugins
1935
+ );
1936
+ }
1937
+ const report = applyReportPlugins(
1938
+ diffEnvironmentSources({
1939
+ contract,
1940
+ sources,
1941
+ strict: context.strict,
1942
+ debugValues: context.debugValues
1943
+ }),
1944
+ context.plugins
1945
+ );
1946
+ return emitAndReturnExitCode(report, {
1947
+ jsonMode: args.jsonMode,
1948
+ ...context.outputFile !== void 0 && { outputFile: context.outputFile }
1949
+ });
1950
+ }
1951
+ async function runDoctorCommand(args) {
1952
+ const context = await prepareCommonContext(args.values);
1953
+ const loadContractOptions = {
1954
+ fallbackExamplePath: context.fallbackExamplePath,
1955
+ cwd: process.cwd(),
1956
+ ...context.contractPath !== void 0 && { contractPath: context.contractPath }
1957
+ };
1958
+ const contract = applyContractPlugins(
1959
+ await loadValidationContract(loadContractOptions),
1960
+ context.plugins
1961
+ );
1962
+ const checkEnvironment = args.values.env?.[0] ?? ".env";
1963
+ let checkValues = await loadEnvSource({ filePath: checkEnvironment, allowMissing: true });
1964
+ checkValues = applySourcePlugins(
1965
+ { environment: checkEnvironment, values: checkValues },
1966
+ context.plugins
1967
+ );
1968
+ const checkReport = validateAgainstContract({
1969
+ contract,
1970
+ values: checkValues,
1971
+ environment: checkEnvironment,
1972
+ strict: context.strict,
1973
+ debugValues: context.debugValues
1974
+ });
1975
+ const sources = {};
1976
+ for (const target of parseTargets(args.values, context.fileConfig)) {
1977
+ const values = await loadEnvSource({ filePath: target, allowMissing: true });
1978
+ sources[target] = applySourcePlugins({ environment: target, values }, context.plugins);
1979
+ }
1980
+ if (context.cloudProvider !== void 0) {
1981
+ const cloudFile = context.cloudFile ?? `${context.cloudProvider}.env.json`;
1982
+ const cloudEnvironment = `cloud:${context.cloudProvider}`;
1983
+ const cloudValues = await loadCloudSource({
1984
+ provider: context.cloudProvider,
1985
+ filePath: cloudFile
520
1986
  });
1987
+ sources[cloudEnvironment] = applySourcePlugins(
1988
+ { environment: cloudEnvironment, values: cloudValues },
1989
+ context.plugins
1990
+ );
1991
+ }
1992
+ const diffReport = diffEnvironmentSources({
1993
+ contract,
1994
+ sources,
1995
+ strict: context.strict,
1996
+ debugValues: context.debugValues
1997
+ });
1998
+ const report = applyReportPlugins(
1999
+ buildDoctorReport({ checkReport, diffReport }),
2000
+ context.plugins
2001
+ );
2002
+ return emitAndReturnExitCode(report, {
2003
+ jsonMode: args.jsonMode,
2004
+ ...context.outputFile !== void 0 && { outputFile: context.outputFile }
2005
+ });
2006
+ }
2007
+ async function runValidationCommand(params) {
2008
+ const parsed = parseValidationArgs(params.argv);
2009
+ if (parsed.values.help === true) {
2010
+ console.log(HELP_TEXT[params.command]);
2011
+ return 0;
2012
+ }
2013
+ if (params.command === "check") {
2014
+ return runCheckCommand(parsed);
2015
+ }
2016
+ if (params.command === "diff") {
2017
+ return runDiffCommand(parsed);
521
2018
  }
2019
+ return runDoctorCommand(parsed);
522
2020
  }
523
2021
 
2022
+ exports.CONTRACT_FILE_NAMES = CONTRACT_FILE_NAMES;
2023
+ exports.applyContractPlugins = applyContractPlugins;
2024
+ exports.applyReportPlugins = applyReportPlugins;
2025
+ exports.applySourcePlugins = applySourcePlugins;
2026
+ exports.buildCiReport = buildCiReport;
524
2027
  exports.defineConfig = defineConfig;
2028
+ exports.defineContract = defineContract;
2029
+ exports.formatCiReport = formatCiReport;
525
2030
  exports.generateDeclaration = generateDeclaration;
526
2031
  exports.generateEnvValidation = generateEnvValidation;
527
2032
  exports.generateT3Env = generateT3Env;
528
- exports.generateTypeScript = generateTypeScriptTypes;
529
2033
  exports.generateTypeScriptTypes = generateTypeScriptTypes;
530
- exports.generateZod = generateZodSchema;
531
2034
  exports.generateZodSchema = generateZodSchema;
532
2035
  exports.inferType = inferType;
533
2036
  exports.inferTypes = inferTypesFromParsedVars;
534
2037
  exports.inferenceRules = inferenceRules;
2038
+ exports.loadCloudSource = loadCloudSource;
535
2039
  exports.loadConfig = loadConfig;
2040
+ exports.loadContract = loadContract;
2041
+ exports.loadPlugins = loadPlugins;
536
2042
  exports.parseCommentBlock = parseCommentBlock;
537
2043
  exports.parseEnvFile = parseEnvFile;
538
2044
  exports.parseEnvFileContent = parseEnvFileContent;
2045
+ exports.runCheck = runCheck;
539
2046
  exports.runGenerate = runGenerate;
2047
+ exports.runValidationCommand = runValidationCommand;
2048
+ exports.validateContract = validateContract;
540
2049
  //# sourceMappingURL=index.cjs.map
541
2050
  //# sourceMappingURL=index.cjs.map