1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Fix windows shell not having all environment variables (#6402)

* Fix windows shell not having all environment variables

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix startup due to buildVersion dependency

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Call cleanup in computeShellEnvironment

Signed-off-by: Sebastian Malton <sebastian@malton.name>

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2022-10-13 07:49:58 -04:00 committed by GitHub
parent 4e02ccb1c7
commit d96918c966
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 367 additions and 144 deletions

View File

@ -0,0 +1,13 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import userStoreInjectable from "./user-store.injectable";
const resolvedShellInjectable = getInjectable({
id: "resolved-shell",
instantiate: (di) => di.inject(userStoreInjectable).resolvedShell,
});
export default resolvedShellInjectable;

View File

@ -7,14 +7,14 @@ import { clearKubeconfigEnvVars } from "../utils/clear-kube-env-vars";
describe("clearKubeconfigEnvVars tests", () => {
it("should not touch non kubeconfig keys", () => {
expect(clearKubeconfigEnvVars({ a: 1 })).toStrictEqual({ a: 1 });
expect(clearKubeconfigEnvVars({ a: "22" })).toStrictEqual({ a: "22" });
});
it("should remove a single kubeconfig key", () => {
expect(clearKubeconfigEnvVars({ a: 1, kubeconfig: "1" })).toStrictEqual({ a: 1 });
expect(clearKubeconfigEnvVars({ a: "22", kubeconfig: "1" })).toStrictEqual({ a: "22" });
});
it("should remove a two kubeconfig key", () => {
expect(clearKubeconfigEnvVars({ a: 1, kubeconfig: "1", kUbeconfig: "1" })).toStrictEqual({ a: 1 });
expect(clearKubeconfigEnvVars({ a: "22", kubeconfig: "1", kUbeconfig: "1" })).toStrictEqual({ a: "22" });
});
});

View File

@ -36,11 +36,8 @@ export class LocalShellSession extends ShellSession {
}
public async open() {
let env = await this.getCachedShellEnv();
// extensions can modify the env
env = this.dependencies.modifyTerminalShellEnv(this.cluster.id, env);
const env = this.dependencies.modifyTerminalShellEnv(this.cluster.id, await this.getCachedShellEnv());
const shell = env.PTYSHELL;
if (!shell) {

View File

@ -17,6 +17,11 @@ import type WebSocket from "ws";
import getDirnameOfPathInjectable from "../../../common/path/get-dirname.injectable";
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
import getBasenameOfPathInjectable from "../../../common/path/get-basename.injectable";
import computeShellEnvironmentInjectable from "../../utils/shell-env/compute-shell-environment.injectable";
import spawnPtyInjectable from "../spawn-pty.injectable";
import resolvedShellInjectable from "../../../common/user-store/resolved-shell.injectable";
import appNameInjectable from "../../../common/vars/app-name.injectable";
import buildVersionInjectable from "../../vars/build-version/build-version.injectable";
export interface OpenLocalShellSessionArgs {
websocket: WebSocket;
@ -34,13 +39,18 @@ const openLocalShellSessionInjectable = getInjectable({
const dependencies: LocalShellSessionDependencies = {
directoryForBinaries: di.inject(directoryForBinariesInjectable),
isMac: di.inject(isMacInjectable),
modifyTerminalShellEnv: di.inject(modifyTerminalShellEnvInjectable),
isWindows: di.inject(isWindowsInjectable),
logger: di.inject(loggerInjectable),
userStore: di.inject(userStoreInjectable),
resolvedShell: di.inject(resolvedShellInjectable),
appName: di.inject(appNameInjectable),
buildVersion: di.inject(buildVersionInjectable),
modifyTerminalShellEnv: di.inject(modifyTerminalShellEnvInjectable),
getDirnameOfPath: di.inject(getDirnameOfPathInjectable),
joinPaths: di.inject(joinPathsInjectable),
getBasenameOfPath: di.inject(getBasenameOfPathInjectable),
computeShellEnvironment: di.inject(computeShellEnvironmentInjectable),
spawnPty: di.inject(spawnPtyInjectable),
};
return (args) => {

View File

@ -0,0 +1,90 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { DiContainer } from "@ogre-tools/injectable";
import { WebSocket } from "ws";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import type { Cluster } from "../../../common/cluster/cluster";
import platformInjectable from "../../../common/vars/platform.injectable";
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import createKubectlInjectable from "../../kubectl/create-kubectl.injectable";
import type { Kubectl } from "../../kubectl/kubectl";
import buildVersionInjectable from "../../vars/build-version/build-version.injectable";
import type { OpenShellSession } from "../create-shell-session.injectable";
import type { SpawnPty } from "../spawn-pty.injectable";
import spawnPtyInjectable from "../spawn-pty.injectable";
import openLocalShellSessionInjectable from "./open.injectable";
describe("technical unit tests for local shell sessions", () => {
let di: DiContainer;
beforeEach(() => {
di = getDiForUnitTesting({
doGeneralOverrides: true,
});
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
di.override(buildVersionInjectable, () => ({
get: () => "1.1.1",
}));
});
describe("when on windows", () => {
let openLocalShellSession: OpenShellSession;
let spawnPtyMock: jest.MockedFunction<SpawnPty>;
beforeEach(() => {
di.override(platformInjectable, () => "win32");
spawnPtyMock = jest.fn();
di.override(spawnPtyInjectable, () => spawnPtyMock);
di.override(createKubectlInjectable, () => () => ({
binDir: async () => "/some-kubectl-binary-dir",
getBundledPath: () => "/some-bundled-kubectl-path",
}) as Partial<Kubectl> as Kubectl);
openLocalShellSession = di.inject(openLocalShellSessionInjectable);
});
describe("when opening a local shell session", () => {
it("should pass through all environment variables to shell", async () => {
process.env.MY_TEST_ENV_VAR = "true";
spawnPtyMock.mockImplementationOnce((file, args, options) => {
expect(options.env).toMatchObject({
MY_TEST_ENV_VAR: "true",
});
return {
cols: 80,
rows: 40,
pid: 12343,
handleFlowControl: false,
kill: jest.fn(),
onData: jest.fn(),
onExit: jest.fn(),
pause: jest.fn(),
process: "my-pty",
resize: jest.fn(),
resume: jest.fn(),
write: jest.fn(),
on: jest.fn(),
};
});
await openLocalShellSession({
cluster: {
getProxyKubeconfigPath: async () => "/some-proxy-kubeconfig",
preferences: {},
} as Partial<Cluster> as Cluster,
tabId: "my-tab-id",
websocket: new WebSocket(null),
});
});
});
});
});

View File

@ -12,6 +12,11 @@ import isMacInjectable from "../../../common/vars/is-mac.injectable";
import isWindowsInjectable from "../../../common/vars/is-windows.injectable";
import loggerInjectable from "../../../common/logger.injectable";
import createKubeJsonApiForClusterInjectable from "../../../common/k8s-api/create-kube-json-api-for-cluster.injectable";
import computeShellEnvironmentInjectable from "../../utils/shell-env/compute-shell-environment.injectable";
import spawnPtyInjectable from "../spawn-pty.injectable";
import resolvedShellInjectable from "../../../common/user-store/resolved-shell.injectable";
import appNameInjectable from "../../../common/vars/app-name.injectable";
import buildVersionInjectable from "../../vars/build-version/build-version.injectable";
export interface NodeShellSessionArgs {
websocket: WebSocket;
@ -28,7 +33,12 @@ const openNodeShellSessionInjectable = getInjectable({
isMac: di.inject(isMacInjectable),
isWindows: di.inject(isWindowsInjectable),
logger: di.inject(loggerInjectable),
resolvedShell: di.inject(resolvedShellInjectable),
appName: di.inject(appNameInjectable),
buildVersion: di.inject(buildVersionInjectable),
createKubeJsonApiForCluster: di.inject(createKubeJsonApiForClusterInjectable),
computeShellEnvironment: di.inject(computeShellEnvironmentInjectable),
spawnPty: di.inject(spawnPtyInjectable),
};
const kubectl = createKubectl(params.cluster.version);
const session = new NodeShellSession(dependencies, { kubectl, ...params });

View File

@ -6,18 +6,18 @@
import type { Cluster } from "../../common/cluster/cluster";
import type { Kubectl } from "../kubectl/kubectl";
import type WebSocket from "ws";
import { shellEnv } from "../utils/shell-env";
import { app } from "electron";
import { clearKubeconfigEnvVars } from "../utils/clear-kube-env-vars";
import path from "path";
import os, { userInfo } from "os";
import { UserStore } from "../../common/user-store";
import * as pty from "node-pty";
import type * as pty from "node-pty";
import { appEventBus } from "../../common/app-event-bus/event-bus";
import { stat } from "fs/promises";
import { getOrInsertWith } from "../../common/utils";
import { type TerminalMessage, TerminalChannels } from "../../common/terminal/channels";
import type { Logger } from "../../common/logger";
import type { ComputeShellEnvironment } from "../utils/shell-env/compute-shell-environment.injectable";
import type { SpawnPty } from "./spawn-pty.injectable";
import type { InitializableState } from "../../common/initializable-state/create";
export class ShellOpenError extends Error {
constructor(message: string, options?: ErrorOptions) {
@ -107,6 +107,11 @@ export interface ShellSessionDependencies {
readonly isWindows: boolean;
readonly isMac: boolean;
readonly logger: Logger;
readonly resolvedShell: string | undefined;
readonly appName: string;
readonly buildVersion: InitializableState<string>;
computeShellEnvironment: ComputeShellEnvironment;
spawnPty: SpawnPty;
}
export interface ShellSessionArgs {
@ -148,14 +153,14 @@ export abstract class ShellSession {
protected abstract get cwd(): string | undefined;
protected ensureShellProcess(shell: string, args: string[], env: Record<string, string | undefined>, cwd: string): { shellProcess: pty.IPty; resume: boolean } {
protected ensureShellProcess(shell: string, args: string[], env: Partial<Record<string, string>>, cwd: string): { shellProcess: pty.IPty; resume: boolean } {
const resume = ShellSession.processes.has(this.terminalId);
const shellProcess = getOrInsertWith(ShellSession.processes, this.terminalId, () => (
pty.spawn(shell, args, {
this.dependencies.spawnPty(shell, args, {
rows: 30,
cols: 80,
cwd,
env: env as Record<string, string>,
env,
name: "xterm-256color",
// TODO: Something else is broken here so we need to force the use of winPty on windows
useConpty: false,
@ -331,19 +336,27 @@ export abstract class ShellSession {
}
protected async getShellEnv() {
const shell = UserStore.getInstance().resolvedShell;
const env = clearKubeconfigEnvVars(JSON.parse(JSON.stringify(await shellEnv(shell || userInfo().shell))));
const pathStr = [await this.kubectlBinDirP, ...this.getPathEntries(), process.env.PATH].join(path.delimiter);
const shell = this.dependencies.resolvedShell || userInfo().shell;
const result = await this.dependencies.computeShellEnvironment(shell);
const rawEnv = (() => {
if (result.callWasSuccessful) {
return result.response ?? process.env;
}
return process.env;
})();
const env = clearKubeconfigEnvVars(JSON.parse(JSON.stringify(rawEnv)));
const pathStr = [await this.kubectlBinDirP, ...this.getPathEntries(), env.PATH].join(path.delimiter);
delete env.DEBUG; // don't pass DEBUG into shells
if (this.dependencies.isWindows) {
env.SystemRoot = process.env.SystemRoot;
env.PTYSHELL = shell || "powershell.exe";
env.PATH = pathStr;
env.LENS_SESSION = "true";
env.WSLENV = [
process.env.WSLENV,
env.WSLENV,
"KUBECONFIG/up:LENS_SESSION/u",
]
.filter(Boolean)
@ -363,8 +376,8 @@ export abstract class ShellSession {
env.PTYPID = process.pid.toString();
env.KUBECONFIG = await this.kubeconfigPathP;
env.TERM_PROGRAM = app.getName();
env.TERM_PROGRAM_VERSION = app.getVersion();
env.TERM_PROGRAM = this.dependencies.appName;
env.TERM_PROGRAM_VERSION = this.dependencies.buildVersion.get();
if (this.cluster.preferences.httpsProxy) {
env.HTTPS_PROXY = this.cluster.preferences.httpsProxy;

View File

@ -0,0 +1,10 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverride } from "../../common/test-utils/get-global-override";
import spawnPtyInjectable from "./spawn-pty.injectable";
export default getGlobalOverride(spawnPtyInjectable, () => () => {
throw new Error("Tried to spawn a PTY without an override");
});

View File

@ -0,0 +1,21 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { IPty, IPtyForkOptions, IWindowsPtyForkOptions } from "node-pty";
import { spawn } from "node-pty";
export type WindowsSpawnPtyOptions = Omit<IWindowsPtyForkOptions, "env"> & { env?: Partial<Record<string, string>> };
export type UnixSpawnPtyOptions = Omit<IPtyForkOptions, "env"> & { env?: Partial<Record<string, string>> };
export type SpawnPtyOptions = UnixSpawnPtyOptions | WindowsSpawnPtyOptions;
export type SpawnPty = (file: string, args: string[], options: SpawnPtyOptions) => IPty;
const spawnPtyInjectable = getInjectable({
id: "spawn-pty",
instantiate: () => spawn as SpawnPty,
causesSideEffects: true,
});
export default spawnPtyInjectable;

View File

@ -5,11 +5,11 @@
import { getInjectable } from "@ogre-tools/injectable";
import loggerInjectable from "../../../common/logger.injectable";
import { onLoadOfApplicationInjectionToken } from "../runnable-tokens/on-load-of-application-injection-token";
import { shellEnv } from "../../utils/shell-env";
import os from "os";
import { unionPATHs } from "../../../common/utils/union-env-path";
import isSnapPackageInjectable from "../../../common/vars/is-snap-package.injectable";
import electronAppInjectable from "../../electron-app/electron-app.injectable";
import computeShellEnvironmentInjectable from "../../utils/shell-env/compute-shell-environment.injectable";
const setupShellInjectable = getInjectable({
id: "setup-shell",
@ -18,13 +18,24 @@ const setupShellInjectable = getInjectable({
const logger = di.inject(loggerInjectable);
const isSnapPackage = di.inject(isSnapPackageInjectable);
const electronApp = di.inject(electronAppInjectable);
const computeShellEnvironment = di.inject(computeShellEnvironmentInjectable);
return {
id: "setup-shell",
run: async () => {
run: async (): Promise<void> => {
logger.info("🐚 Syncing shell environment");
const env = await shellEnv(os.userInfo().shell);
const result = await computeShellEnvironment(os.userInfo().shell);
if (!result.callWasSuccessful) {
return void logger.error(`[SHELL-SYNC]: ${result.error}`);
}
const env = result.response;
if (!env) {
return void logger.debug("[SHELL-SYNC]: nothing to do, env not special in shells");
}
if (!env.LANG) {
// the LANG env var expects an underscore instead of electron's dash

View File

@ -13,7 +13,7 @@ const anyKubeconfig = /^kubeconfig$/i;
* before KUBECONFIG and we only set KUBECONFIG.
* @param env The current copy of env
*/
export function clearKubeconfigEnvVars(env: Record<string, any>): Record<string, any> {
export function clearKubeconfigEnvVars(env: Partial<Record<string, string>>): Partial<Record<string, string>> {
return Object.fromEntries(
Object.entries(env)
.filter(([key]) => anyKubeconfig.exec(key) === null),

View File

@ -1,118 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { spawn } from "child_process";
import { randomUUID } from "crypto";
import { basename } from "path";
import { isWindows } from "../../common/vars";
import logger from "../logger";
export type EnvironmentVariables = Partial<Record<string, string>>;
async function unixShellEnvironment(shell: string): Promise<EnvironmentVariables> {
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);
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[] = [];
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);
}
});
});
}
let shellSyncFailed = false;
/**
* Attempts to get the shell environment per the user's existing startup scripts.
* If the environment can't be retrieved after 5 seconds an error message is logged.
* Subsequent calls after such a timeout simply log an error message without trying
* to get the environment, unless forceRetry is true.
* @param shell the shell to get the environment from
* @returns object containing the shell's environment variables. An empty object is
* returned if the call fails.
*/
export async function shellEnv(shell: string) : Promise<EnvironmentVariables> {
if (isWindows) {
return {};
}
if (!shellSyncFailed) {
try {
return await Promise.race([
unixShellEnvironment(shell),
new Promise<EnvironmentVariables>((_resolve, reject) => setTimeout(() => {
reject(new Error("Resolving shell environment is taking very long. Please review your shell configuration."));
}, 30_000)),
]);
} catch (error) {
logger.error(`shellEnv: ${error}`);
shellSyncFailed = true;
}
} else {
logger.error("shellSync(): Resolving shell environment took too long. Please review your shell configuration.");
}
return {};
}

View File

@ -0,0 +1,62 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { AsyncResult } from "../../../common/utils/async-result";
import { getInjectable } from "@ogre-tools/injectable";
import isWindowsInjectable from "../../../common/vars/is-windows.injectable";
import { disposer } from "../../../common/utils";
import computeUnixShellEnvironmentInjectable from "./compute-unix-shell-environment.injectable";
export type EnvironmentVariables = Partial<Record<string, string>>;
export type ComputeShellEnvironment = (shell: string) => Promise<AsyncResult<EnvironmentVariables | undefined, string>>;
const computeShellEnvironmentInjectable = getInjectable({
id: "compute-shell-environment",
instantiate: (di): ComputeShellEnvironment => {
const isWindows = di.inject(isWindowsInjectable);
const computeUnixShellEnvironment = di.inject(computeUnixShellEnvironmentInjectable);
if (isWindows) {
return async () => ({
callWasSuccessful: true,
response: undefined,
});
}
return async (shell) => {
const controller = new AbortController();
const shellEnv = computeUnixShellEnvironment(shell, { signal: controller.signal });
const cleanup = disposer();
const timeoutHandle = setTimeout(() => controller.abort(), 30_000);
cleanup.push(() => clearTimeout(timeoutHandle));
try {
return {
callWasSuccessful: true,
response: await shellEnv,
};
} catch (error) {
if (controller.signal.aborted) {
return {
callWasSuccessful: false,
error: "Resolving shell environment is taking very long. Please review your shell configuration.",
};
}
return {
callWasSuccessful: false,
error: String(error),
};
} finally {
cleanup();
}
};
},
});
export default computeShellEnvironmentInjectable;

View File

@ -0,0 +1,11 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverride } from "../../../common/test-utils/get-global-override";
import computeUnixShellEnvironmentInjectable from "./compute-unix-shell-environment.injectable";
export default getGlobalOverride(computeUnixShellEnvironmentInjectable, () => async () => {
throw new Error("Tried to get unix shell env without override");
});

View File

@ -0,0 +1,93 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { spawn } from "child_process";
import { randomUUID } from "crypto";
import { basename } from "path";
import type { EnvironmentVariables } from "./compute-shell-environment.injectable";
import { getInjectable } from "@ogre-tools/injectable";
interface UnixShellEnvOptions {
signal?: AbortSignal;
}
export type ComputeUnixShellEnvironment = (shell: string, opts?: UnixShellEnvOptions) => Promise<EnvironmentVariables>;
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",
};
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,
});
export default computeUnixShellEnvironmentInjectable;