Skip to content

Commit 8674323

Browse files
committed
fix(env): avoid kenv escaping
1 parent c98a5a1 commit 8674323

File tree

3 files changed

+62
-15
lines changed

3 files changed

+62
-15
lines changed

src/cli/set-env-var.test.ts

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -217,11 +217,13 @@ await tmp.withDir(async (dir) => {
217217
t.is(parsed.BRAND_NEW_KEY, "brand_new_value")
218218
})
219219

220-
ava.serial("should handle Windows-style paths", async (t: Context) => {
220+
ava.serial("should handle Windows-style paths (normalized to forward slashes)", async (t: Context) => {
221221
const windowsPath = "C:\\Users\\JohnDoe\\Documents\\Project"
222+
const expectedNormalized = "C:/Users/JohnDoe/Documents/Project"
222223
await global.setEnvVar("WIN_PATH", windowsPath)
223224
const { parsed } = dotenv.config({ files: [t.context.envFile] })
224-
t.is(parsed.WIN_PATH, windowsPath)
225+
// Paths are normalized to forward slashes to prevent .env parsing issues
226+
t.is(parsed.WIN_PATH, expectedNormalized)
225227
})
226228

227229
ava.serial("should handle POSIX-style paths", async (t: Context) => {
@@ -233,37 +235,72 @@ await tmp.withDir(async (dir) => {
233235

234236
ava.serial("should handle paths with spaces", async (t: Context) => {
235237
const windowsPath = "C:\\Program Files\\My App\\Config"
238+
const expectedWinNormalized = "C:/Program Files/My App/Config"
236239
const posixPath = "/home/user/My Documents/project"
237240

238241
await global.setEnvVar("WIN_SPACE_PATH", windowsPath)
239242
await global.setEnvVar("POSIX_SPACE_PATH", posixPath)
240243

241244
const { parsed } = dotenv.config({ files: [t.context.envFile] })
242-
t.is(parsed.WIN_SPACE_PATH, windowsPath)
245+
// Windows paths normalized to forward slashes, POSIX paths unchanged
246+
t.is(parsed.WIN_SPACE_PATH, expectedWinNormalized)
243247
t.is(parsed.POSIX_SPACE_PATH, posixPath)
244248
})
245249

246250
ava.serial("should handle network paths and UNC paths", async (t: Context) => {
247251
const uncPath = "\\\\server\\share\\folder"
252+
const expectedUncNormalized = "//server/share/folder"
248253
const networkPath = "//server/share/folder"
249254

250255
await global.setEnvVar("UNC_PATH", uncPath)
251256
await global.setEnvVar("NETWORK_PATH", networkPath)
252257

253258
const { parsed } = dotenv.config({ files: [t.context.envFile] })
254-
t.is(parsed.UNC_PATH, uncPath)
259+
// UNC paths normalized to forward slashes, network paths unchanged
260+
t.is(parsed.UNC_PATH, expectedUncNormalized)
255261
t.is(parsed.NETWORK_PATH, networkPath)
256262
})
257263

258264
ava.serial("should handle relative paths", async (t: Context) => {
259265
const winRelative = "..\\parent\\child"
266+
const expectedWinNormalized = "../parent/child"
260267
const posixRelative = "../parent/child"
261268

262269
await global.setEnvVar("WIN_RELATIVE", winRelative)
263270
await global.setEnvVar("POSIX_RELATIVE", posixRelative)
264271

265272
const { parsed } = dotenv.config({ files: [t.context.envFile] })
266-
t.is(parsed.WIN_RELATIVE, winRelative)
273+
// Windows relative paths normalized to forward slashes, POSIX paths unchanged
274+
t.is(parsed.WIN_RELATIVE, expectedWinNormalized)
267275
t.is(parsed.POSIX_RELATIVE, posixRelative)
268276
})
277+
278+
ava.serial("should fix KENV and KIT_NODE_PATH issue (GitHub issue)", async (t: Context) => {
279+
// This tests the exact scenario reported in the issue:
280+
// Windows paths with backslashes were being wrapped in quotes,
281+
// causing .env parsing to fail
282+
const kenvPath = "C:\\Users\\myusername\\.kenv"
283+
const kitNodePath = "C:\\Users\\myusername\\.kit\\nodejs\\22.9.0\\node.exe"
284+
285+
const expectedKenv = "C:/Users/myusername/.kenv"
286+
const expectedKitNode = "C:/Users/myusername/.kit/nodejs/22.9.0/node.exe"
287+
288+
// Use different var names to avoid confusing the path resolution
289+
await global.setEnvVar("TEST_KENV_PATH", kenvPath)
290+
await global.setEnvVar("TEST_KIT_NODE_PATH", kitNodePath)
291+
292+
// Read raw file to verify no backslashes in quotes
293+
const contents = await readFile(t.context.envFile, "utf-8")
294+
t.log("File contents:", contents)
295+
296+
// Verify the file doesn't contain quoted backslash paths
297+
t.false(contents.includes('="C:\\'), "Should not have quoted backslash paths")
298+
// Verify it has normalized forward slash paths
299+
t.true(contents.includes('C:/Users/myusername'), "Should have normalized forward slash paths")
300+
301+
// Verify dotenv can parse correctly
302+
const { parsed } = dotenv.config({ files: [t.context.envFile] })
303+
t.is(parsed.TEST_KENV_PATH, expectedKenv, "Path should be normalized to forward slashes")
304+
t.is(parsed.TEST_KIT_NODE_PATH, expectedKitNode, "Path should be normalized to forward slashes")
305+
})
269306
})

src/cli/set-env-var.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,14 @@ function setEnvValue(key: string, value?: string) {
8989

9090
const NEEDS_QUOTES_REGEX = /[\s"'`$&|<>^;,\(\)\\]/
9191
function formatEnvLine(key: string, value: string) {
92+
// Normalize Windows paths to use forward slashes
93+
// Forward slashes work correctly on Windows and don't need quoting in .env files
94+
const normalizedValue = value.replace(/\\/g, '/')
95+
9296
// Check if value contains spaces, special characters, or hash symbols
93-
const needsQuotes = NEEDS_QUOTES_REGEX.test(value) || value.includes('#');
94-
return needsQuotes ? `${key}="${value}"` : `${key}=${value}`;
97+
// Note: After normalization, backslashes are gone, so this won't trigger on paths
98+
const needsQuotes = NEEDS_QUOTES_REGEX.test(normalizedValue) || normalizedValue.includes('#');
99+
return needsQuotes ? `${key}="${normalizedValue}"` : `${key}=${normalizedValue}`;
95100
}
96101

97102
// Update an existing environment variable

src/core/env-backup.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -76,28 +76,33 @@ export const formatEnvContent = (envMap: Map<string, string>): string => {
7676
const lines: string[] = []
7777

7878
for (const [key, value] of envMap) {
79+
// Normalize Windows paths to use forward slashes
80+
// Forward slashes work correctly on Windows and don't need quoting in .env files
81+
let normalizedValue = value.replace(/\\/g, '/')
82+
7983
const isAlreadyQuoted =
80-
(value.startsWith('"') && value.endsWith('"')) ||
81-
(value.startsWith("'") && value.endsWith("'"));
84+
(normalizedValue.startsWith('"') && normalizedValue.endsWith('"')) ||
85+
(normalizedValue.startsWith("'") && normalizedValue.endsWith("'"));
8286

83-
let formattedValue = value;
87+
let formattedValue = normalizedValue;
8488

8589
if (isAlreadyQuoted) {
8690
// If already quoted, leave as is.
8791
// Or, if it's double-quoted and contains internal double quotes that are not escaped, it's tricky.
8892
// For now, we assume valid quoting if already quoted.
89-
formattedValue = value;
93+
formattedValue = normalizedValue;
9094
} else {
9195
// Needs quoting if it's empty, contains problematic characters, or includes a #
92-
const containsProblematicChars = /[\s"'`$&|<>^;,\(\)\\]/.test(value);
93-
const needsExternalQuotes = value === "" || containsProblematicChars || value.includes('#');
96+
// Note: After normalization, backslashes are gone, so this won't trigger on paths
97+
const containsProblematicChars = /[\s"'`$&|<>^;,\(\)\\]/.test(normalizedValue);
98+
const needsExternalQuotes = normalizedValue === "" || containsProblematicChars || normalizedValue.includes('#');
9499

95100
if (needsExternalQuotes) {
96101
// Escape only internal double quotes, then wrap with double quotes
97-
formattedValue = `"${value.replace(/"/g, '\"')}"`;
102+
formattedValue = `"${normalizedValue.replace(/"/g, '\"')}"`;
98103
} else {
99104
// No quoting needed, use raw value
100-
formattedValue = value;
105+
formattedValue = normalizedValue;
101106
}
102107
}
103108
lines.push(`${key}=${formattedValue}`);

0 commit comments

Comments
 (0)