ori-sh-handler-args/sh_handler.js

75 lines
2.1 KiB
JavaScript

import { toString } from "@weborigami/async-tree";
import { spawn } from "node:child_process";
/**
* Shell script file extension handler
*/
export default {
mediaType: "text/plain",
/** @type {import("@weborigami/async-tree").UnpackFunction} */
async unpack(packed) {
const scriptText = toString(packed);
if (scriptText === null) {
throw new Error(".sh handler: input isn't text");
}
//HANS: take remaining arguments
return async (input, ...rest) => {
return runShellScript(scriptText, input, rest);
};
},
};
/**
* Run arbitrary shell script text in /bin/sh and feed it stdin.
* Supports multiple commands, pipelines, redirects, etc.
*
* @param {string} scriptText - Shell code (may contain newlines/side effects)
* @param {import("@weborigami/async-tree").Stringlike} inputText - Text to pipe to the script's stdin
* @returns {Promise<string>}
*/
function runShellScript(scriptText, inputText, rest) {
if (inputText instanceof Function) {
throw new Error(
"A .sh file expects text input but got a function instead. Did you mean to invoke the function?",
);
}
return new Promise((resolve, reject) => {
// Use sh -c "<scriptText>" so stdin is free for inputText
// HANS: pass remaining arguments to child process.
// 'command' is inserted here because sh -c expects command_name at that position.
const child = spawn("sh", ["-c", scriptText, "command", ...rest], {
env: { ...process.env },
stdio: ["pipe", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
child.stdout.on("data", (c) => (stdout += c));
child.stderr.on("data", (c) => (stderr += c));
child.on("error", reject);
child.on("close", (code) => {
if (code !== 0) {
/** @type {any} */
const err = new Error(
`Shell exited with code ${code}${stderr ? `: ${stderr}` : ""}`,
);
err.code = code;
err.stdout = stdout;
err.stderr = stderr;
return reject(err);
}
resolve(stdout);
});
// Feed the input to the script's stdin and close it
child.stdin.end(inputText);
});
}