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

Fix syncing shell environment when using fish

- Add some better logging for the future

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2022-11-02 09:20:04 -04:00
parent d63bf277b2
commit 7e17f5c2fe
2 changed files with 60 additions and 24 deletions

View File

@ -6,7 +6,7 @@
import type { AsyncResult } from "../../../common/utils/async-result"; import type { AsyncResult } from "../../../common/utils/async-result";
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import isWindowsInjectable from "../../../common/vars/is-windows.injectable"; import isWindowsInjectable from "../../../common/vars/is-windows.injectable";
import { disposer } from "../../../common/utils"; import { disposer, hasTypedProperty, isString } from "../../../common/utils";
import computeUnixShellEnvironmentInjectable from "./compute-unix-shell-environment.injectable"; import computeUnixShellEnvironmentInjectable from "./compute-unix-shell-environment.injectable";
export type EnvironmentVariables = Partial<Record<string, string>>; export type EnvironmentVariables = Partial<Record<string, string>>;
@ -47,6 +47,13 @@ const computeShellEnvironmentInjectable = getInjectable({
}; };
} }
if (error && hasTypedProperty(error, "stderr", isString)) {
return {
callWasSuccessful: false,
error: `${error}:\n${error.stderr}`,
};
}
return { return {
callWasSuccessful: false, callWasSuccessful: false,
error: String(error), error: String(error),

View File

@ -7,6 +7,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import getBasenameOfPathInjectable from "../../../common/path/get-basename.injectable"; import getBasenameOfPathInjectable from "../../../common/path/get-basename.injectable";
import spawnInjectable from "../../child-process/spawn.injectable"; import spawnInjectable from "../../child-process/spawn.injectable";
import randomUUIDInjectable from "../../crypto/random-uuid.injectable"; import randomUUIDInjectable from "../../crypto/random-uuid.injectable";
import loggerInjectable from "../../../common/logger.injectable";
export interface UnixShellEnvOptions { export interface UnixShellEnvOptions {
signal: AbortSignal; signal: AbortSignal;
@ -14,19 +15,33 @@ export interface UnixShellEnvOptions {
export type ComputeUnixShellEnvironment = (shell: string, opts: UnixShellEnvOptions) => Promise<EnvironmentVariables>; export type ComputeUnixShellEnvironment = (shell: string, opts: UnixShellEnvOptions) => Promise<EnvironmentVariables>;
const getResetProcessEnv = (src: Partial<Record<string, string>>, names: string[]): ((target: Partial<Record<string, string>>) => void) => {
const pairs = names.map(name => ([name, src[name]] as const));
return (target) => {
for (const [name, orginalValue] of pairs) {
if (orginalValue) {
target[name] = orginalValue;
} else {
delete target[name];
}
}
};
};
const computeUnixShellEnvironmentInjectable = getInjectable({ const computeUnixShellEnvironmentInjectable = getInjectable({
id: "compute-unix-shell-environment", id: "compute-unix-shell-environment",
instantiate: (di): ComputeUnixShellEnvironment => { instantiate: (di): ComputeUnixShellEnvironment => {
const powerShellName = /^pwsh(-preview)?$/; const powerShellName = /^pwsh(-preview)?$/;
const nonBashLikeShellName = /^t?csh$/; const cshLikeShellName = /^(t?csh)$/;
const fishLikeShellName = /^fish$/;
const getBasenameOfPath = di.inject(getBasenameOfPathInjectable); const getBasenameOfPath = di.inject(getBasenameOfPathInjectable);
const spawn = di.inject(spawnInjectable); const spawn = di.inject(spawnInjectable);
const logger = di.inject(loggerInjectable);
const randomUUID = di.inject(randomUUIDInjectable); const randomUUID = di.inject(randomUUIDInjectable);
const getShellSpecifices = (shellPath: string, mark: string) => { const getShellSpecifices = (shellName: string, mark: string) => {
const shellName = getBasenameOfPath(shellPath);
if (powerShellName.test(shellName)) { if (powerShellName.test(shellName)) {
// Older versions of PowerShell removes double quotes sometimes so we use "double single quotes" which is how // 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. // you escape single quotes inside of a single quoted string.
@ -38,66 +53,80 @@ const computeUnixShellEnvironmentInjectable = getInjectable({
return { return {
command: `'${process.execPath}' -p '"${mark}" + JSON.stringify(process.env) + "${mark}"'`, command: `'${process.execPath}' -p '"${mark}" + JSON.stringify(process.env) + "${mark}"'`,
shellArgs: nonBashLikeShellName.test(shellName) shellArgs: cshLikeShellName.test(shellName) || fishLikeShellName.test(shellName)
// tcsh and csh don't support any other options when providing the -l (login) shell option // Some shells don't support any other options when providing the -l (login) shell option
? ["-l"] ? ["-l"]
// zsh (at least, maybe others) don't load RC files when in non-interactive mode, even when using -l (login) option // zsh (at least, maybe others) don't load RC files when in non-interactive mode, even when using -l (login) option
: ["-li"], : ["-li"],
}; };
}; };
return async (shellPath, opts) => { return async (shellPath, opts) => {
const runAsNode = process.env["ELECTRON_RUN_AS_NODE"]; const resetEnvPairs = getResetProcessEnv(process.env, [
const noAttach = process.env["ELECTRON_NO_ATTACH_CONSOLE"]; "ELECTRON_RUN_AS_NODE",
"ELECTRON_NO_ATTACH_CONSOLE",
"TERM",
]);
const env = { const env = {
...process.env, ...process.env,
ELECTRON_RUN_AS_NODE: "1", ELECTRON_RUN_AS_NODE: "1",
ELECTRON_NO_ATTACH_CONSOLE: "1", ELECTRON_NO_ATTACH_CONSOLE: "1",
TERM: "screen-256color-bce", // required for fish
}; };
const mark = randomUUID().replace(/-/g, ""); const mark = randomUUID().replace(/-/g, "");
const regex = new RegExp(`${mark}(\\{.*\\})${mark}`); const regex = new RegExp(`${mark}(\\{.*\\})${mark}`);
const { command, shellArgs } = getShellSpecifices(shellPath, mark); const { command, shellArgs } = getShellSpecifices(shellPath, mark);
logger.info(`[UNIX-SHELL-ENV]: running against ${shellPath}`, { command, shellArgs });
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const shellName = getBasenameOfPath(shellPath);
const isFishShellLike = fishLikeShellName.test(shellName);
if (isFishShellLike) {
shellArgs.push("-c", command);
}
const shellProcess = spawn(shellPath, shellArgs, { const shellProcess = spawn(shellPath, shellArgs, {
detached: true,
signal: opts.signal, signal: opts.signal,
env, env,
}); });
const stdout: Buffer[] = []; const stdout: Buffer[] = [];
const stderr: Buffer[] = [];
shellProcess.stdout.on("data", b => stdout.push(b)); shellProcess.stdout.on("data", b => stdout.push(b));
shellProcess.stderr.on("data", b => stderr.push(b));
shellProcess.on("error", (err) => reject(err)); shellProcess.on("error", (err) => reject(err));
shellProcess.on("close", (code, signal) => { shellProcess.on("close", (code, signal) => {
if (code || signal) { if (code || signal) {
return reject(new Error(`Unexpected return code from spawned shell (code: ${code}, signal: ${signal})`)); return reject(Object.assign(new Error(`Unexpected return code from spawned shell (code: ${code}, signal: ${signal})`), {
stderr: Buffer.concat(stderr).toString("utf-8"),
}));
} }
try { try {
const rawOutput = Buffer.concat(stdout).toString("utf-8"); const rawOutput = Buffer.concat(stdout).toString("utf-8");
logger.info(`[UNIX-SHELL-ENV]: got the following output`, { rawOutput });
const match = regex.exec(rawOutput); const match = regex.exec(rawOutput);
const strippedRawOutput = match ? match[1] : "{}"; const strippedRawOutput = match ? match[1] : "{}";
const resolvedEnv = JSON.parse(strippedRawOutput); const resolvedEnv = JSON.parse(strippedRawOutput);
if (runAsNode) { resetEnvPairs(resolvedEnv);
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); resolve(resolvedEnv);
} catch (err) { } catch (err) {
reject(err); reject(err);
} }
}); });
shellProcess.stdin.end(command);
if (isFishShellLike) {
shellProcess.stdin.end();
} else {
shellProcess.stdin.end(command);
}
}); });
}; };
}, },