@kindgi/dotenv-file 0.0.0-stage → 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/layers.js ADDED
@@ -0,0 +1,54 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+ /**
4
+ * Layered env files — several dotenv files read as one view, the way
5
+ * an application reads `.env` + `.env.local`.
6
+ *
7
+ * Layers are ordered LOWEST precedence first: a key in a later layer
8
+ * overrides the same key in an earlier one. Raw values are merged
9
+ * first, then expanded once over the merged view, so a reference in
10
+ * `.env` to a name defined in `.env.local` sees the `.env.local` value
11
+ * (matching how Next.js loads its files). Pure — the caller reads the
12
+ * files and passes their contents (`null` for a file that doesn't
13
+ * exist), so this module never touches the filesystem.
14
+ */
15
+ import { expandEnv } from './expand.js';
16
+ import { parseEnvFile } from './parse.js';
17
+ export function readEnvLayers(layers, options = {}) {
18
+ const raw = {};
19
+ const origin = {};
20
+ const present = [];
21
+ const diagnostics = [];
22
+ for (const layer of layers) {
23
+ if (layer.contents === null)
24
+ continue;
25
+ present.push(layer.source);
26
+ let lineNo = 1;
27
+ for (const line of parseEnvFile(layer.contents)) {
28
+ if (line.kind === 'entry' && line.key !== undefined && line.value !== undefined) {
29
+ raw[line.key] = line.value;
30
+ origin[line.key] = layer.source;
31
+ }
32
+ else if (line.kind === 'malformed') {
33
+ diagnostics.push({
34
+ kind: 'malformed',
35
+ source: layer.source,
36
+ line: lineNo,
37
+ raw: line.raw,
38
+ reason: line.reason ?? 'not a `KEY=value` line',
39
+ });
40
+ }
41
+ lineNo += line.raw.split('\n').length;
42
+ }
43
+ }
44
+ const expanded = expandEnv(raw, options);
45
+ for (const d of expanded.diagnostics) {
46
+ diagnostics.push({ ...d, source: origin[d.key] ?? '' });
47
+ }
48
+ return { values: expanded.values, raw, origin, present, diagnostics };
49
+ }
50
+ /** One file's values, parsed and expanded. */
51
+ export function readEnv(contents, options = {}) {
52
+ return { ...readEnvLayers([{ source: '', contents }], options).values };
53
+ }
54
+ //# sourceMappingURL=layers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layers.js","sourceRoot":"","sources":["../src/layers.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,iCAAiC;AAEjC;;;;;;;;;;;GAWG;AAEH,OAAO,EAA6C,SAAS,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAkC1C,MAAM,UAAU,aAAa,CAC3B,MAA2B,EAC3B,UAAyB,EAAE;IAE3B,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,WAAW,GAAoB,EAAE,CAAC;IAExC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI;YAAE,SAAS;QACtC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC3B,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,KAAK,MAAM,IAAI,IAAI,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;YAChD,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBAChF,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;gBAC3B,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC;YAClC,CAAC;iBAAM,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBACrC,WAAW,CAAC,IAAI,CAAC;oBACf,IAAI,EAAE,WAAW;oBACjB,MAAM,EAAE,KAAK,CAAC,MAAM;oBACpB,IAAI,EAAE,MAAM;oBACZ,GAAG,EAAE,IAAI,CAAC,GAAG;oBACb,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,wBAAwB;iBAChD,CAAC,CAAC;YACL,CAAC;YACD,MAAM,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;QACxC,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACzC,KAAK,MAAM,CAAC,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC;QACrC,WAAW,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;AACxE,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,OAAO,CAAC,QAAgB,EAAE,UAAyB,EAAE;IACnE,OAAO,EAAE,GAAG,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC;AAC1E,CAAC"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Structured, lossless dotenv parser.
3
+ *
4
+ * **Grammar = `dotenv`'s `parse()`** (verified against `dotenv@16.3.1`,
5
+ * the version Next.js 16 bundles, by `tests/conformance.test.ts`). An
6
+ * application Kindgi is embedded in reads its `.env` files with that
7
+ * parser; Kindgi reading the same file must see the same values.
8
+ *
9
+ * - `KEY=value` or `KEY: value` (colon + at least one space).
10
+ * Optional leading whitespace and an optional `export ` prefix.
11
+ * - Keys: `[A-Za-z0-9_.-]+` (dotenv accepts lowercase, dots, dashes).
12
+ * - Unquoted values end at the first `#` (inline comment) and are
13
+ * trimmed.
14
+ * - `'…'`, `"…"` and `` `…` `` values may span lines. The closing
15
+ * quote must be followed only by whitespace or a `# comment` on its
16
+ * line; otherwise the value is read unquoted. Inside double quotes
17
+ * `\n` / `\r` become newline / carriage return; nothing else is
18
+ * unescaped (`\"` stays `\"`, exactly as dotenv does).
19
+ * - Duplicate keys: the last one wins.
20
+ * - Anything else is ignored by dotenv; here it surfaces as a
21
+ * `malformed` line so hand-edited files round-trip untouched.
22
+ *
23
+ * Deliberate divergence (pathological inputs only): dotenv's regex lets
24
+ * a key's separator or an empty value's leading whitespace run across
25
+ * a newline (`KEY=` followed by a line that starts with a quote joins
26
+ * the two). Here a key, its separator, and the start of its value are
27
+ * always on one line.
28
+ *
29
+ * Variable expansion (`${VAR}`) is a separate step — see `expand.ts`.
30
+ * `parseEnvFile` returns values exactly as written.
31
+ */
32
+ export interface EnvLine {
33
+ readonly kind: 'blank' | 'comment' | 'entry' | 'malformed';
34
+ /**
35
+ * The line's source text. For an `entry` whose quoted value spans
36
+ * several lines, `raw` holds all of them joined with `\n`.
37
+ */
38
+ readonly raw: string;
39
+ readonly key?: string;
40
+ /** Parsed (unexpanded) value. */
41
+ readonly value?: string;
42
+ /** `true` when the entry was written with an `export ` prefix. */
43
+ readonly exported?: boolean;
44
+ readonly reason?: string;
45
+ }
46
+ /** Keys dotenv accepts when READING a file. */
47
+ export declare const DOTENV_KEY_REGEX: RegExp;
48
+ /**
49
+ * Keys Kindgi WRITES: POSIX-shell env-var shape. Stricter than
50
+ * `DOTENV_KEY_REGEX` so every name Kindgi writes is also exportable
51
+ * from a shell.
52
+ */
53
+ export declare const ENV_KEY_REGEX: RegExp;
54
+ /**
55
+ * Split dotenv text into structured lines. `serializeEnvFile` of the
56
+ * result reproduces the input (line endings normalised to `\n`, which
57
+ * is also what dotenv does before parsing).
58
+ */
59
+ export declare function parseEnvFile(raw: string): readonly EnvLine[];
60
+ /**
61
+ * Parse a single entry's source text (possibly multi-line). Returns
62
+ * `undefined` unless the whole text is exactly one entry. Used by the
63
+ * serializer to decide whether an entry's `raw` is still faithful to
64
+ * its `key` / `value`.
65
+ */
66
+ export declare function parseEntryRaw(raw: string): EnvLine | undefined;
67
+ /** Flatten entries into a record (last duplicate wins). Values are unexpanded. */
68
+ export declare function envLinesToRecord(lines: readonly EnvLine[]): Record<string, string>;
69
+ //# sourceMappingURL=parse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,WAAW,CAAC;IAC3D;;;OAGG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,+CAA+C;AAC/C,eAAO,MAAM,gBAAgB,QAAc,CAAC;AAE5C;;;;GAIG;AACH,eAAO,MAAM,aAAa,QAAuB,CAAC;AAMlD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,OAAO,EAAE,CAa5D;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAK9D;AAED,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQlF"}
package/dist/parse.js ADDED
@@ -0,0 +1,173 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+ /** Keys dotenv accepts when READING a file. */
4
+ export const DOTENV_KEY_REGEX = /^[\w.-]+$/;
5
+ /**
6
+ * Keys Kindgi WRITES: POSIX-shell env-var shape. Stricter than
7
+ * `DOTENV_KEY_REGEX` so every name Kindgi writes is also exportable
8
+ * from a shell.
9
+ */
10
+ export const ENV_KEY_REGEX = /^[A-Z_][A-Z0-9_]*$/;
11
+ const ENTRY_HEAD = /^\s*(export\s+)?([\w.-]+)(?:\s*=|:\s)\s*/;
12
+ const TRAILER = /^\s*(?:#.*)?$/;
13
+ const QUOTES = new Set(["'", '"', '`']);
14
+ /**
15
+ * Split dotenv text into structured lines. `serializeEnvFile` of the
16
+ * result reproduces the input (line endings normalised to `\n`, which
17
+ * is also what dotenv does before parsing).
18
+ */
19
+ export function parseEnvFile(raw) {
20
+ if (raw === '')
21
+ return [];
22
+ const physical = raw.split(/\r\n?|\n/);
23
+ const lines = physical.length > 0 && physical[physical.length - 1] === '' ? physical.slice(0, -1) : physical;
24
+ const out = [];
25
+ let i = 0;
26
+ while (i < lines.length) {
27
+ const { line, consumed } = parseAt(lines, i);
28
+ out.push(line);
29
+ i += consumed;
30
+ }
31
+ return out;
32
+ }
33
+ /**
34
+ * Parse a single entry's source text (possibly multi-line). Returns
35
+ * `undefined` unless the whole text is exactly one entry. Used by the
36
+ * serializer to decide whether an entry's `raw` is still faithful to
37
+ * its `key` / `value`.
38
+ */
39
+ export function parseEntryRaw(raw) {
40
+ const lines = parseEnvFile(raw);
41
+ if (lines.length !== 1)
42
+ return undefined;
43
+ const [only] = lines;
44
+ return only?.kind === 'entry' ? only : undefined;
45
+ }
46
+ /** Flatten entries into a record (last duplicate wins). Values are unexpanded. */
47
+ export function envLinesToRecord(lines) {
48
+ const out = {};
49
+ for (const line of lines) {
50
+ if (line.kind === 'entry' && line.key !== undefined && line.value !== undefined) {
51
+ out[line.key] = line.value;
52
+ }
53
+ }
54
+ return out;
55
+ }
56
+ function parseAt(lines, i) {
57
+ const first = lines[i] ?? '';
58
+ if (first.trim() === '')
59
+ return { line: { kind: 'blank', raw: first }, consumed: 1 };
60
+ if (first.trimStart().startsWith('#')) {
61
+ return { line: { kind: 'comment', raw: first }, consumed: 1 };
62
+ }
63
+ const head = ENTRY_HEAD.exec(first);
64
+ if (head === null) {
65
+ return {
66
+ line: { kind: 'malformed', raw: first, reason: 'not a `KEY=value` line' },
67
+ consumed: 1,
68
+ };
69
+ }
70
+ const key = head[2] ?? '';
71
+ const exported = head[1] !== undefined;
72
+ const valueStart = head[0].length;
73
+ const opener = first[valueStart];
74
+ if (opener !== undefined && QUOTES.has(opener)) {
75
+ const quoted = scanQuoted(lines, i, valueStart, opener);
76
+ if (quoted !== undefined) {
77
+ const raw = lines.slice(i, i + quoted.lineCount).join('\n');
78
+ return {
79
+ line: entry(raw, key, unescapeQuoted(quoted.inner, opener), exported),
80
+ consumed: quoted.lineCount,
81
+ };
82
+ }
83
+ }
84
+ return { line: entry(first, key, unquotedValue(first.slice(valueStart)), exported), consumed: 1 };
85
+ }
86
+ function entry(raw, key, value, exported) {
87
+ return exported
88
+ ? { kind: 'entry', raw, key, value, exported: true }
89
+ : { kind: 'entry', raw, key, value };
90
+ }
91
+ /**
92
+ * Unquoted value: everything up to the first `#`, trimmed. dotenv then
93
+ * strips one pair of matching outer quotes from the trimmed text (e.g.
94
+ * `KEY="a" "b"` → `a" "b`), and expands `\n` / `\r` if that pair was
95
+ * double quotes. Replicated exactly.
96
+ */
97
+ function unquotedValue(rest) {
98
+ const hash = rest.indexOf('#');
99
+ const v = (hash === -1 ? rest : rest.slice(0, hash)).trim();
100
+ const q = v[0];
101
+ if (v.length >= 2 && q !== undefined && QUOTES.has(q) && v[v.length - 1] === q) {
102
+ return unescapeQuoted(v.slice(1, -1), q);
103
+ }
104
+ return v;
105
+ }
106
+ function unescapeQuoted(inner, quote) {
107
+ return quote === '"' ? inner.replace(/\\n/g, '\n').replace(/\\r/g, '\r') : inner;
108
+ }
109
+ /**
110
+ * Closing-quote candidates in the order dotenv's regex tries them: the
111
+ * first quote not preceded by a backslash, then each backslash-escaped
112
+ * quote before it, last to first.
113
+ */
114
+ function closingCandidates(lines, startLine, startCol, quote) {
115
+ const escaped = [];
116
+ for (let line = startLine; line < lines.length; line += 1) {
117
+ const text = lines[line] ?? '';
118
+ const found = scanLine(text, line === startLine ? startCol + 1 : 0, quote);
119
+ for (const col of found.escaped)
120
+ escaped.push({ line, col });
121
+ if (found.closing !== -1)
122
+ return [{ line, col: found.closing }, ...escaped.reverse()];
123
+ }
124
+ return escaped.reverse();
125
+ }
126
+ /** One line's escaped-quote columns, and the first unescaped quote (-1 if none). */
127
+ function scanLine(text, from, quote) {
128
+ const escaped = [];
129
+ let col = from;
130
+ while (col < text.length) {
131
+ if (text[col] === '\\' && text[col + 1] === quote) {
132
+ escaped.push(col + 1);
133
+ col += 2;
134
+ }
135
+ else if (text[col] === quote) {
136
+ return { escaped, closing: col };
137
+ }
138
+ else {
139
+ col += 1;
140
+ }
141
+ }
142
+ return { escaped, closing: -1 };
143
+ }
144
+ /**
145
+ * Find the closing quote the way dotenv's `'(?:\\'|[^'])*'` alternative
146
+ * does (per quote character): the first quote not preceded by a
147
+ * backslash is tried first; then, backtracking, each backslash-escaped
148
+ * quote before it, last to first. A candidate is accepted when the rest
149
+ * of its line is whitespace or a `# comment`. `undefined` = no candidate
150
+ * works and the value is read unquoted.
151
+ */
152
+ function scanQuoted(lines, startLine, startCol, quote) {
153
+ for (const c of closingCandidates(lines, startLine, startCol, quote)) {
154
+ const closingLine = lines[c.line] ?? '';
155
+ if (!TRAILER.test(closingLine.slice(c.col + 1)))
156
+ continue;
157
+ return {
158
+ inner: sliceAcross(lines, startLine, startCol + 1, c.line, c.col),
159
+ lineCount: c.line - startLine + 1,
160
+ };
161
+ }
162
+ return undefined;
163
+ }
164
+ function sliceAcross(lines, fromLine, fromCol, toLine, toCol) {
165
+ if (fromLine === toLine)
166
+ return (lines[fromLine] ?? '').slice(fromCol, toCol);
167
+ const parts = [(lines[fromLine] ?? '').slice(fromCol)];
168
+ for (let li = fromLine + 1; li < toLine; li += 1)
169
+ parts.push(lines[li] ?? '');
170
+ parts.push((lines[toLine] ?? '').slice(0, toCol));
171
+ return parts.join('\n');
172
+ }
173
+ //# sourceMappingURL=parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.js","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Writer side: render entries so they READ BACK as the exact value
3
+ * written — through `dotenv`'s parser and `expand.ts`'s `${VAR}`
4
+ * expansion (the pipeline Next.js / Vite / dotenv-cli apply) — and keep
5
+ * every untouched line byte-for-byte.
6
+ *
7
+ * Round-trip contract, for any string `v` the writer accepts:
8
+ *
9
+ * expandEnv(envLinesToRecord(parseEnvFile(setKey('', K, v).contents))).values[K] === v
10
+ *
11
+ * Quoting, first rule that fits wins:
12
+ * 1. Plain value (`[A-Za-z0-9_.,:/@+=%~-]+`) → unquoted.
13
+ * 2. Double quotes, when the value has no `"` and no literal `\n` /
14
+ * `\r` sequence (dotenv would turn those into control chars).
15
+ * Real newlines / carriage returns are written as `\n` / `\r`.
16
+ * 3. Single quotes (literal), when the value has no `'` and no `\r`.
17
+ * 4. Backticks (literal), when the value has no `` ` `` and no `\r`.
18
+ * Otherwise the value is not representable in dotenv syntax and
19
+ * `EnvValueNotRepresentableError` is thrown — never a silently
20
+ * different value.
21
+ *
22
+ * `$` is always written as `\$`: expanders substitute `$NAME` even
23
+ * inside quotes, and `\$` is the one escape they all undo.
24
+ */
25
+ import { type EnvLine } from './parse.js';
26
+ /** Thrown when a value cannot be written so that it reads back unchanged. */
27
+ export declare class EnvValueNotRepresentableError extends Error {
28
+ readonly key: string;
29
+ constructor(key: string);
30
+ }
31
+ /** Render one `KEY=value` entry (throws `EnvValueNotRepresentableError`). */
32
+ export declare function renderEntry(key: string, value: string, options?: {
33
+ exported?: boolean;
34
+ }): string;
35
+ /**
36
+ * Serialize lines back to file text with a trailing newline. An entry
37
+ * whose `raw` still parses to its `key` / `value` is emitted verbatim
38
+ * (hand-written quoting, `export`, inline comments survive); any other
39
+ * entry is re-rendered from `key` / `value`. Non-entry lines are
40
+ * emitted verbatim — a malformed line is never dropped.
41
+ */
42
+ export declare function serializeEnvFile(lines: readonly EnvLine[]): string;
43
+ /**
44
+ * Set `KEY=value`. When the key already exists, the LAST occurrence —
45
+ * the one readers see — is replaced in place (keeping its `export`
46
+ * prefix); otherwise the entry is appended. `key` must be a POSIX env
47
+ * name (`ENV_KEY_REGEX`).
48
+ */
49
+ export declare function setKey(rawFileContents: string, key: string, value: string): {
50
+ readonly contents: string;
51
+ readonly overwritten: boolean;
52
+ };
53
+ /**
54
+ * Remove every entry for KEY; unrelated lines are preserved.
55
+ * Idempotent — `removed: false` when the key wasn't present.
56
+ */
57
+ export declare function unsetKey(rawFileContents: string, key: string): {
58
+ readonly contents: string;
59
+ readonly removed: boolean;
60
+ };
61
+ //# sourceMappingURL=serialize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../src/serialize.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAiB,KAAK,OAAO,EAA+B,MAAM,YAAY,CAAC;AAEtF,6EAA6E;AAC7E,qBAAa,6BAA8B,SAAQ,KAAK;IACtD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;gBACT,GAAG,EAAE,MAAM;CAOxB;AAID,6EAA6E;AAC7E,wBAAgB,WAAW,CACzB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAO,GACnC,MAAM,CAGR;AAgBD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,GAAG,MAAM,CAGlE;AAgBD;;;;;GAKG;AACH,wBAAgB,MAAM,CACpB,eAAe,EAAE,MAAM,EACvB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,GACZ;IAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAqB9D;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,eAAe,EAAE,MAAM,EACvB,GAAG,EAAE,MAAM,GACV;IAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAY1D"}
@@ -0,0 +1,130 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+ /**
4
+ * Writer side: render entries so they READ BACK as the exact value
5
+ * written — through `dotenv`'s parser and `expand.ts`'s `${VAR}`
6
+ * expansion (the pipeline Next.js / Vite / dotenv-cli apply) — and keep
7
+ * every untouched line byte-for-byte.
8
+ *
9
+ * Round-trip contract, for any string `v` the writer accepts:
10
+ *
11
+ * expandEnv(envLinesToRecord(parseEnvFile(setKey('', K, v).contents))).values[K] === v
12
+ *
13
+ * Quoting, first rule that fits wins:
14
+ * 1. Plain value (`[A-Za-z0-9_.,:/@+=%~-]+`) → unquoted.
15
+ * 2. Double quotes, when the value has no `"` and no literal `\n` /
16
+ * `\r` sequence (dotenv would turn those into control chars).
17
+ * Real newlines / carriage returns are written as `\n` / `\r`.
18
+ * 3. Single quotes (literal), when the value has no `'` and no `\r`.
19
+ * 4. Backticks (literal), when the value has no `` ` `` and no `\r`.
20
+ * Otherwise the value is not representable in dotenv syntax and
21
+ * `EnvValueNotRepresentableError` is thrown — never a silently
22
+ * different value.
23
+ *
24
+ * `$` is always written as `\$`: expanders substitute `$NAME` even
25
+ * inside quotes, and `\$` is the one escape they all undo.
26
+ */
27
+ import { ENV_KEY_REGEX, parseEntryRaw, parseEnvFile } from './parse.js';
28
+ /** Thrown when a value cannot be written so that it reads back unchanged. */
29
+ export class EnvValueNotRepresentableError extends Error {
30
+ key;
31
+ constructor(key) {
32
+ super(`The value for ${key} cannot be written to a dotenv file so that it reads back unchanged (it mixes quote characters with content no dotenv quoting style can hold).`);
33
+ this.name = 'EnvValueNotRepresentableError';
34
+ this.key = key;
35
+ }
36
+ }
37
+ const PLAIN = /^[A-Za-z0-9_.,:/@+=%~-]+$/;
38
+ /** Render one `KEY=value` entry (throws `EnvValueNotRepresentableError`). */
39
+ export function renderEntry(key, value, options = {}) {
40
+ const prefix = options.exported === true ? 'export ' : '';
41
+ return `${prefix}${key}=${renderValue(key, value)}`;
42
+ }
43
+ function renderValue(key, value) {
44
+ if (value === '')
45
+ return '""';
46
+ const escaped = value.replace(/\$/g, '\\$');
47
+ if (PLAIN.test(escaped))
48
+ return escaped;
49
+ if (!escaped.includes('"') && !/\\[nr]/.test(escaped)) {
50
+ return `"${escaped.replace(/\n/g, '\\n').replace(/\r/g, '\\r')}"`;
51
+ }
52
+ if (!value.includes('\r')) {
53
+ if (!escaped.includes("'"))
54
+ return `'${escaped}'`;
55
+ if (!escaped.includes('`'))
56
+ return `\`${escaped}\``;
57
+ }
58
+ throw new EnvValueNotRepresentableError(key);
59
+ }
60
+ /**
61
+ * Serialize lines back to file text with a trailing newline. An entry
62
+ * whose `raw` still parses to its `key` / `value` is emitted verbatim
63
+ * (hand-written quoting, `export`, inline comments survive); any other
64
+ * entry is re-rendered from `key` / `value`. Non-entry lines are
65
+ * emitted verbatim — a malformed line is never dropped.
66
+ */
67
+ export function serializeEnvFile(lines) {
68
+ const body = lines.map(serializeLine).join('\n');
69
+ return body === '' ? '' : `${body}\n`;
70
+ }
71
+ function serializeLine(line) {
72
+ if (line.kind !== 'entry' || line.key === undefined || line.value === undefined)
73
+ return line.raw;
74
+ const reparsed = parseEntryRaw(line.raw);
75
+ if (reparsed !== undefined &&
76
+ reparsed.key === line.key &&
77
+ reparsed.value === line.value &&
78
+ (reparsed.exported === true) === (line.exported === true)) {
79
+ return line.raw;
80
+ }
81
+ return renderEntry(line.key, line.value, { exported: line.exported === true });
82
+ }
83
+ /**
84
+ * Set `KEY=value`. When the key already exists, the LAST occurrence —
85
+ * the one readers see — is replaced in place (keeping its `export`
86
+ * prefix); otherwise the entry is appended. `key` must be a POSIX env
87
+ * name (`ENV_KEY_REGEX`).
88
+ */
89
+ export function setKey(rawFileContents, key, value) {
90
+ if (!ENV_KEY_REGEX.test(key)) {
91
+ throw new TypeError(`Invalid env key "${key}" — must match ${ENV_KEY_REGEX.source}.`);
92
+ }
93
+ const lines = [...parseEnvFile(rawFileContents)];
94
+ let target = -1;
95
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
96
+ const line = lines[i];
97
+ if (line?.kind === 'entry' && line.key === key) {
98
+ target = i;
99
+ break;
100
+ }
101
+ }
102
+ const exported = target === -1 ? false : lines[target]?.exported === true;
103
+ const raw = renderEntry(key, value, { exported });
104
+ const replacement = exported
105
+ ? { kind: 'entry', raw, key, value, exported: true }
106
+ : { kind: 'entry', raw, key, value };
107
+ if (target === -1)
108
+ lines.push(replacement);
109
+ else
110
+ lines[target] = replacement;
111
+ return { contents: serializeEnvFile(lines), overwritten: target !== -1 };
112
+ }
113
+ /**
114
+ * Remove every entry for KEY; unrelated lines are preserved.
115
+ * Idempotent — `removed: false` when the key wasn't present.
116
+ */
117
+ export function unsetKey(rawFileContents, key) {
118
+ const lines = parseEnvFile(rawFileContents);
119
+ let removed = false;
120
+ const kept = [];
121
+ for (const line of lines) {
122
+ if (line.kind === 'entry' && line.key === key) {
123
+ removed = true;
124
+ continue;
125
+ }
126
+ kept.push(line);
127
+ }
128
+ return { contents: serializeEnvFile(kept), removed };
129
+ }
130
+ //# sourceMappingURL=serialize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialize.js","sourceRoot":"","sources":["../src/serialize.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,50 @@
1
1
  {
2
2
  "name": "@kindgi/dotenv-file",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Dotenv files the way applications read them (dotenv grammar, verified against the version Next.js bundles), expanded (${VAR}), layered (.env < .env.local) and edited losslessly. Used by @kindgi/secrets-dotenv. Zero runtime dependencies.",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/kindgi/kindgi-sdk.git",
9
+ "directory": "packages/dotenv-file"
10
+ },
11
+ "homepage": "https://github.com/kindgi/kindgi-sdk/tree/main/packages/dotenv-file#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/kindgi/kindgi-sdk/issues"
14
+ },
15
+ "type": "module",
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ }
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "src",
27
+ "README.md"
28
+ ],
29
+ "devDependencies": {
30
+ "@types/node": "^22.10.5",
31
+ "dotenv": "16.3.1",
32
+ "dotenv-expand-10": "npm:dotenv-expand@10.0.0",
33
+ "dotenv-expand-12": "npm:dotenv-expand@12.0.3",
34
+ "typescript": "^5.7.3",
35
+ "vitest": "^2.1.8"
36
+ },
37
+ "engines": {
38
+ "node": ">=22.0.0"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public",
42
+ "provenance": true
43
+ },
44
+ "scripts": {
45
+ "build": "tsc -p tsconfig.build.json",
46
+ "typecheck": "tsc --noEmit",
47
+ "test": "vitest run",
48
+ "clean": "rm -rf dist *.tsbuildinfo"
49
+ }
6
50
  }