node-safe-env 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,892 @@
1
+ // src/errors/EnvValidationError.ts
2
+ var EnvValidationError = class extends Error {
3
+ constructor(issues) {
4
+ super(
5
+ [
6
+ "Environment validation failed:",
7
+ ...issues.map((issue) => `- [${issue.code}] ${issue.message}`)
8
+ ].join("\n")
9
+ );
10
+ this.name = "EnvValidationError";
11
+ this.issues = issues;
12
+ }
13
+ };
14
+
15
+ // src/createEnv.ts
16
+ import path2 from "path";
17
+
18
+ // src/flattenSchema.ts
19
+ function isEnvRule(value) {
20
+ return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string";
21
+ }
22
+ function toEnvKey(path3) {
23
+ return path3.map((segment) => segment.toUpperCase()).join("_");
24
+ }
25
+ function flattenSchema(schema, parentPath = []) {
26
+ const entries = [];
27
+ for (const [key, value] of Object.entries(schema)) {
28
+ const currentPath = [...parentPath, key];
29
+ if (isEnvRule(value)) {
30
+ entries.push({
31
+ path: currentPath,
32
+ envKey: toEnvKey(currentPath),
33
+ rule: value
34
+ });
35
+ continue;
36
+ }
37
+ if (typeof value === "object" && value !== null) {
38
+ entries.push(
39
+ ...flattenSchema(value, currentPath)
40
+ );
41
+ }
42
+ }
43
+ return entries;
44
+ }
45
+
46
+ // src/findUnknownEnvKeys.ts
47
+ function findUnknownEnvKeys(schema, source) {
48
+ const knownKeys = new Set(flattenSchema(schema).map((entry) => entry.envKey));
49
+ const issues = [];
50
+ for (const key of Object.keys(source)) {
51
+ if (source[key] === void 0) {
52
+ continue;
53
+ }
54
+ if (knownKeys.has(key)) {
55
+ continue;
56
+ }
57
+ issues.push({
58
+ key,
59
+ code: "unknown_key",
60
+ message: `Environment variable "${key}" is not defined in the schema.`
61
+ });
62
+ }
63
+ return issues;
64
+ }
65
+
66
+ // src/loadEnvFiles.ts
67
+ import fs from "fs";
68
+ import path from "path";
69
+ var ENV_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
70
+ function stripInlineComment(input) {
71
+ let inSingleQuote = false;
72
+ let inDoubleQuote = false;
73
+ let isEscaped = false;
74
+ for (let index = 0; index < input.length; index += 1) {
75
+ const char = input[index];
76
+ if (isEscaped) {
77
+ isEscaped = false;
78
+ continue;
79
+ }
80
+ if (char === "\\") {
81
+ isEscaped = true;
82
+ continue;
83
+ }
84
+ if (char === '"' && !inSingleQuote) {
85
+ inDoubleQuote = !inDoubleQuote;
86
+ continue;
87
+ }
88
+ if (char === "'" && !inDoubleQuote) {
89
+ inSingleQuote = !inSingleQuote;
90
+ continue;
91
+ }
92
+ if (char === "#" && !inSingleQuote && !inDoubleQuote && (index === 0 || /\s/.test(input[index - 1] ?? ""))) {
93
+ return input.slice(0, index).trimEnd();
94
+ }
95
+ }
96
+ return input.trimEnd();
97
+ }
98
+ function parseEnvFile(filePath) {
99
+ if (!fs.existsSync(filePath)) {
100
+ return /* @__PURE__ */ Object.create(null);
101
+ }
102
+ const content = fs.readFileSync(filePath, "utf8");
103
+ const result = /* @__PURE__ */ Object.create(null);
104
+ for (const rawLine of content.split(/\r?\n/)) {
105
+ let line = rawLine.trim();
106
+ if (!line || line.startsWith("#")) {
107
+ continue;
108
+ }
109
+ if (line.startsWith("export ")) {
110
+ line = line.slice("export ".length).trim();
111
+ if (!line) {
112
+ continue;
113
+ }
114
+ }
115
+ const equalIndex = line.indexOf("=");
116
+ if (equalIndex <= 0) {
117
+ continue;
118
+ }
119
+ const key = line.slice(0, equalIndex).trim();
120
+ if (!ENV_KEY_PATTERN.test(key)) {
121
+ continue;
122
+ }
123
+ let value = stripInlineComment(line.slice(equalIndex + 1)).trim();
124
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
125
+ value = value.slice(1, -1);
126
+ }
127
+ result[key] = value;
128
+ }
129
+ return result;
130
+ }
131
+ function loadEnvFiles(options = {}) {
132
+ const cwd = options.cwd ?? process.cwd();
133
+ const nodeEnv = options.nodeEnv ?? process.env.NODE_ENV ?? "development";
134
+ const base = parseEnvFile(path.join(cwd, ".env"));
135
+ const local = parseEnvFile(path.join(cwd, ".env.local"));
136
+ const environment = parseEnvFile(path.join(cwd, `.env.${nodeEnv}`));
137
+ const custom = options.envFile ? parseEnvFile(path.resolve(cwd, options.envFile)) : /* @__PURE__ */ Object.create(null);
138
+ return {
139
+ base,
140
+ local,
141
+ environment,
142
+ custom
143
+ };
144
+ }
145
+
146
+ // src/mergeSources.ts
147
+ function mergeSources(files, runtimeValues = process.env) {
148
+ return Object.assign(
149
+ /* @__PURE__ */ Object.create(null),
150
+ files.base,
151
+ files.local,
152
+ files.environment,
153
+ files.custom,
154
+ runtimeValues
155
+ );
156
+ }
157
+
158
+ // src/applyTransform.ts
159
+ function applyTransform(key, rule, result) {
160
+ if (result.issue || result.value === void 0 || !rule.transform) {
161
+ return result;
162
+ }
163
+ try {
164
+ return {
165
+ value: rule.transform(result.value)
166
+ };
167
+ } catch (error) {
168
+ const message = error instanceof Error && error.message ? error.message : `Environment variable "${key}" failed transform.`;
169
+ return {
170
+ issue: {
171
+ key,
172
+ code: "invalid_custom",
173
+ message
174
+ }
175
+ };
176
+ }
177
+ }
178
+
179
+ // src/validators/boolean.ts
180
+ var TRUE_VALUES = /* @__PURE__ */ new Set(["true", "1", "yes", "on"]);
181
+ var FALSE_VALUES = /* @__PURE__ */ new Set(["false", "0", "no", "off"]);
182
+ var validateBoolean = ({ key, rawValue }) => {
183
+ const normalized = rawValue.trim().toLowerCase();
184
+ if (TRUE_VALUES.has(normalized)) {
185
+ return { value: true };
186
+ }
187
+ if (FALSE_VALUES.has(normalized)) {
188
+ return { value: false };
189
+ }
190
+ return {
191
+ issue: {
192
+ key,
193
+ code: "invalid_boolean",
194
+ message: `Environment variable "${key}" must be a valid boolean.`
195
+ }
196
+ };
197
+ };
198
+
199
+ // src/validators/enum.ts
200
+ var validateEnum = ({ key, rawValue, rule }) => {
201
+ const enumRule = rule;
202
+ if (!enumRule.values.includes(rawValue)) {
203
+ return {
204
+ issue: {
205
+ key,
206
+ code: "invalid_enum",
207
+ message: `Environment variable "${key}" must be one of: ${enumRule.values.join(", ")}.`
208
+ }
209
+ };
210
+ }
211
+ return { value: rawValue };
212
+ };
213
+
214
+ // src/validators/json.ts
215
+ var validateJson = ({ key, rawValue }) => {
216
+ try {
217
+ const parsed = JSON.parse(rawValue);
218
+ return { value: parsed };
219
+ } catch {
220
+ return {
221
+ issue: {
222
+ key,
223
+ code: "invalid_json",
224
+ message: `Environment variable "${key}" must contain valid JSON.`
225
+ }
226
+ };
227
+ }
228
+ };
229
+
230
+ // src/validators/number.ts
231
+ var validateNumber = ({ key, rawValue }) => {
232
+ const parsed = Number(rawValue);
233
+ if (!Number.isFinite(parsed)) {
234
+ return {
235
+ issue: {
236
+ key,
237
+ code: "invalid_number",
238
+ message: `Environment variable "${key}" must be a valid number.`
239
+ }
240
+ };
241
+ }
242
+ return { value: parsed };
243
+ };
244
+
245
+ // src/validators/port.ts
246
+ var validatePort = ({ key, rawValue }) => {
247
+ const num = Number(rawValue);
248
+ if (!Number.isInteger(num) || num < 1 || num > 65535) {
249
+ return {
250
+ issue: {
251
+ key,
252
+ code: "invalid_port",
253
+ message: `Environment variable "${key}" must be a valid port (1\u201365535).`
254
+ }
255
+ };
256
+ }
257
+ return { value: num };
258
+ };
259
+
260
+ // src/validators/string.ts
261
+ var validateString = ({ rawValue }) => {
262
+ return { value: rawValue };
263
+ };
264
+
265
+ // src/validators/url.ts
266
+ var validateUrl = ({ key, rawValue }) => {
267
+ try {
268
+ new URL(rawValue);
269
+ return { value: rawValue };
270
+ } catch {
271
+ return {
272
+ issue: {
273
+ key,
274
+ code: "invalid_url",
275
+ message: `Environment variable "${key}" must be a valid URL.`
276
+ }
277
+ };
278
+ }
279
+ };
280
+
281
+ // src/validators/int.ts
282
+ var validateInt = ({ key, rawValue }) => {
283
+ const parsed = Number(rawValue);
284
+ if (!Number.isInteger(parsed)) {
285
+ return {
286
+ issue: {
287
+ key,
288
+ code: "invalid_number",
289
+ message: `Environment variable "${key}" must be a valid integer.`
290
+ }
291
+ };
292
+ }
293
+ return { value: parsed };
294
+ };
295
+
296
+ // src/validators/float.ts
297
+ var validateFloat = ({ key, rawValue }) => {
298
+ const parsed = Number(rawValue);
299
+ if (!Number.isFinite(parsed)) {
300
+ return {
301
+ issue: {
302
+ key,
303
+ code: "invalid_number",
304
+ message: `Environment variable "${key}" must be a valid float.`
305
+ }
306
+ };
307
+ }
308
+ return { value: parsed };
309
+ };
310
+
311
+ // src/validators/array.ts
312
+ var validateArray = ({ key, rawValue, rule }) => {
313
+ const arrayRule = rule;
314
+ const separator = arrayRule.separator ?? ",";
315
+ const trimItems = arrayRule.trimItems ?? true;
316
+ const allowEmptyItems = arrayRule.allowEmptyItems ?? false;
317
+ const splitValues = rawValue.split(separator);
318
+ const parsed = trimItems ? splitValues.map((item) => item.trim()) : splitValues;
319
+ if (!allowEmptyItems && parsed.some((item) => item === "")) {
320
+ const issue = {
321
+ key,
322
+ code: "invalid_array",
323
+ message: `Environment variable "${key}" cannot contain empty array items`
324
+ };
325
+ return { issue };
326
+ }
327
+ return { value: parsed };
328
+ };
329
+
330
+ // src/validators/custom.ts
331
+ var validateCustom = ({ key, rawValue, rule }) => {
332
+ const customRule = rule;
333
+ try {
334
+ const parsed = customRule.parse(rawValue);
335
+ return { value: parsed };
336
+ } catch (error) {
337
+ const message = error instanceof Error && error.message ? error.message : `Environment variable "${key}" failed custom validation.`;
338
+ return {
339
+ issue: {
340
+ key,
341
+ code: "invalid_custom",
342
+ message
343
+ }
344
+ };
345
+ }
346
+ };
347
+
348
+ // src/validators/email.ts
349
+ var validateEmail = ({ key, rawValue, rule }) => {
350
+ const emailRule = rule;
351
+ void emailRule;
352
+ const value = rawValue.trim();
353
+ if (!value) {
354
+ const issue = {
355
+ key,
356
+ code: "invalid_email",
357
+ message: `Environment variable "${key}" must be a valid email address`
358
+ };
359
+ return { issue };
360
+ }
361
+ if (/\s/.test(value)) {
362
+ const issue = {
363
+ key,
364
+ code: "invalid_email",
365
+ message: `Environment variable "${key}" must be a valid email address`
366
+ };
367
+ return { issue };
368
+ }
369
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
370
+ if (!emailRegex.test(value)) {
371
+ const issue = {
372
+ key,
373
+ code: "invalid_email",
374
+ message: `Environment variable "${key}" must be a valid email address`
375
+ };
376
+ return { issue };
377
+ }
378
+ return {
379
+ value
380
+ };
381
+ };
382
+
383
+ // src/validators/date.ts
384
+ function isValidDate(date) {
385
+ return !Number.isNaN(date.getTime());
386
+ }
387
+ var validateDate = ({ key, rawValue, rule }) => {
388
+ const dateRule = rule;
389
+ void dateRule;
390
+ const value = rawValue.trim();
391
+ if (!value) {
392
+ const issue = {
393
+ key,
394
+ code: "invalid_date",
395
+ message: `Environment variable "${key}" must be a valid ISO date`
396
+ };
397
+ return { issue };
398
+ }
399
+ const isoDateRegex = /^\d{4}-\d{2}-\d{2}$/;
400
+ const isoDateTimeRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d{1,3})?)?(Z|[+-]\d{2}:\d{2})$/;
401
+ if (!isoDateRegex.test(value) && !isoDateTimeRegex.test(value)) {
402
+ const issue = {
403
+ key,
404
+ code: "invalid_date",
405
+ message: `Environment variable "${key}" must be a valid ISO date string`
406
+ };
407
+ return { issue };
408
+ }
409
+ const parsed = new Date(value);
410
+ if (!isValidDate(parsed)) {
411
+ const issue = {
412
+ key,
413
+ code: "invalid_date",
414
+ message: `Environment variable "${key}" must be a valid date`
415
+ };
416
+ return { issue };
417
+ }
418
+ if (isoDateRegex.test(value)) {
419
+ const [year, month, day] = value.split("-").map(Number);
420
+ if (parsed.getUTCFullYear() !== year || parsed.getUTCMonth() + 1 !== month || parsed.getUTCDate() !== day) {
421
+ const issue = {
422
+ key,
423
+ code: "invalid_date",
424
+ message: `Environment variable "${key}" must be a real calendar date`
425
+ };
426
+ return { issue };
427
+ }
428
+ }
429
+ return {
430
+ value: parsed
431
+ };
432
+ };
433
+
434
+ // src/validators/index.ts
435
+ var validators = {
436
+ string: validateString,
437
+ number: validateNumber,
438
+ boolean: validateBoolean,
439
+ enum: validateEnum,
440
+ url: validateUrl,
441
+ port: validatePort,
442
+ json: validateJson,
443
+ int: validateInt,
444
+ float: validateFloat,
445
+ array: validateArray,
446
+ custom: validateCustom,
447
+ email: validateEmail,
448
+ date: validateDate
449
+ };
450
+
451
+ // src/parseValue.ts
452
+ function parseValue(key, rawValue, rule) {
453
+ const validator = validators[rule.type];
454
+ const result = validator({
455
+ key,
456
+ rawValue,
457
+ rule
458
+ });
459
+ return applyTransform(key, rule, result);
460
+ }
461
+
462
+ // src/setNestedValue.ts
463
+ function setNestedValue(target, path3, value) {
464
+ let current = target;
465
+ for (let index = 0; index < path3.length - 1; index += 1) {
466
+ const segment = path3[index];
467
+ const existing = current[segment];
468
+ if (typeof existing !== "object" || existing === null || Array.isArray(existing)) {
469
+ current[segment] = {};
470
+ }
471
+ current = current[segment];
472
+ }
473
+ current[path3[path3.length - 1]] = value;
474
+ }
475
+
476
+ // src/createEnv.ts
477
+ function isEmptyString(value) {
478
+ return value.trim() === "";
479
+ }
480
+ function defaultToRawValue(value) {
481
+ if (typeof value === "string") {
482
+ return value;
483
+ }
484
+ if (typeof value === "number" || typeof value === "boolean") {
485
+ return String(value);
486
+ }
487
+ if (value instanceof Date) {
488
+ return value.toISOString();
489
+ }
490
+ return JSON.stringify(value);
491
+ }
492
+ function resolveDefaultValue(value) {
493
+ return typeof value === "function" ? value() : value;
494
+ }
495
+ function parseDefaultValue(envKey, rule) {
496
+ const defaultKind = typeof rule.default === "function" ? "function" : "static";
497
+ let resolvedDefault;
498
+ try {
499
+ resolvedDefault = resolveDefaultValue(rule.default);
500
+ } catch (err) {
501
+ const message = err instanceof Error ? err.message : String(err);
502
+ return {
503
+ issue: {
504
+ key: envKey,
505
+ code: "invalid_default",
506
+ message: `Default function for "${envKey}" threw an error: ${message}`
507
+ },
508
+ defaultKind
509
+ };
510
+ }
511
+ const rawDefault = defaultToRawValue(resolvedDefault);
512
+ return {
513
+ ...parseValue(envKey, rawDefault, rule),
514
+ defaultKind,
515
+ rawDefault
516
+ };
517
+ }
518
+ function countKeys(source) {
519
+ return Object.values(source).filter((value) => typeof value === "string").length;
520
+ }
521
+ function buildLoadedFileReport(loadedFiles, cwd, nodeEnv, envFile) {
522
+ return [
523
+ {
524
+ source: ".env",
525
+ path: path2.join(cwd, ".env"),
526
+ keyCount: countKeys(loadedFiles.base)
527
+ },
528
+ {
529
+ source: ".env.local",
530
+ path: path2.join(cwd, ".env.local"),
531
+ keyCount: countKeys(loadedFiles.local)
532
+ },
533
+ {
534
+ source: ".env.environment",
535
+ path: path2.join(cwd, `.env.${nodeEnv}`),
536
+ keyCount: countKeys(loadedFiles.environment)
537
+ },
538
+ {
539
+ source: "custom",
540
+ path: envFile ? path2.resolve(cwd, envFile) : void 0,
541
+ keyCount: countKeys(loadedFiles.custom)
542
+ }
543
+ ];
544
+ }
545
+ function buildSourceTrace(loadedFiles, runtimeValues) {
546
+ const trace = /* @__PURE__ */ Object.create(null);
547
+ const applySource = (source, label) => {
548
+ for (const [key, raw] of Object.entries(source)) {
549
+ if (typeof raw === "string") {
550
+ trace[key] = { source: label, raw };
551
+ }
552
+ }
553
+ };
554
+ applySource(loadedFiles.base, ".env");
555
+ applySource(loadedFiles.local, ".env.local");
556
+ applySource(loadedFiles.environment, ".env.environment");
557
+ applySource(loadedFiles.custom, "custom");
558
+ applySource(runtimeValues, "process.env");
559
+ return trace;
560
+ }
561
+ function maskDebugValue(value, sensitive) {
562
+ if (value === void 0) {
563
+ return void 0;
564
+ }
565
+ return sensitive ? "***" : value;
566
+ }
567
+ function resolveDebugLogger(debug) {
568
+ if (debug === true) {
569
+ return (report) => {
570
+ console.info(report);
571
+ };
572
+ }
573
+ if (debug && typeof debug === "object" && debug.logger) {
574
+ return debug.logger;
575
+ }
576
+ return void 0;
577
+ }
578
+ function createEnv(schema, options = {}) {
579
+ const debugEnabled = options.debug !== void 0 && options.debug !== false;
580
+ const debugLogger = debugEnabled ? resolveDebugLogger(options.debug) : void 0;
581
+ const debugKeys = [];
582
+ let source;
583
+ let loadedFileReport = [];
584
+ let sourceTrace = /* @__PURE__ */ Object.create(null);
585
+ if (options.source) {
586
+ source = options.source;
587
+ if (debugEnabled) {
588
+ for (const [key, raw] of Object.entries(source)) {
589
+ if (typeof raw === "string") {
590
+ sourceTrace[key] = { source: "process.env", raw };
591
+ }
592
+ }
593
+ }
594
+ } else {
595
+ const loadedFiles = loadEnvFiles({
596
+ cwd: options.cwd,
597
+ nodeEnv: options.nodeEnv,
598
+ envFile: options.envFile
599
+ });
600
+ source = mergeSources(loadedFiles, process.env);
601
+ if (debugEnabled) {
602
+ const cwd = options.cwd ?? process.cwd();
603
+ const nodeEnv = options.nodeEnv ?? process.env.NODE_ENV ?? "development";
604
+ loadedFileReport = buildLoadedFileReport(
605
+ loadedFiles,
606
+ cwd,
607
+ nodeEnv,
608
+ options.envFile
609
+ );
610
+ sourceTrace = buildSourceTrace(loadedFiles, process.env);
611
+ }
612
+ }
613
+ const issues = [];
614
+ const result = {};
615
+ const flattenedSchema = flattenSchema(schema);
616
+ if (options.strict) {
617
+ issues.push(
618
+ ...findUnknownEnvKeys(schema, source)
619
+ );
620
+ }
621
+ for (const entry of flattenedSchema) {
622
+ const { path: path3, envKey, rule } = entry;
623
+ const currentValue = source[envKey];
624
+ const sourceInfo = sourceTrace[envKey];
625
+ const sensitive = rule.sensitive === true;
626
+ const defaultKind = rule.default === void 0 ? void 0 : typeof rule.default === "function" ? "function" : "static";
627
+ const pushDebugEntry = (status, values) => {
628
+ if (!debugEnabled) {
629
+ return;
630
+ }
631
+ debugKeys.push({
632
+ key: envKey,
633
+ ruleType: rule.type,
634
+ source: values.source,
635
+ usedDefault: values.usedDefault,
636
+ defaultKind: values.defaultKind,
637
+ raw: maskDebugValue(values.raw, sensitive),
638
+ parsed: maskDebugValue(values.parsed, sensitive),
639
+ status,
640
+ issue: values.issue
641
+ });
642
+ };
643
+ if (typeof currentValue !== "string") {
644
+ if (rule.default !== void 0) {
645
+ const parsedDefault = parseDefaultValue(envKey, rule);
646
+ if (parsedDefault.issue) {
647
+ issues.push(parsedDefault.issue);
648
+ pushDebugEntry("issue", {
649
+ source: "default",
650
+ usedDefault: true,
651
+ defaultKind: parsedDefault.defaultKind,
652
+ raw: parsedDefault.rawDefault,
653
+ issue: parsedDefault.issue
654
+ });
655
+ continue;
656
+ }
657
+ pushDebugEntry("defaulted", {
658
+ source: "default",
659
+ usedDefault: true,
660
+ defaultKind: parsedDefault.defaultKind,
661
+ raw: parsedDefault.rawDefault,
662
+ parsed: parsedDefault.value
663
+ });
664
+ setNestedValue(result, path3, parsedDefault.value);
665
+ continue;
666
+ }
667
+ if (rule.required) {
668
+ const issue = {
669
+ key: envKey,
670
+ code: "missing",
671
+ message: `Missing required environment variable "${envKey}".`
672
+ };
673
+ issues.push(issue);
674
+ pushDebugEntry("missing", {
675
+ source: "missing",
676
+ usedDefault: false,
677
+ defaultKind,
678
+ issue
679
+ });
680
+ } else {
681
+ pushDebugEntry("missing", {
682
+ source: "missing",
683
+ usedDefault: false,
684
+ defaultKind
685
+ });
686
+ }
687
+ continue;
688
+ }
689
+ const rawValue = currentValue;
690
+ if (!rule.allowEmpty && isEmptyString(rawValue)) {
691
+ if (rule.default !== void 0) {
692
+ const parsedDefault = parseDefaultValue(envKey, rule);
693
+ if (parsedDefault.issue) {
694
+ issues.push(parsedDefault.issue);
695
+ pushDebugEntry("issue", {
696
+ source: "default",
697
+ usedDefault: true,
698
+ defaultKind: parsedDefault.defaultKind,
699
+ raw: parsedDefault.rawDefault,
700
+ issue: parsedDefault.issue
701
+ });
702
+ continue;
703
+ }
704
+ pushDebugEntry("defaulted", {
705
+ source: "default",
706
+ usedDefault: true,
707
+ defaultKind: parsedDefault.defaultKind,
708
+ raw: parsedDefault.rawDefault,
709
+ parsed: parsedDefault.value
710
+ });
711
+ setNestedValue(result, path3, parsedDefault.value);
712
+ continue;
713
+ }
714
+ const issue = {
715
+ key: envKey,
716
+ code: "empty",
717
+ message: `Environment variable "${envKey}" cannot be empty.`
718
+ };
719
+ issues.push(issue);
720
+ pushDebugEntry("empty", {
721
+ source: sourceInfo?.source ?? "process.env",
722
+ usedDefault: false,
723
+ defaultKind,
724
+ raw: rawValue,
725
+ issue
726
+ });
727
+ continue;
728
+ }
729
+ const parsed = parseValue(envKey, rawValue, rule);
730
+ if (parsed.issue) {
731
+ issues.push(parsed.issue);
732
+ pushDebugEntry("issue", {
733
+ source: sourceInfo?.source ?? "process.env",
734
+ usedDefault: false,
735
+ defaultKind,
736
+ raw: rawValue,
737
+ issue: parsed.issue
738
+ });
739
+ continue;
740
+ }
741
+ pushDebugEntry("parsed", {
742
+ source: sourceInfo?.source ?? "process.env",
743
+ usedDefault: false,
744
+ defaultKind,
745
+ raw: rawValue,
746
+ parsed: parsed.value
747
+ });
748
+ setNestedValue(result, path3, parsed.value);
749
+ }
750
+ if (debugLogger) {
751
+ debugLogger({
752
+ loadedFiles: loadedFileReport,
753
+ keys: debugKeys
754
+ });
755
+ }
756
+ if (issues.length > 0) {
757
+ throw new EnvValidationError(issues);
758
+ }
759
+ return result;
760
+ }
761
+
762
+ // src/defineEnv.ts
763
+ function defineEnv(schema) {
764
+ return schema;
765
+ }
766
+
767
+ // src/maskEnv.ts
768
+ function maskValue() {
769
+ return "***";
770
+ }
771
+ function maskEnv(schema, env) {
772
+ const masked = {};
773
+ const envRecord = env;
774
+ for (const key of Object.keys(schema)) {
775
+ const value = envRecord[key];
776
+ if (value === void 0) {
777
+ continue;
778
+ }
779
+ masked[key] = schema[key].sensitive ? maskValue() : value;
780
+ }
781
+ return masked;
782
+ }
783
+
784
+ // src/traceEnv.ts
785
+ function traceEnv(schema, sources, env) {
786
+ const traced = {};
787
+ const envRecord = env;
788
+ for (const key of Object.keys(schema)) {
789
+ const value = envRecord[key];
790
+ if (value === void 0) {
791
+ continue;
792
+ }
793
+ const sourceInfo = sources[key];
794
+ if (!sourceInfo) {
795
+ continue;
796
+ }
797
+ traced[key] = {
798
+ source: sourceInfo.source,
799
+ raw: sourceInfo.raw,
800
+ parsed: value
801
+ };
802
+ }
803
+ return traced;
804
+ }
805
+
806
+ // src/validateExampleEnv.ts
807
+ function validateExampleEnv(schema, exampleSource) {
808
+ const issues = [];
809
+ const flattenedSchema = flattenSchema(schema);
810
+ const expectedKeys = new Set(flattenedSchema.map((entry) => entry.envKey));
811
+ for (const entry of flattenedSchema) {
812
+ const { envKey } = entry;
813
+ if (!(envKey in exampleSource)) {
814
+ issues.push({
815
+ key: envKey,
816
+ code: "missing_example_key",
817
+ message: `Environment variable "${envKey}" is missing from .env.example.`
818
+ });
819
+ }
820
+ }
821
+ for (const key of Object.keys(exampleSource)) {
822
+ if (exampleSource[key] === void 0) {
823
+ continue;
824
+ }
825
+ if (expectedKeys.has(key)) {
826
+ continue;
827
+ }
828
+ issues.push({
829
+ key,
830
+ code: "unknown_example_key",
831
+ message: `Environment variable "${key}" in .env.example is not defined in the schema.`
832
+ });
833
+ }
834
+ return issues;
835
+ }
836
+
837
+ // src/readEnvFileSource.ts
838
+ import { existsSync, readFileSync } from "fs";
839
+ import { resolve } from "path";
840
+ function stripQuotes(value) {
841
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
842
+ return value.slice(1, -1);
843
+ }
844
+ return value;
845
+ }
846
+ function readEnvFileSource(filePath) {
847
+ if (!existsSync(filePath)) {
848
+ return {};
849
+ }
850
+ const content = readFileSync(filePath, "utf8");
851
+ const source = {};
852
+ for (const line of content.split(/\r?\n/)) {
853
+ const trimmed = line.trim();
854
+ if (!trimmed || trimmed.startsWith("#")) {
855
+ continue;
856
+ }
857
+ const equalsIndex = trimmed.indexOf("=");
858
+ if (equalsIndex === -1) {
859
+ continue;
860
+ }
861
+ const key = trimmed.slice(0, equalsIndex).trim();
862
+ const rawValue = trimmed.slice(equalsIndex + 1).trim();
863
+ if (!key) {
864
+ continue;
865
+ }
866
+ source[key] = stripQuotes(rawValue);
867
+ }
868
+ return source;
869
+ }
870
+ function resolveExampleEnvPath(cwd = process.cwd(), exampleFile = ".env.example") {
871
+ return resolve(cwd, exampleFile);
872
+ }
873
+
874
+ // src/validateExampleEnvFile.ts
875
+ function validateExampleEnvFile(schema, options = {}) {
876
+ const filePath = resolveExampleEnvPath(options.cwd, options.exampleFile);
877
+ const exampleSource = readEnvFileSource(filePath);
878
+ return validateExampleEnv(schema, exampleSource);
879
+ }
880
+ export {
881
+ EnvValidationError,
882
+ createEnv,
883
+ defineEnv,
884
+ maskEnv,
885
+ mergeSources,
886
+ readEnvFileSource,
887
+ resolveExampleEnvPath,
888
+ traceEnv,
889
+ validateExampleEnv,
890
+ validateExampleEnvFile
891
+ };
892
+ //# sourceMappingURL=index.js.map