* test: reproduce Windows SDK not found after fresh npx install (#3211) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(install): Windows persistent Path probe + npx-PATH filter on Windows (#3211) Add getUserShellWindowsPersistentPath() — the Windows counterpart to getUserShellPath(). Probes the user-level 'Path' registry key via powershell.exe so the installer can verify gsd-sdk is reachable from PowerShell/cmd.exe/Git Bash post-install, not just in the transient npx subprocess PATH. Wire it into installSdkIfNeeded: on Windows, use the registry-derived persistent Path (with npx dirs stripped) as the cross-shell reachability gate, instead of skipping the check entirely. This is the Windows sibling of the Linux fix in #3249/#3231. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * changeset: pr=3282 for #3211 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(install): include Machine+User Path in Windows persistent probe (#3211) getUserShellWindowsPersistentPath now merges Machine-level and User-level registry Path entries (matching the effective PATH that PowerShell, cmd.exe, and Git Bash inherit), instead of reading only User-level. Reading User-only would produce a false warning when gsd-sdk is installed in a machine-level bin dir (e.g. C:\Program Files\nodejs). Addresses CodeRabbit finding on PR #3282. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
102
bin/install.js
102
bin/install.js
@@ -9803,24 +9803,32 @@ function installSdkIfNeeded(opts) {
|
||||
// mismatch, POSIX ~/.local/bin missing from login shell, or node-
|
||||
// version-manager PATH shims. Probe the user's login shell PATH and
|
||||
// require the shim to be reachable there too before claiming ✓.
|
||||
// POSIX-only probe; on Windows getUserShellPath() returns null and
|
||||
// we trust the existing check (Windows-specific fix is separate).
|
||||
//
|
||||
// #3231: when getUserShellPath() returns null (e.g. $SHELL unset on
|
||||
// Linux, rc-file timeout), we cannot confirm persistent reachability.
|
||||
// In that case, do NOT preserve a true onPath — require the initial
|
||||
// check (on persistentPath) to have found the shim in a persistent
|
||||
// location. Since we already filtered npx dirs above, onPath=true here
|
||||
// means a non-transient dir has the shim, which is sufficient.
|
||||
const userShellPath = getUserShellPath();
|
||||
// #3211 (Windows): getUserShellWindowsPersistentPath() reads the user-level
|
||||
// 'Path' registry key via PowerShell — the correct cross-shell source on
|
||||
// Windows (Git Bash, PowerShell, and cmd.exe all inherit it). Returns null
|
||||
// when PowerShell is unavailable or the probe times out.
|
||||
//
|
||||
// #3231: when getUserShellPath() / getUserShellWindowsPersistentPath()
|
||||
// returns null (probe failed or unavailable), we cannot confirm persistent
|
||||
// reachability. Since we already filtered npx dirs from persistentPath above,
|
||||
// onPath=true means a non-transient dir has the shim — that is the best
|
||||
// available invariant and is sufficient to claim ✓.
|
||||
const userShellPath = process.platform === 'win32'
|
||||
? getUserShellWindowsPersistentPath()
|
||||
: getUserShellPath();
|
||||
if (onPath && userShellPath !== null) {
|
||||
const persistentUserShellPath = filterNpxFromPath(userShellPath);
|
||||
// filterNpxFromPath is applied inside getUserShellWindowsPersistentPath
|
||||
// (Windows) and here for the POSIX case.
|
||||
const persistentUserShellPath = process.platform === 'win32'
|
||||
? userShellPath // already filtered by getUserShellWindowsPersistentPath
|
||||
: filterNpxFromPath(userShellPath);
|
||||
const userSees = isGsdSdkOnPath(persistentUserShellPath);
|
||||
if (!userSees) {
|
||||
onPath = false;
|
||||
}
|
||||
}
|
||||
// If userShellPath is null (POSIX probe failed), onPath reflects
|
||||
// If userShellPath is null (probe failed or unavailable), onPath reflects
|
||||
// the persistent-PATH check — that is the best available invariant.
|
||||
|
||||
if (onPath) {
|
||||
@@ -9995,9 +10003,8 @@ function isGsdSdkOnPath(pathString) {
|
||||
* login shell.
|
||||
*
|
||||
* Uses `$SHELL -lc 'printf %s "$PATH"'` on POSIX. Returns null on Windows
|
||||
* (cross-shell PATH probing requires a different strategy — Git Bash
|
||||
* vs PowerShell vs cmd.exe each read PATH from different sources, and
|
||||
* a future revision can build a Windows-aware probe). Returns null
|
||||
* (the Windows counterpart is getUserShellWindowsPersistentPath, which reads
|
||||
* the user-level 'Path' registry key via PowerShell). Returns null
|
||||
* when $SHELL is unset, when the spawn fails, or when the result is
|
||||
* empty — callers must fall back to process.env.PATH in those cases.
|
||||
*
|
||||
@@ -10030,6 +10037,72 @@ function getUserShellPath() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* #3211: Windows counterpart to getUserShellPath(). Probes the effective
|
||||
* persistent Path from the Windows registry via PowerShell by merging
|
||||
* Machine-level + User-level entries:
|
||||
*
|
||||
* $m=[Environment]::GetEnvironmentVariable('Path','Machine')
|
||||
* $u=[Environment]::GetEnvironmentVariable('Path','User')
|
||||
* ($m + ';' + $u).Trim(';')
|
||||
*
|
||||
* This is the correct primitive for Windows cross-shell PATH verification —
|
||||
* Git Bash, PowerShell, and cmd.exe all inherit the effective (Machine;User)
|
||||
* registry Path, while the install-subprocess process.env.PATH is polluted
|
||||
* with transient npx entries and may not include directories added by the
|
||||
* user post-install. Reading only User-level Path would produce a false
|
||||
* warning when gsd-sdk is in a machine-level bin dir (e.g. C:\Program Files\nodejs).
|
||||
*
|
||||
* Returns the filtered persistent Path string (npx segments stripped) or null
|
||||
* on any failure (non-Windows, PowerShell not available, spawn timeout, empty
|
||||
* result). Callers must treat null as "check unavailable — trust install-time
|
||||
* filtered PATH".
|
||||
*
|
||||
* Synchronous, 2-second timeout, best-effort — safe to call from
|
||||
* installSdkIfNeeded without restructuring to async.
|
||||
*/
|
||||
function getUserShellWindowsPersistentPath() {
|
||||
if (process.platform !== 'win32') return null;
|
||||
const cp = require('child_process');
|
||||
// Use the same execFileSync form as getUserShellPath() above — static
|
||||
// literal args, no user input, no injection vector.
|
||||
const execFile = cp.execFileSync.bind(cp);
|
||||
try {
|
||||
// Read Machine + User Path and merge them — the effective PATH that
|
||||
// PowerShell, cmd.exe, and Git Bash inherit is Machine;User (machine
|
||||
// entries first). Reading only User-level Path would produce a false
|
||||
// warning when gsd-sdk is installed in a machine-level bin dir
|
||||
// (e.g. C:\Program Files\nodejs).
|
||||
const out = execFile(
|
||||
'powershell.exe',
|
||||
[
|
||||
'-NoProfile',
|
||||
'-Command',
|
||||
"$u=[Environment]::GetEnvironmentVariable('Path','User');" +
|
||||
"$m=[Environment]::GetEnvironmentVariable('Path','Machine');" +
|
||||
"[Console]::Out.Write(($m + ';' + $u).Trim(';'))",
|
||||
],
|
||||
{
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
// 2-second cap — a locked registry or slow profile can't hang the install.
|
||||
timeout: 2000,
|
||||
},
|
||||
);
|
||||
// Take the last non-empty line so any motd/banner noise before the output
|
||||
// doesn't corrupt the result — same defensive pattern as getUserShellPath.
|
||||
const lines = String(out || '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
||||
const candidate = lines.length > 0 ? lines[lines.length - 1] : '';
|
||||
if (!candidate) return null;
|
||||
// Strip transient npx dirs from the persistent Path before returning —
|
||||
// the registry can accumulate stale _npx entries from prior runs.
|
||||
const filtered = filterNpxFromPath(candidate);
|
||||
return filtered.length > 0 ? filtered : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* #2775 helper: attempt to materialize the `gsd-sdk` shim at a user-writable
|
||||
* PATH location. Returns the absolute path created on success, or null if no
|
||||
@@ -10468,6 +10541,7 @@ if (process.env.GSD_TEST_MODE) {
|
||||
isLegacyGsdSdkShim,
|
||||
isGsdSdkOnPath,
|
||||
getUserShellPath,
|
||||
getUserShellWindowsPersistentPath,
|
||||
homePathCoveredByRc,
|
||||
maybeSuggestPathExport,
|
||||
runtimeMap,
|
||||
|
||||
Reference in New Issue
Block a user