ultraenv 1.0.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.
Files changed (89) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2058 -0
  3. package/bin/ultraenv.mjs +3 -0
  4. package/dist/chunk-2USZPWLZ.js +288 -0
  5. package/dist/chunk-3UV2QNJL.js +270 -0
  6. package/dist/chunk-3VYXPTYV.js +179 -0
  7. package/dist/chunk-4XUYMRK5.js +366 -0
  8. package/dist/chunk-5G2DU52U.js +189 -0
  9. package/dist/chunk-6KS56D6E.js +172 -0
  10. package/dist/chunk-AWN6ADV7.js +328 -0
  11. package/dist/chunk-CHVO6NWI.js +203 -0
  12. package/dist/chunk-CIFMBJ4H.js +3975 -0
  13. package/dist/chunk-GC7RXHLA.js +253 -0
  14. package/dist/chunk-HFXQGJY3.js +445 -0
  15. package/dist/chunk-IGFVP24Q.js +91 -0
  16. package/dist/chunk-IKPTKALB.js +78 -0
  17. package/dist/chunk-JB7RKV3C.js +66 -0
  18. package/dist/chunk-MNVFG7H4.js +611 -0
  19. package/dist/chunk-MSXMESFP.js +1910 -0
  20. package/dist/chunk-N5PAV4NM.js +127 -0
  21. package/dist/chunk-NBOABPHM.js +158 -0
  22. package/dist/chunk-OMAOROL4.js +49 -0
  23. package/dist/chunk-R7PZRSZ7.js +105 -0
  24. package/dist/chunk-TE7HPLA6.js +73 -0
  25. package/dist/chunk-TMT5KCO3.js +101 -0
  26. package/dist/chunk-UEWYFN6A.js +189 -0
  27. package/dist/chunk-WMHN5RW2.js +128 -0
  28. package/dist/chunk-XC65ORJ5.js +70 -0
  29. package/dist/chunk-YMMP4VQL.js +118 -0
  30. package/dist/chunk-YN2KGTCB.js +33 -0
  31. package/dist/chunk-YTICOB5M.js +65 -0
  32. package/dist/chunk-YVWLXFUT.js +107 -0
  33. package/dist/ci-check-sync-VBMSVWIV.js +48 -0
  34. package/dist/ci-scan-24MT5XGS.js +41 -0
  35. package/dist/ci-setup-C2NKEFRD.js +135 -0
  36. package/dist/ci-validate-7AW24LSQ.js +57 -0
  37. package/dist/cli/index.cjs +9217 -0
  38. package/dist/cli/index.d.cts +9 -0
  39. package/dist/cli/index.d.ts +9 -0
  40. package/dist/cli/index.js +339 -0
  41. package/dist/comparator-RDKX3OI7.js +13 -0
  42. package/dist/completion-MW35C2XO.js +168 -0
  43. package/dist/config-O5YRQP5Z.js +13 -0
  44. package/dist/debug-PTPXAF3K.js +131 -0
  45. package/dist/declaration-LEME4AFZ.js +10 -0
  46. package/dist/doctor-FZAUPKHS.js +129 -0
  47. package/dist/envs-compare-5K3HESX5.js +49 -0
  48. package/dist/envs-create-2XXHXMGA.js +58 -0
  49. package/dist/envs-list-NQM5252B.js +59 -0
  50. package/dist/envs-switch-6L2AQYID.js +50 -0
  51. package/dist/envs-validate-FL73Q76T.js +89 -0
  52. package/dist/fs-VH7ATUS3.js +31 -0
  53. package/dist/generator-LFZBMZZS.js +14 -0
  54. package/dist/git-BZS4DPAI.js +30 -0
  55. package/dist/help-3XJBXEHE.js +121 -0
  56. package/dist/index.cjs +12907 -0
  57. package/dist/index.d.cts +2562 -0
  58. package/dist/index.d.ts +2562 -0
  59. package/dist/index.js +3212 -0
  60. package/dist/init-Y7JQ2KYJ.js +146 -0
  61. package/dist/install-hook-SKXIV6NV.js +111 -0
  62. package/dist/json-schema-I26YNQBH.js +10 -0
  63. package/dist/key-manager-O3G55WPU.js +25 -0
  64. package/dist/middleware/express.cjs +103 -0
  65. package/dist/middleware/express.d.cts +115 -0
  66. package/dist/middleware/express.d.ts +115 -0
  67. package/dist/middleware/express.js +8 -0
  68. package/dist/middleware/fastify.cjs +91 -0
  69. package/dist/middleware/fastify.d.cts +111 -0
  70. package/dist/middleware/fastify.d.ts +111 -0
  71. package/dist/middleware/fastify.js +8 -0
  72. package/dist/module-IDIZPP4M.js +10 -0
  73. package/dist/protect-NCWPM6VC.js +161 -0
  74. package/dist/scan-TRLY36TT.js +58 -0
  75. package/dist/schema/index.cjs +4074 -0
  76. package/dist/schema/index.d.cts +1244 -0
  77. package/dist/schema/index.d.ts +1244 -0
  78. package/dist/schema/index.js +152 -0
  79. package/dist/sync-TMHMTLH2.js +186 -0
  80. package/dist/typegen-SQOSXBWM.js +80 -0
  81. package/dist/validate-IOAM5HWS.js +100 -0
  82. package/dist/vault-decrypt-U6HJZNBV.js +111 -0
  83. package/dist/vault-diff-B3ZOQTWI.js +132 -0
  84. package/dist/vault-encrypt-GUSLCSKS.js +112 -0
  85. package/dist/vault-init-GUBOTOUL.js +106 -0
  86. package/dist/vault-rekey-DAHT7JCN.js +132 -0
  87. package/dist/vault-status-GDLRU2OK.js +90 -0
  88. package/dist/vault-verify-CD76FJSF.js +102 -0
  89. package/package.json +106 -0
@@ -0,0 +1,253 @@
1
+ import {
2
+ parseEnvFile
3
+ } from "./chunk-HFXQGJY3.js";
4
+ import {
5
+ exists,
6
+ listFiles,
7
+ readFile,
8
+ writeFile
9
+ } from "./chunk-3VYXPTYV.js";
10
+ import {
11
+ FileSystemError
12
+ } from "./chunk-5G2DU52U.js";
13
+
14
+ // src/environments/manager.ts
15
+ import { resolve, join } from "path";
16
+ import { statSync } from "fs";
17
+ var KNOWN_ENVIRONMENTS = [
18
+ "development",
19
+ "staging",
20
+ "production",
21
+ "test"
22
+ ];
23
+ var ENV_FILE_PATTERNS = [
24
+ ".env",
25
+ ".env.local",
26
+ ".env.development",
27
+ ".env.development.local",
28
+ ".env.staging",
29
+ ".env.staging.local",
30
+ ".env.production",
31
+ ".env.production.local",
32
+ ".env.test",
33
+ ".env.test.local",
34
+ ".env.ci"
35
+ ];
36
+ function extractEnvName(fileName) {
37
+ if (fileName === ".env" || fileName === ".env.local") {
38
+ return null;
39
+ }
40
+ if (!fileName.startsWith(".env.")) {
41
+ return null;
42
+ }
43
+ let name = fileName.slice(5);
44
+ if (name.endsWith(".local")) {
45
+ name = name.slice(0, -6);
46
+ if (name === "") return null;
47
+ }
48
+ return name;
49
+ }
50
+ async function listEnvironments(cwd) {
51
+ const baseDir = resolve(cwd ?? process.cwd());
52
+ const environments = [];
53
+ for (const pattern of ENV_FILE_PATTERNS) {
54
+ const absolutePath = join(baseDir, pattern);
55
+ const fileExists = await exists(absolutePath);
56
+ const envName = extractEnvName(pattern);
57
+ let fileSize = 0;
58
+ let lastModified = "";
59
+ let variableCount = 0;
60
+ if (fileExists) {
61
+ try {
62
+ const stat = statSync(absolutePath);
63
+ fileSize = stat.size;
64
+ lastModified = stat.mtime.toISOString();
65
+ } catch {
66
+ }
67
+ try {
68
+ const content = await readFile(absolutePath);
69
+ const parsed = parseEnvFile(content, absolutePath);
70
+ variableCount = parsed.vars.length;
71
+ } catch {
72
+ }
73
+ }
74
+ environments.push({
75
+ name: envName ?? "base",
76
+ fileName: pattern,
77
+ absolutePath,
78
+ exists: fileExists,
79
+ variableCount,
80
+ fileSize,
81
+ lastModified
82
+ });
83
+ }
84
+ environments.sort((a, b) => {
85
+ const nameOrder = (name) => {
86
+ if (name === "base") return 0;
87
+ const idx = KNOWN_ENVIRONMENTS.indexOf(name);
88
+ return idx >= 0 ? idx + 1 : 100;
89
+ };
90
+ return nameOrder(a.name) - nameOrder(b.name);
91
+ });
92
+ return environments;
93
+ }
94
+ async function validateAllEnvironments(schema, cwd) {
95
+ const baseDir = resolve(cwd ?? process.cwd());
96
+ const results = /* @__PURE__ */ new Map();
97
+ for (const pattern of ENV_FILE_PATTERNS) {
98
+ const absolutePath = join(baseDir, pattern);
99
+ const envName = extractEnvName(pattern);
100
+ if (!await exists(absolutePath)) {
101
+ continue;
102
+ }
103
+ try {
104
+ const content = await readFile(absolutePath);
105
+ const parsed = parseEnvFile(content, absolutePath);
106
+ const vars = {};
107
+ for (const envVar of parsed.vars) {
108
+ vars[envVar.key] = envVar.value;
109
+ }
110
+ const errors = [];
111
+ const warnings = [];
112
+ for (const [key, schemaEntry] of Object.entries(schema)) {
113
+ const r = schemaEntry;
114
+ const rawValue = vars[key];
115
+ const isRequired = schemaEntry.optional !== true && schemaEntry.default === void 0 && r.hasDefault !== true;
116
+ if (isRequired && (rawValue === void 0 || rawValue === "")) {
117
+ errors.push({
118
+ field: key,
119
+ value: rawValue ?? "",
120
+ message: `Required variable "${key}" is missing`,
121
+ hint: `Set "${key}" in ${pattern}`
122
+ });
123
+ }
124
+ if (rawValue !== void 0 && r.type === "number") {
125
+ if (Number.isNaN(Number(rawValue))) {
126
+ errors.push({
127
+ field: key,
128
+ value: rawValue,
129
+ message: `"${key}" should be a number but got "${rawValue}"`,
130
+ hint: `Provide a valid number for "${key}"`
131
+ });
132
+ }
133
+ }
134
+ if (rawValue !== void 0 && r.type === "boolean") {
135
+ const lower = rawValue.toLowerCase();
136
+ if (!["true", "false", "1", "0", "yes", "no", "on", "off", ""].includes(lower)) {
137
+ errors.push({
138
+ field: key,
139
+ value: rawValue,
140
+ message: `"${key}" should be a boolean but got "${rawValue}"`,
141
+ hint: `Use true/false, 1/0, or yes/no for "${key}"`
142
+ });
143
+ }
144
+ }
145
+ if (r.isDeprecated === true && rawValue !== void 0 && rawValue !== "") {
146
+ const msg = r.deprecationMessage;
147
+ warnings.push({
148
+ field: key,
149
+ value: rawValue,
150
+ message: msg ?? `Variable "${key}" is deprecated`,
151
+ code: "DEPRECATED"
152
+ });
153
+ }
154
+ }
155
+ results.set(envName ?? pattern, {
156
+ valid: errors.length === 0,
157
+ errors,
158
+ warnings
159
+ });
160
+ } catch (error) {
161
+ const message = error instanceof Error ? error.message : String(error);
162
+ results.set(envName ?? pattern, {
163
+ valid: false,
164
+ errors: [{
165
+ field: "",
166
+ value: "",
167
+ message: `Failed to read/parse file: ${message}`,
168
+ hint: "Check that the file is a valid .env file."
169
+ }],
170
+ warnings: []
171
+ });
172
+ }
173
+ }
174
+ return results;
175
+ }
176
+ async function switchEnvironment(envName, cwd) {
177
+ const baseDir = resolve(cwd ?? process.cwd());
178
+ const envFile = join(baseDir, `.env.${envName}`);
179
+ if (!await exists(envFile)) {
180
+ throw new FileSystemError(
181
+ `Environment file ".env.${envName}" not found`,
182
+ {
183
+ path: envFile,
184
+ operation: "read",
185
+ hint: `Create a ".env.${envName}" file first, or use "ultraenv env create ${envName}".`
186
+ }
187
+ );
188
+ }
189
+ const content = await readFile(envFile);
190
+ const header = [
191
+ "# ============================================================",
192
+ `# Switched to environment: ${envName}`,
193
+ `# Source: .env.${envName}`,
194
+ `# Generated by ultraenv on ${(/* @__PURE__ */ new Date()).toISOString()}`,
195
+ "# ============================================================",
196
+ ""
197
+ ].join("\n");
198
+ const localPath = join(baseDir, ".env.local");
199
+ await writeFile(localPath, header + content);
200
+ const basePath = join(baseDir, ".env");
201
+ if (!await exists(basePath)) {
202
+ const baseHeader = [
203
+ "# ============================================================",
204
+ "# Base environment variables",
205
+ "# Generated by ultraenv",
206
+ "# ============================================================",
207
+ ""
208
+ ].join("\n");
209
+ await writeFile(basePath, baseHeader);
210
+ }
211
+ }
212
+ async function getActiveEnvironment(cwd) {
213
+ const baseDir = resolve(cwd ?? process.cwd());
214
+ const localPath = join(baseDir, ".env.local");
215
+ if (!await exists(localPath)) {
216
+ return "base";
217
+ }
218
+ try {
219
+ const content = await readFile(localPath);
220
+ const match = content.match(/^#\s*Switched to environment:\s*(\S+)/m);
221
+ if (match !== null && match[1] !== void 0) {
222
+ return match[1];
223
+ }
224
+ } catch {
225
+ }
226
+ return "base";
227
+ }
228
+ async function discoverEnvironments(cwd) {
229
+ const baseDir = resolve(cwd ?? process.cwd());
230
+ const discovered = [];
231
+ try {
232
+ const files = await listFiles(baseDir, false);
233
+ const knownNames = /* @__PURE__ */ new Set([...KNOWN_ENVIRONMENTS]);
234
+ for (const file of files) {
235
+ if (!file.startsWith(".env.")) continue;
236
+ if (file.endsWith(".local")) continue;
237
+ const name = extractEnvName(file);
238
+ if (name !== null && !knownNames.has(name)) {
239
+ discovered.push(name);
240
+ }
241
+ }
242
+ } catch {
243
+ }
244
+ return discovered.sort();
245
+ }
246
+
247
+ export {
248
+ listEnvironments,
249
+ validateAllEnvironments,
250
+ switchEnvironment,
251
+ getActiveEnvironment,
252
+ discoverEnvironments
253
+ };
@@ -0,0 +1,445 @@
1
+ import {
2
+ ParseError
3
+ } from "./chunk-5G2DU52U.js";
4
+
5
+ // src/core/parser.ts
6
+ var NAME_START_RE = /^[A-Za-z_]/;
7
+ var NAME_CHAR_RE = /^[A-Za-z0-9_]/;
8
+ var COMMENT_LINE_RE = /^[ \t]*#/;
9
+ var EXPORT_PREFIX_RE = /^[ \t]*export[ \t]+/i;
10
+ function resolveEscapeSequence(chars, startIndex, filePath, lineNumber) {
11
+ const next = startIndex + 1 < chars.length ? chars[startIndex + 1] : "";
12
+ if (next === "") {
13
+ return { resolved: "\\", charsConsumed: 1 };
14
+ }
15
+ switch (next) {
16
+ case "n":
17
+ return { resolved: "\n", charsConsumed: 2 };
18
+ case "t":
19
+ return { resolved: " ", charsConsumed: 2 };
20
+ case "r":
21
+ return { resolved: "\r", charsConsumed: 2 };
22
+ case "\\":
23
+ return { resolved: "\\", charsConsumed: 2 };
24
+ case '"':
25
+ return { resolved: '"', charsConsumed: 2 };
26
+ /* v8 ignore start */
27
+ case "$":
28
+ return { resolved: "$", charsConsumed: 2 };
29
+ case "/":
30
+ return { resolved: "/", charsConsumed: 2 };
31
+ /* v8 ignore stop */
32
+ case "x":
33
+ case "X": {
34
+ const hex = chars.slice(startIndex + 2, startIndex + 4).join("");
35
+ if (hex.length === 2 && /^[0-9A-Fa-f]{2}$/.test(hex)) {
36
+ const codePoint = parseInt(hex, 16);
37
+ if (codePoint === 0) {
38
+ throw new ParseError("Null character (\\x00) is not allowed in .env values", {
39
+ line: lineNumber,
40
+ filePath,
41
+ column: startIndex
42
+ });
43
+ }
44
+ return { resolved: String.fromCodePoint(codePoint), charsConsumed: 4 };
45
+ }
46
+ throw new ParseError(
47
+ `Invalid hex escape sequence: ${"\\"}x${hex}`,
48
+ {
49
+ line: lineNumber,
50
+ filePath,
51
+ hint: "Hex escapes must be exactly 2 hex digits, e.g. \\x0A for newline."
52
+ }
53
+ );
54
+ }
55
+ case "u":
56
+ case "U": {
57
+ const hexDigits = chars.slice(startIndex + 2, startIndex + 6).join("");
58
+ if (hexDigits.length === 4 && /^[0-9A-Fa-f]{4}$/.test(hexDigits)) {
59
+ const codePoint = parseInt(hexDigits, 16);
60
+ if (codePoint === 0) {
61
+ throw new ParseError("Null character (\\u0000) is not allowed in .env values", {
62
+ line: lineNumber,
63
+ filePath
64
+ });
65
+ }
66
+ return { resolved: String.fromCodePoint(codePoint), charsConsumed: 6 };
67
+ }
68
+ throw new ParseError(
69
+ `Invalid unicode escape sequence: ${"\\"}u${hexDigits}`,
70
+ {
71
+ line: lineNumber,
72
+ filePath,
73
+ hint: 'Unicode escapes must be exactly 4 hex digits, e.g. \\u0041 for "A".'
74
+ }
75
+ );
76
+ }
77
+ /* v8 ignore start */
78
+ default: {
79
+ return { resolved: `\\${next}`, charsConsumed: 2 };
80
+ }
81
+ }
82
+ }
83
+ function parseDoubleQuotedValue(lines, startLineIndex, startCharIndex, filePath, lineNumber) {
84
+ const parts = [];
85
+ let currentLine = lines[startLineIndex];
86
+ let chars = Array.from(currentLine.slice(startCharIndex));
87
+ let charIndex = 0;
88
+ let lineIndex = startLineIndex;
89
+ while (lineIndex < lines.length) {
90
+ while (charIndex < chars.length) {
91
+ const ch = chars[charIndex];
92
+ if (ch === '"') {
93
+ return {
94
+ value: parts.join(""),
95
+ closed: true,
96
+ extraLines: lineIndex - startLineIndex
97
+ };
98
+ }
99
+ if (ch === "\n") {
100
+ parts.push("\n");
101
+ charIndex++;
102
+ continue;
103
+ }
104
+ if (ch === "\r") {
105
+ parts.push("\r");
106
+ charIndex++;
107
+ if (charIndex < chars.length && chars[charIndex] === "\n") {
108
+ charIndex++;
109
+ }
110
+ continue;
111
+ }
112
+ if (ch === "\\") {
113
+ const resolved = resolveEscapeSequence(
114
+ chars,
115
+ charIndex,
116
+ filePath,
117
+ lineNumber + lineIndex - startLineIndex
118
+ );
119
+ parts.push(resolved.resolved);
120
+ charIndex += resolved.charsConsumed;
121
+ continue;
122
+ }
123
+ parts.push(ch);
124
+ charIndex++;
125
+ }
126
+ lineIndex++;
127
+ if (lineIndex < lines.length) {
128
+ parts.push("\n");
129
+ currentLine = lines[lineIndex];
130
+ chars = Array.from(currentLine);
131
+ charIndex = 0;
132
+ }
133
+ }
134
+ return {
135
+ value: parts.join(""),
136
+ closed: false,
137
+ extraLines: lineIndex - startLineIndex - 1
138
+ };
139
+ }
140
+ function parseSingleQuotedValue(lines, startLineIndex, startCharIndex) {
141
+ const parts = [];
142
+ let currentLine = lines[startLineIndex];
143
+ let chars = Array.from(currentLine.slice(startCharIndex));
144
+ let charIndex = 0;
145
+ let lineIndex = startLineIndex;
146
+ while (lineIndex < lines.length) {
147
+ while (charIndex < chars.length) {
148
+ const ch = chars[charIndex];
149
+ if (ch === "'") {
150
+ return {
151
+ value: parts.join(""),
152
+ closed: true,
153
+ extraLines: lineIndex - startLineIndex
154
+ };
155
+ }
156
+ parts.push(ch);
157
+ charIndex++;
158
+ }
159
+ lineIndex++;
160
+ if (lineIndex < lines.length) {
161
+ parts.push("\n");
162
+ currentLine = lines[lineIndex];
163
+ chars = Array.from(currentLine);
164
+ charIndex = 0;
165
+ }
166
+ }
167
+ return {
168
+ value: parts.join(""),
169
+ closed: false,
170
+ extraLines: lineIndex - startLineIndex - 1
171
+ };
172
+ }
173
+ function parseBacktickQuotedValue(lines, startLineIndex, startCharIndex) {
174
+ const parts = [];
175
+ let currentLine = lines[startLineIndex];
176
+ let chars = Array.from(currentLine.slice(startCharIndex));
177
+ let charIndex = 0;
178
+ let lineIndex = startLineIndex;
179
+ while (lineIndex < lines.length) {
180
+ while (charIndex < chars.length) {
181
+ const ch = chars[charIndex];
182
+ if (ch === "`") {
183
+ return {
184
+ value: parts.join(""),
185
+ closed: true,
186
+ extraLines: lineIndex - startLineIndex
187
+ };
188
+ }
189
+ parts.push(ch);
190
+ charIndex++;
191
+ }
192
+ lineIndex++;
193
+ if (lineIndex < lines.length) {
194
+ parts.push("\n");
195
+ currentLine = lines[lineIndex];
196
+ chars = Array.from(currentLine);
197
+ charIndex = 0;
198
+ }
199
+ }
200
+ return {
201
+ value: parts.join(""),
202
+ closed: false,
203
+ extraLines: lineIndex - startLineIndex - 1
204
+ };
205
+ }
206
+ function parseKey(line, lineIndex, filePath) {
207
+ let i = 0;
208
+ const len = line.length;
209
+ while (i < len && (line[i] === " " || line[i] === " ")) {
210
+ i++;
211
+ }
212
+ const keyStart = i;
213
+ if (i >= len || !NAME_START_RE.test(line[i])) {
214
+ throw new ParseError("Expected a variable name starting with a letter or underscore", {
215
+ line: lineIndex + 1,
216
+ column: i + 1,
217
+ raw: line,
218
+ filePath
219
+ });
220
+ }
221
+ i++;
222
+ while (i < len && NAME_CHAR_RE.test(line[i])) {
223
+ i++;
224
+ }
225
+ const key = line.slice(keyStart, i);
226
+ return { key, endIndex: i };
227
+ }
228
+ function extractInlineComment(value) {
229
+ let commentStart = -1;
230
+ for (let i = value.length - 1; i >= 0; i--) {
231
+ if (value[i] === " " || value[i] === " ") {
232
+ const rest = value.slice(i + 1);
233
+ if (rest.length > 0 && rest[0] === "#") {
234
+ commentStart = i;
235
+ }
236
+ break;
237
+ }
238
+ if (value[i] !== " " && value[i] !== " ") {
239
+ break;
240
+ }
241
+ }
242
+ if (commentStart >= 0) {
243
+ return {
244
+ cleanValue: value.slice(0, commentStart).trimEnd(),
245
+ comment: value.slice(commentStart + 1).trim()
246
+ };
247
+ }
248
+ return { cleanValue: value, comment: "" };
249
+ }
250
+ function parseEnvFile(content, filePath) {
251
+ const resolvedPath = filePath ?? ".env";
252
+ const lines = splitLines(content);
253
+ const vars = [];
254
+ let lineIndex = 0;
255
+ while (lineIndex < lines.length) {
256
+ const rawLine = lines[lineIndex];
257
+ const oneBasedLine = lineIndex + 1;
258
+ if (isEmptyLine(rawLine)) {
259
+ lineIndex++;
260
+ continue;
261
+ }
262
+ if (COMMENT_LINE_RE.test(rawLine)) {
263
+ lineIndex++;
264
+ continue;
265
+ }
266
+ let workingLine = rawLine;
267
+ const exportMatch = EXPORT_PREFIX_RE.exec(workingLine);
268
+ if (exportMatch !== null) {
269
+ workingLine = workingLine.slice(exportMatch[0].length);
270
+ }
271
+ if (isEmptyLine(workingLine) || COMMENT_LINE_RE.test(workingLine)) {
272
+ lineIndex++;
273
+ continue;
274
+ }
275
+ let key;
276
+ let afterKeyIndex;
277
+ try {
278
+ const parsed = parseKey(workingLine, lineIndex, resolvedPath);
279
+ key = parsed.key;
280
+ afterKeyIndex = parsed.endIndex;
281
+ } catch {
282
+ lineIndex++;
283
+ continue;
284
+ }
285
+ while (afterKeyIndex < workingLine.length && (workingLine[afterKeyIndex] === " " || workingLine[afterKeyIndex] === " ")) {
286
+ afterKeyIndex++;
287
+ }
288
+ if (afterKeyIndex >= workingLine.length || workingLine[afterKeyIndex] !== "=") {
289
+ const restAfterKey = workingLine.slice(afterKeyIndex).trim();
290
+ if (restAfterKey === "") {
291
+ const varEntry2 = {
292
+ key,
293
+ value: "",
294
+ raw: "",
295
+ source: resolvedPath,
296
+ lineNumber: oneBasedLine,
297
+ comment: ""
298
+ };
299
+ vars.push(varEntry2);
300
+ lineIndex++;
301
+ continue;
302
+ }
303
+ lineIndex++;
304
+ continue;
305
+ }
306
+ afterKeyIndex++;
307
+ while (afterKeyIndex < workingLine.length && (workingLine[afterKeyIndex] === " " || workingLine[afterKeyIndex] === " ")) {
308
+ afterKeyIndex++;
309
+ }
310
+ const valueStartIndex = afterKeyIndex;
311
+ if (valueStartIndex >= workingLine.length) {
312
+ const varEntry2 = {
313
+ key,
314
+ value: "",
315
+ raw: "",
316
+ source: resolvedPath,
317
+ lineNumber: oneBasedLine,
318
+ comment: ""
319
+ };
320
+ vars.push(varEntry2);
321
+ lineIndex++;
322
+ continue;
323
+ }
324
+ const firstChar = workingLine[valueStartIndex];
325
+ let value;
326
+ let raw;
327
+ let comment = "";
328
+ let extraLinesConsumed = 0;
329
+ if (firstChar === '"') {
330
+ const result = parseDoubleQuotedValue(
331
+ lines,
332
+ lineIndex,
333
+ valueStartIndex + 1,
334
+ resolvedPath,
335
+ oneBasedLine
336
+ );
337
+ if (!result.closed) {
338
+ throw new ParseError("Unterminated double-quoted string", {
339
+ line: oneBasedLine,
340
+ raw: workingLine,
341
+ filePath: resolvedPath,
342
+ hint: 'Make sure every double-quoted value has a closing ".'
343
+ });
344
+ }
345
+ value = result.value;
346
+ raw = `"${result.value}"`;
347
+ extraLinesConsumed = result.extraLines;
348
+ const closingQuoteLine = lines[lineIndex + extraLinesConsumed];
349
+ const afterClosingQuote = closingQuoteLine.slice(
350
+ valueStartIndex + 1 + result.value.length + 1
351
+ );
352
+ const trimmedAfter = afterClosingQuote.trim();
353
+ if (trimmedAfter.startsWith("#")) {
354
+ comment = trimmedAfter.slice(1).trim();
355
+ }
356
+ } else if (firstChar === "'") {
357
+ const result = parseSingleQuotedValue(
358
+ lines,
359
+ lineIndex,
360
+ valueStartIndex + 1
361
+ );
362
+ if (!result.closed) {
363
+ throw new ParseError("Unterminated single-quoted string", {
364
+ line: oneBasedLine,
365
+ raw: workingLine,
366
+ filePath: resolvedPath,
367
+ hint: "Make sure every single-quoted value has a closing '."
368
+ });
369
+ }
370
+ value = result.value;
371
+ raw = `'${result.value}'`;
372
+ extraLinesConsumed = result.extraLines;
373
+ const closingQuoteLine = lines[lineIndex + extraLinesConsumed];
374
+ const afterClosingQuote = closingQuoteLine.slice(
375
+ valueStartIndex + 1 + result.value.length + 1
376
+ );
377
+ const trimmedAfter = afterClosingQuote.trim();
378
+ if (trimmedAfter.startsWith("#")) {
379
+ comment = trimmedAfter.slice(1).trim();
380
+ }
381
+ } else if (firstChar === "`") {
382
+ const result = parseBacktickQuotedValue(
383
+ lines,
384
+ lineIndex,
385
+ valueStartIndex + 1
386
+ );
387
+ if (!result.closed) {
388
+ throw new ParseError("Unterminated backtick-quoted string", {
389
+ line: oneBasedLine,
390
+ raw: workingLine,
391
+ filePath: resolvedPath,
392
+ hint: "Make sure every backtick-quoted value has a closing `."
393
+ });
394
+ }
395
+ value = result.value;
396
+ raw = `\`${result.value}\``;
397
+ extraLinesConsumed = result.extraLines;
398
+ const closingQuoteLine = lines[lineIndex + extraLinesConsumed];
399
+ const afterClosingQuote = closingQuoteLine.slice(
400
+ valueStartIndex + 1 + result.value.length + 1
401
+ );
402
+ const trimmedAfter = afterClosingQuote.trim();
403
+ if (trimmedAfter.startsWith("#")) {
404
+ comment = trimmedAfter.slice(1).trim();
405
+ }
406
+ } else {
407
+ let rawValue = workingLine.slice(valueStartIndex);
408
+ const extracted = extractInlineComment(rawValue);
409
+ value = extracted.cleanValue;
410
+ comment = extracted.comment;
411
+ raw = rawValue;
412
+ }
413
+ const varEntry = {
414
+ key,
415
+ value,
416
+ raw,
417
+ source: resolvedPath,
418
+ lineNumber: oneBasedLine,
419
+ comment
420
+ };
421
+ vars.push(varEntry);
422
+ lineIndex += 1 + extraLinesConsumed;
423
+ }
424
+ return {
425
+ path: resolvedPath,
426
+ vars,
427
+ exists: true
428
+ };
429
+ }
430
+ function splitLines(content) {
431
+ const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
432
+ return normalized.split("\n");
433
+ }
434
+ function isEmptyLine(line) {
435
+ for (let i = 0; i < line.length; i++) {
436
+ if (line[i] !== " " && line[i] !== " ") {
437
+ return false;
438
+ }
439
+ }
440
+ return true;
441
+ }
442
+
443
+ export {
444
+ parseEnvFile
445
+ };