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:
parent
d63bf277b2
commit
7e17f5c2fe
@ -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),
|
||||||
|
|||||||
@ -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);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user