Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion packages/plugin-sdk/src/host-scripts/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,15 @@ export type ScriptShellMode = "plain" | "auto" | "root" | "root-only";

const ERR = "SCRIPT_ERR:";

/** The command line that runs a script sent on stdin. */
/**
* The command line that runs a script sent on stdin. SSH hands it to the
* login shell, which may be fish or csh, so the POSIX part runs under sh.
*/
export function scriptShell(mode: ScriptShellMode): string {
return `sh -c ${shellQuote(posixShell(mode))}`;
}

function posixShell(mode: ScriptShellMode): string {
switch (mode) {
case "plain":
return `${SCRIPT_PRELUDE}exec bash -s`;
Expand Down
17 changes: 17 additions & 0 deletions src/backend/tests/plugins/host-scripts/shell.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,23 @@ describe("script shells", () => {
expect(scriptShell("root-only")).toContain("SCRIPT_ERR:");
});

it("is one sh -c command, so a fish or csh login shell can run it", () => {
for (const mode of ["plain", "auto", "root", "root-only"] as const) {
expect(scriptShell(mode)).toMatch(/^sh -c '[^]*'$/);
}
});

it.runIf(process.platform !== "win32")(
"still reads the script from stdin through sh",
() => {
const out = execFileSync("sh", ["-c", scriptShell("plain")], {
input: 'echo "ran:$TZ"\n',
encoding: "utf8",
});
expect(out.trim()).toBe("ran:UTC");
},
);

it("collects error lines, old and new markers", () => {
expect(scriptError("x\nSCRIPT_ERR:one\nSERVICES_ERR:two\n")).toBe(
"one\ntwo",
Expand Down
Loading