mirror of
https://github.com/cloudflare/wrangler-action.git
synced 2024-11-28 12:44:45 +01:00
d647227bbc
Instead of using a mix of `child_process.exec`, `child_process.execSync` and a promisified version of `child_process.exec`, we now (mostly) just use `@actions/exec`. That runs `child_process.spawn` under the hood and handles a lot of character escaping for us. We can also now pass Buffers directly into the subprocess as stdin instead of relying on shell piping. This ends up fixing a few problems we had where secrets and env var values containing shell metacharacters were being misinterpreted. Unfortunately, `@actions/exec` doesn't support running with a shell. That means we still have to roll our own wrapper around `child_process.exec` to avoid a breaking change to `preCommands` and `postCommands`, since users might be expecting these to run within a shell. Also worth noting that we're no longer hiding stdout and stderr from the secret uploading step. We were previously doing this out of an abundance of caution, but it made debugging issues very difficult if secret upload failed for some reason. I feel ok doing this since we're no longer echoing & piping the secret values, wrangler doesn't ever output secret values, and as a last line of defense GitHub masks any secret values that accidentally get logged.
54 lines
1.2 KiB
TypeScript
54 lines
1.2 KiB
TypeScript
import {
|
|
exec as _childProcessExec,
|
|
type ExecException,
|
|
} from "node:child_process";
|
|
import { EOL } from "node:os";
|
|
import { promisify } from "node:util";
|
|
|
|
export { exec } from "@actions/exec";
|
|
|
|
const childProcessExec = promisify(_childProcessExec);
|
|
|
|
type ExecAsyncException = ExecException & {
|
|
stderr: string;
|
|
stdout: string;
|
|
};
|
|
|
|
function isExecAsyncException(err: unknown): err is ExecAsyncException {
|
|
return err instanceof Error && "code" in err && "stderr" in err;
|
|
}
|
|
|
|
export async function execShell(
|
|
command: string,
|
|
{
|
|
silent = false,
|
|
...options
|
|
}: Parameters<typeof childProcessExec>[1] & { silent?: boolean } = {},
|
|
) {
|
|
if (!silent) {
|
|
process.stdout.write("[command]" + command + EOL);
|
|
}
|
|
|
|
try {
|
|
const promise = childProcessExec(command, {
|
|
...options,
|
|
});
|
|
|
|
const { child } = promise;
|
|
|
|
if (!silent) {
|
|
child.stdout?.on("data", (data: Buffer) => process.stdout.write(data));
|
|
child.stderr?.on("data", (data: Buffer) => process.stderr.write(data));
|
|
}
|
|
|
|
await promise;
|
|
return child.exitCode;
|
|
} catch (err: any) {
|
|
if (isExecAsyncException(err)) {
|
|
process.stderr.write(err.stderr);
|
|
throw new Error(`Process failed with exit code ${err.code}`);
|
|
}
|
|
|
|
throw err;
|
|
}
|
|
}
|