From 01d7fbaeec0e467ebc89caed94a3dcfac8f64a89 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Fri, 21 Oct 2022 13:48:41 -0400 Subject: [PATCH] Fix syncing shell env on TCSH and CSH Signed-off-by: Sebastian Malton --- ...mpute-unix-shell-environment.injectable.ts | 148 +++++++++--------- 1 file changed, 78 insertions(+), 70 deletions(-) diff --git a/src/main/utils/shell-env/compute-unix-shell-environment.injectable.ts b/src/main/utils/shell-env/compute-unix-shell-environment.injectable.ts index 7186e96a88..aaf546fd87 100644 --- a/src/main/utils/shell-env/compute-unix-shell-environment.injectable.ts +++ b/src/main/utils/shell-env/compute-unix-shell-environment.injectable.ts @@ -8,7 +8,7 @@ import { basename } from "path"; import type { EnvironmentVariables } from "./compute-shell-environment.injectable"; import { getInjectable } from "@ogre-tools/injectable"; -interface UnixShellEnvOptions { +export interface UnixShellEnvOptions { signal?: AbortSignal; } @@ -16,76 +16,84 @@ export type ComputeUnixShellEnvironment = (shell: string, opts?: UnixShellEnvOpt const computeUnixShellEnvironmentInjectable = getInjectable({ id: "compute-unix-shell-environment", - instantiate: (): ComputeUnixShellEnvironment => async (shell, opts) => { - const runAsNode = process.env["ELECTRON_RUN_AS_NODE"]; - const noAttach = process.env["ELECTRON_NO_ATTACH_CONSOLE"]; - const env = { - ...process.env, - ELECTRON_RUN_AS_NODE: "1", - ELECTRON_NO_ATTACH_CONSOLE: "1", + instantiate: (): ComputeUnixShellEnvironment => { + const powerShellName = /^pwsh(-preview)?$/; + const nonBashLikeShellName = /^t?csh$/; + + return async (shell, opts = {}) => { + const runAsNode = process.env["ELECTRON_RUN_AS_NODE"]; + const noAttach = process.env["ELECTRON_NO_ATTACH_CONSOLE"]; + const env = { + ...process.env, + ELECTRON_RUN_AS_NODE: "1", + ELECTRON_NO_ATTACH_CONSOLE: "1", + }; + const mark = randomUUID().replace(/-/g, ""); + const regex = new RegExp(`${mark}(\\{.*\\})${mark}`); + const shellName = basename(shell); + const { command, shellArgs } = (() => { + if (powerShellName.test(shellName)) { + // Older versions of PowerShell removes double quotes sometimes so we use "double single quotes" which is how + // you escape single quotes inside of a single quoted string. + return { + command: `Command '${process.execPath}' -p '\\"${mark}\\" + JSON.stringify(process.env) + \\"${mark}\\"'`, + shellArgs: ["-Login"], + }; + } + + return { + command: `'${process.execPath}' -p '"${mark}" + JSON.stringify(process.env) + "${mark}"'`, + shellArgs: nonBashLikeShellName.test(shellName) + // tcsh and csh don't support any other options when providing the -l (login) shell option + ? ["-l"] + // zsh (at least, maybe others) don't load RC files when in non-interactive mode, even when using -l (login) option + : ["-li"], + }; + })(); + + return new Promise((resolve, reject) => { + const shellProcess = spawn(shell, shellArgs, { + detached: true, + env, + }); + const stdout: Buffer[] = []; + + opts.signal?.addEventListener("abort", () => shellProcess.kill()); + + shellProcess.stdout.on("data", b => stdout.push(b)); + + shellProcess.on("error", (err) => reject(err)); + shellProcess.on("close", (code, signal) => { + if (code || signal) { + return reject(new Error(`Unexpected return code from spawned shell (code: ${code}, signal: ${signal})`)); + } + + try { + const rawOutput = Buffer.concat(stdout).toString("utf-8"); + const match = regex.exec(rawOutput); + const strippedRawOutput = match ? match[1] : "{}"; + const resolvedEnv = JSON.parse(strippedRawOutput); + + if (runAsNode) { + resolvedEnv["ELECTRON_RUN_AS_NODE"] = runAsNode; + } else { + delete resolvedEnv["ELECTRON_RUN_AS_NODE"]; + } + + if (noAttach) { + resolvedEnv["ELECTRON_NO_ATTACH_CONSOLE"] = noAttach; + } else { + delete resolvedEnv["ELECTRON_NO_ATTACH_CONSOLE"]; + } + + resolve(resolvedEnv); + } catch (err) { + reject(err); + } + }); + shellProcess.stdin.end(command); + }); }; - const mark = randomUUID().replace(/-/g, ""); - const regex = new RegExp(`${mark}(.*)${mark}`); - const shellName = basename(shell); - let command: string; - let shellArgs: string[]; - - if (/^pwsh(-preview)?$/.test(shellName)) { - // Older versions of PowerShell removes double quotes sometimes so we use "double single quotes" which is how - // you escape single quotes inside of a single quoted string. - command = `& '${process.execPath}' -p '''${mark}'' + JSON.stringify(process.env) + ''${mark}'''`; - shellArgs = ["-Login", "-Command"]; - } else { - command = `'${process.execPath}' -p '"${mark}" + JSON.stringify(process.env) + "${mark}"'`; - - if (shellName === "tcsh") { - shellArgs = ["-ic"]; - } else { - shellArgs = ["-ilc"]; - } - } - - return new Promise((resolve, reject) => { - const shellProcess = spawn(shell, [...shellArgs, command], { - detached: true, - stdio: ["ignore", "pipe", "pipe"], - env, - }); - const stdout: Buffer[] = []; - - opts?.signal?.addEventListener("abort", () => shellProcess.kill()); - - shellProcess.on("error", (err) => reject(err)); - shellProcess.stdout.on("data", b => stdout.push(b)); - shellProcess.on("close", (code, signal) => { - if (code || signal) { - return reject(new Error(`Unexpected return code from spawned shell (code: ${code}, signal: ${signal})`)); - } - - try { - const rawOutput = Buffer.concat(stdout).toString("utf-8"); - const match = regex.exec(rawOutput); - const strippedRawOutput = match ? match[1] : "{}"; - const resolvedEnv = JSON.parse(strippedRawOutput); - - if (runAsNode) { - resolvedEnv["ELECTRON_RUN_AS_NODE"] = runAsNode; - } else { - delete resolvedEnv["ELECTRON_RUN_AS_NODE"]; - } - - if (noAttach) { - resolvedEnv["ELECTRON_NO_ATTACH_CONSOLE"] = noAttach; - } else { - delete resolvedEnv["ELECTRON_NO_ATTACH_CONSOLE"]; - } - - resolve(resolvedEnv); - } catch(err) { - reject(err); - } - }); - }); }, causesSideEffects: true, });