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