Files
oh-my-pi/packages/utils/test/env.test.ts
T
can1357 4b6be0b0df fix(utils): preserve Windows env names with parentheses
The Bun.env scrub and filterProcessEnv used isValidEnvName (strict shell
identifier shape), which deleted standard Windows variables like
ProgramFiles(x86) and CommonProgramFiles(x86). procmgr.ts imports this
module before resolving the shell and reads Bun.env['ProgramFiles(x86)']
to find Git Bash under 32-bit Program Files, so installations that only
had Git there were no longer discovered and failed with 'No bash shell
found'.

The unsafe cases for native execve are '=' or NUL in names and NUL in
values, not parentheses. Introduce isSafeEnvName covering exactly those
cases and use it for the in-place Bun.env scrub and the spawn-env
filter. Keep isValidEnvName (strict) for dotenv parsing, where strict
shell-identifier shape is the right contract.
2026-05-17 13:43:00 +02:00

85 lines
2.1 KiB
TypeScript

import { afterEach, describe, expect, it } from "bun:test";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { filterProcessEnv, parseEnvFile } from "../src/env";
const tempDirs: string[] = [];
afterEach(() => {
for (const dir of tempDirs.splice(0)) {
fs.rmSync(dir, { force: true, recursive: true });
}
});
function writeTempEnv(content: string): string {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-utils-env-"));
tempDirs.push(dir);
const filePath = path.join(dir, ".env");
fs.writeFileSync(filePath, content);
return filePath;
}
describe("parseEnvFile", () => {
it("ignores malformed names and nul-containing values", () => {
const filePath = writeTempEnv(
[
"GOOD=value",
"_ALSO_GOOD='quoted value'",
"1BAD=value",
"BAD-NAME=value",
"BAD NAME=value",
"BAD_VALUE=before\0after",
"# comment",
"NO_EQUALS",
].join("\n"),
);
expect(parseEnvFile(filePath)).toEqual({
GOOD: "value",
_ALSO_GOOD: "quoted value",
});
});
it("mirrors valid OMP_ variables to PI_ variables", () => {
const filePath = writeTempEnv("OMP_FEATURE=enabled\nOMP_BAD=before\0after\n");
expect(parseEnvFile(filePath)).toEqual({
OMP_FEATURE: "enabled",
PI_FEATURE: "enabled",
});
});
});
describe("filterProcessEnv", () => {
it("drops entries that cannot be passed to process spawn env", () => {
expect(
filterProcessEnv({
GOOD: "value",
EMPTY: "",
"BAD=NAME": "value",
BAD_VALUE: "before\0after",
MISSING: undefined,
}),
).toEqual({
GOOD: "value",
EMPTY: "",
});
});
it("preserves Windows-style variable names containing parentheses", () => {
// `ProgramFiles(x86)` and friends are standard on Windows and must
// survive the scrub so Git Bash discovery in procmgr.ts can resolve
// 32-bit Program Files installations.
expect(
filterProcessEnv({
"ProgramFiles(x86)": "C:\\Program Files (x86)",
"CommonProgramFiles(x86)": "C:\\Program Files (x86)\\Common Files",
}),
).toEqual({
"ProgramFiles(x86)": "C:\\Program Files (x86)",
"CommonProgramFiles(x86)": "C:\\Program Files (x86)\\Common Files",
});
});
});