fix(setup): preserve overridden runner variables and test isolation on Windows

Support case-insensitive environment value lookup in ClientRegistrationIdentity while preserving JavaScript property order precedence with findLast(). Add unit regression coverage for ambient uppercase npm variables on Windows. Isolate USERPROFILE in cliSetup tests so child processes do not touch the host profile directory. Harden JSON decision extraction in runJourney against ConPTY trailing ANSI escape codes. Deterministically sequence multiselect prompt navigation before confirmation. Filter ambient runner variables case-insensitively before launching interactive setup completion tests.
This commit is contained in:
Swatantra Yadav committed 2026-10-15 00:56:31 +05:30
1 parent 93e75bc8fe
commit 2abfe53dbd
3 files changed
+32 -7

No files matched your search

@@ -13,8 +13,9 @@ const environmentValue = (
platform: NodeJS.Platform,
): string | undefined => {
if (platform !== "win32") return environment[name];
const key = Object.keys(environment).find(
(candidate) => candidate.toLowerCase() === name,
const target = name.toLowerCase();
const key = Object.keys(environment).findLast(
(candidate) => candidate.toLowerCase() === target,
);
return key === undefined ? undefined : environment[key];
};
+9
View File
@@ -182,3 +182,12 @@ it.each([
});
},
);
it("prioritizes overridden package runner environment variables over ambient keys with differing case on Windows", () => {
const environment = {
NPM_COMMAND: "run",
npm_command: "exec",
NPM_CONFIG_USER_AGENT: "npm/10.9.0 node/v24.11.0 win32 x64",
};
expect(invokingPackageRunner(environment, "win32")).toBe("npx");
});
+20 -5
View File
@@ -179,7 +179,10 @@ describe("TTY setup dry run", () => {
describe("interactive setup journey", () => {
it("shows detected and absent clients without preselecting detected clients", async () => {
const result = await runJourney(
[step("Which agents should use REA?", "\u001b[B\r")],
[
step("Which agents should use REA?", "\u001b[B"),
step("Not detected · configuration is still available", "\r"),
],
false,
[actions[0]],
);
@@ -359,7 +362,14 @@ describe("interactive setup completion", () => {
{
cwd: process.cwd(),
env: {
...process.env,
...Object.fromEntries(
Object.entries(process.env).filter(
([key]) =>
!["npm_command", "npm_config_user_agent"].includes(
key.toLowerCase(),
),
),
),
npm_command: npmCommand,
npm_config_user_agent: userAgent,
NO_COLOR: "1",
@@ -427,6 +437,7 @@ const runJourney = async (
env: {
...process.env,
HOME: isolatedHome,
USERPROFILE: isolatedHome,
NO_COLOR: "1",
TERM: "xterm-256color",
},
@@ -464,9 +475,13 @@ const runJourney = async (
finish(new Error(`Setup journey returned no decision:\n${output}`));
return;
}
const serialized = output
.slice(markerIndex + decisionMarker.length)
.split(/\r?\n/u, 1)[0];
const raw = output.slice(markerIndex + decisionMarker.length);
const jsonStart = raw.indexOf("{");
const jsonEnd = raw.lastIndexOf("}");
const serialized =
jsonStart !== -1 && jsonEnd !== -1 && jsonEnd > jsonStart
? raw.slice(jsonStart, jsonEnd + 1)
: raw.split(/\r?\n/u, 1)[0];
if (serialized === undefined) {
finish(
new Error(`Setup journey returned an empty decision:\n${output}`),