Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
102 lines
3.4 KiB
JavaScript
102 lines
3.4 KiB
JavaScript
const test = require("node:test");
|
|
const assert = require("node:assert/strict");
|
|
const fs = require("node:fs");
|
|
const os = require("node:os");
|
|
const path = require("node:path");
|
|
const childProcess = require("node:child_process");
|
|
|
|
// shellDiscovery destructures execFileSync at require time, so the fake
|
|
// `where.exe` must be installed before the module under test is loaded.
|
|
const realExecFileSync = childProcess.execFileSync;
|
|
const whereResults = new Map();
|
|
|
|
childProcess.execFileSync = function fakeExecFileSync(file, args) {
|
|
const argv0 = String(file).toLowerCase();
|
|
const isWhere =
|
|
argv0 === "where.exe" || argv0.endsWith("\\where.exe") || argv0.endsWith("/where.exe");
|
|
if (isWhere) {
|
|
const lines = whereResults.get(args[0]);
|
|
if (lines !== undefined) {
|
|
if (lines === null) {
|
|
throw new Error("not found");
|
|
}
|
|
return lines.join("\r\n");
|
|
}
|
|
}
|
|
// reg.exe / wsl.exe probes from other detectors: treated as missing.
|
|
throw new Error(`unexpected execFileSync call: ${file} ${JSON.stringify(args)}`);
|
|
};
|
|
|
|
const shellDiscovery = require("./shellDiscovery.cjs");
|
|
|
|
function makeHarness(t) {
|
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "netcatty-shell-discovery-"));
|
|
t.after(() => fs.rmSync(tmp, { recursive: true, force: true }));
|
|
|
|
const previousLocalAppData = process.env.LOCALAPPDATA;
|
|
process.env.LOCALAPPDATA = tmp;
|
|
t.after(() => {
|
|
if (previousLocalAppData === undefined) {
|
|
delete process.env.LOCALAPPDATA;
|
|
} else {
|
|
process.env.LOCALAPPDATA = previousLocalAppData;
|
|
}
|
|
});
|
|
|
|
const aliasPath = path.join(
|
|
tmp,
|
|
"Microsoft",
|
|
"WindowsApps",
|
|
"pwsh.exe",
|
|
);
|
|
// The alias fixture intentionally does NOT exist on disk: real App
|
|
// Execution Aliases fail fs.statSync() with EACCES, so existsSync() is
|
|
// false on a real machine too.
|
|
|
|
return { tmp, aliasPath };
|
|
}
|
|
|
|
test("findExecutableOnPath accepts a WindowsApps execution alias as the only candidate", (t) => {
|
|
const { aliasPath } = makeHarness(t);
|
|
whereResults.set("pwsh", [aliasPath]);
|
|
|
|
assert.equal(shellDiscovery.findExecutableOnPath("pwsh"), aliasPath);
|
|
whereResults.delete("pwsh");
|
|
});
|
|
|
|
test("findExecutableOnPath prefers a regular executable over an execution alias", (t) => {
|
|
const { tmp, aliasPath } = makeHarness(t);
|
|
const realPwsh = path.join(tmp, "Program Files", "PowerShell", "7", "pwsh.exe");
|
|
fs.mkdirSync(path.dirname(realPwsh), { recursive: true });
|
|
fs.writeFileSync(realPwsh, "");
|
|
whereResults.set("pwsh", [aliasPath, realPwsh]);
|
|
|
|
assert.equal(shellDiscovery.findExecutableOnPath("pwsh"), realPwsh);
|
|
whereResults.delete("pwsh");
|
|
});
|
|
|
|
test("findExecutableOnPath returns null when where.exe finds nothing", (t) => {
|
|
makeHarness(t);
|
|
whereResults.set("pwsh", null);
|
|
|
|
assert.equal(shellDiscovery.findExecutableOnPath("pwsh"), null);
|
|
whereResults.delete("pwsh");
|
|
});
|
|
|
|
test("discoverWindowsShells lists PowerShell 7 from an MSIX alias and marks it default", (t) => {
|
|
const { aliasPath } = makeHarness(t);
|
|
whereResults.set("pwsh", [aliasPath]);
|
|
whereResults.set("powershell", null);
|
|
t.after(() => {
|
|
whereResults.delete("pwsh");
|
|
whereResults.delete("powershell");
|
|
});
|
|
|
|
const shells = shellDiscovery.discoverWindowsShells();
|
|
const pwsh = shells.find((s) => s.id === "pwsh");
|
|
|
|
assert.ok(pwsh, "expected a discovered pwsh shell entry");
|
|
assert.equal(pwsh.command, aliasPath);
|
|
assert.equal(pwsh.isDefault, true);
|
|
});
|