mirror of
https://github.com/cloudflare/wrangler-action.git
synced 2024-11-22 10:03:24 +01:00
Merge pull request #171 from cloudflare/cina/refactor-subprocess-exec
Refactor subprocess execution to use `@actions/exec`
This commit is contained in:
commit
819ea006e6
6 changed files with 166 additions and 109 deletions
5
.changeset/proud-insects-add.md
Normal file
5
.changeset/proud-insects-add.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"wrangler-action": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fixed issues that caused the action to fail if any secret or var values contained shell metacharacters.
|
5
.changeset/warm-mugs-judge.md
Normal file
5
.changeset/warm-mugs-judge.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"wrangler-action": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Bumped `DEFAULT_WRANGLER_VERSION` to 3.13.2
|
20
package-lock.json
generated
20
package-lock.json
generated
|
@ -1,15 +1,16 @@
|
||||||
{
|
{
|
||||||
"name": "wrangler-action",
|
"name": "wrangler-action",
|
||||||
"version": "3.3.0",
|
"version": "3.3.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "wrangler-action",
|
"name": "wrangler-action",
|
||||||
"version": "3.3.0",
|
"version": "3.3.1",
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.10.0"
|
"@actions/core": "^1.10.0",
|
||||||
|
"@actions/exec": "^1.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@changesets/changelog-github": "^0.4.8",
|
"@changesets/changelog-github": "^0.4.8",
|
||||||
|
@ -31,6 +32,14 @@
|
||||||
"uuid": "^8.3.2"
|
"uuid": "^8.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@actions/exec": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@actions/io": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@actions/http-client": {
|
"node_modules/@actions/http-client": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.1.0.tgz",
|
||||||
|
@ -39,6 +48,11 @@
|
||||||
"tunnel": "^0.0.6"
|
"tunnel": "^0.0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@actions/io": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="
|
||||||
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.22.5",
|
"version": "7.22.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz",
|
||||||
|
|
|
@ -29,7 +29,8 @@
|
||||||
"check": "prettier --check ."
|
"check": "prettier --check ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.10.0"
|
"@actions/core": "^1.10.0",
|
||||||
|
"@actions/exec": "^1.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@changesets/changelog-github": "^0.4.8",
|
"@changesets/changelog-github": "^0.4.8",
|
||||||
|
|
54
src/exec.ts
Normal file
54
src/exec.ts
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
188
src/index.ts
188
src/index.ts
|
@ -8,13 +8,11 @@ import {
|
||||||
startGroup as originalStartGroup,
|
startGroup as originalStartGroup,
|
||||||
setFailed,
|
setFailed,
|
||||||
} from "@actions/core";
|
} from "@actions/core";
|
||||||
import { exec, execSync } from "node:child_process";
|
import { exec, execShell } from "./exec";
|
||||||
import * as util from "node:util";
|
|
||||||
import { checkWorkingDirectory, semverCompare } from "./utils";
|
import { checkWorkingDirectory, semverCompare } from "./utils";
|
||||||
import { getPackageManager } from "./packageManagers";
|
import { getPackageManager } from "./packageManagers";
|
||||||
const execAsync = util.promisify(exec);
|
|
||||||
|
|
||||||
const DEFAULT_WRANGLER_VERSION = "3.5.1";
|
const DEFAULT_WRANGLER_VERSION = "3.13.2";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A configuration object that contains all the inputs & immutable state for the action.
|
* A configuration object that contains all the inputs & immutable state for the action.
|
||||||
|
@ -62,8 +60,8 @@ function endGroup(): void {
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
try {
|
try {
|
||||||
installWrangler();
|
|
||||||
authenticationSetup();
|
authenticationSetup();
|
||||||
|
await installWrangler();
|
||||||
await execCommands(getMultilineInput("preCommands"), "pre");
|
await execCommands(getMultilineInput("preCommands"), "pre");
|
||||||
await uploadSecrets();
|
await uploadSecrets();
|
||||||
await wranglerCommands();
|
await wranglerCommands();
|
||||||
|
@ -75,36 +73,28 @@ async function main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runProcess(
|
async function installWrangler() {
|
||||||
command: Parameters<typeof execAsync>[0],
|
|
||||||
options: Parameters<typeof execAsync>[1],
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const result = await execAsync(command, options);
|
|
||||||
|
|
||||||
result.stdout && info(result.stdout.toString());
|
|
||||||
result.stderr && error(result.stderr.toString(), true);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (err: any) {
|
|
||||||
err.stdout && info(err.stdout.toString());
|
|
||||||
err.stderr && error(err.stderr.toString(), true);
|
|
||||||
throw new Error(`\`${command}\` returned non-zero exit code.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function installWrangler() {
|
|
||||||
if (config["WRANGLER_VERSION"].startsWith("1")) {
|
if (config["WRANGLER_VERSION"].startsWith("1")) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Wrangler v1 is no longer supported by this action. Please use major version 2 or greater`,
|
`Wrangler v1 is no longer supported by this action. Please use major version 2 or greater`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
startGroup("📥 Installing Wrangler");
|
startGroup("📥 Installing Wrangler");
|
||||||
const command = `${packageManager.install} wrangler@${config["WRANGLER_VERSION"]}`;
|
try {
|
||||||
info(`Running command: ${command}`);
|
await exec(
|
||||||
execSync(command, { cwd: config["workingDirectory"], env: process.env });
|
packageManager.install,
|
||||||
info(`✅ Wrangler installed`, true);
|
[`wrangler@${config["WRANGLER_VERSION"]}`],
|
||||||
endGroup();
|
{
|
||||||
|
cwd: config["workingDirectory"],
|
||||||
|
silent: config["QUIET_MODE"],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
info(`✅ Wrangler installed`, true);
|
||||||
|
} finally {
|
||||||
|
endGroup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function authenticationSetup() {
|
function authenticationSetup() {
|
||||||
|
@ -119,28 +109,21 @@ async function execCommands(commands: string[], cmdType: string) {
|
||||||
|
|
||||||
startGroup(`🚀 Running ${cmdType}Commands`);
|
startGroup(`🚀 Running ${cmdType}Commands`);
|
||||||
try {
|
try {
|
||||||
const arrPromises = commands.map(async (command) => {
|
for (const command of commands) {
|
||||||
const cmd = command.startsWith("wrangler")
|
const cmd = command.startsWith("wrangler")
|
||||||
? `${packageManager.exec} ${command}`
|
? `${packageManager.exec} ${command}`
|
||||||
: command;
|
: command;
|
||||||
|
|
||||||
info(`🚀 Executing command: ${cmd}`);
|
await execShell(cmd, {
|
||||||
|
|
||||||
return await runProcess(cmd, {
|
|
||||||
cwd: config["workingDirectory"],
|
cwd: config["workingDirectory"],
|
||||||
env: process.env,
|
silent: config["QUIET_MODE"],
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
await Promise.all(arrPromises);
|
|
||||||
} finally {
|
} finally {
|
||||||
endGroup();
|
endGroup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper function to get the secret from the environment variables.
|
|
||||||
*/
|
|
||||||
function getSecret(secret: string) {
|
function getSecret(secret: string) {
|
||||||
if (!secret) {
|
if (!secret) {
|
||||||
throw new Error("Secret name cannot be blank.");
|
throw new Error("Secret name cannot be blank.");
|
||||||
|
@ -148,33 +131,43 @@ function getSecret(secret: string) {
|
||||||
|
|
||||||
const value = process.env[secret];
|
const value = process.env[secret];
|
||||||
if (!value) {
|
if (!value) {
|
||||||
throw new Error(`Value for secret ${secret} not found.`);
|
throw new Error(`Value for secret ${secret} not found in environment.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function legacyUploadSecrets(
|
function getEnvVar(envVar: string) {
|
||||||
|
if (!envVar) {
|
||||||
|
throw new Error("Var name cannot be blank.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = process.env[envVar];
|
||||||
|
if (!value) {
|
||||||
|
throw new Error(`Value for var ${envVar} not found in environment.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function legacyUploadSecrets(
|
||||||
secrets: string[],
|
secrets: string[],
|
||||||
environment?: string,
|
environment?: string,
|
||||||
workingDirectory?: string,
|
workingDirectory?: string,
|
||||||
) {
|
) {
|
||||||
const arrPromises = secrets
|
return Promise.all(
|
||||||
.map((secret) => {
|
secrets.map((secret) => {
|
||||||
const command = `echo ${getSecret(secret)} | ${
|
const args = ["wrangler", "secret", "put", secret];
|
||||||
packageManager.exec
|
if (environment) {
|
||||||
} wrangler secret put ${secret}`;
|
args.push("--env", environment);
|
||||||
return environment ? command.concat(` --env ${environment}`) : command;
|
}
|
||||||
})
|
return exec(packageManager.exec, args, {
|
||||||
.map(
|
cwd: workingDirectory,
|
||||||
async (command) =>
|
silent: config["QUIET_MODE"],
|
||||||
await execAsync(command, {
|
input: Buffer.from(getSecret(secret)),
|
||||||
cwd: workingDirectory,
|
});
|
||||||
env: process.env,
|
}),
|
||||||
}),
|
);
|
||||||
);
|
|
||||||
|
|
||||||
await Promise.all(arrPromises);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function uploadSecrets() {
|
async function uploadSecrets() {
|
||||||
|
@ -189,51 +182,34 @@ async function uploadSecrets() {
|
||||||
startGroup("🔑 Uploading secrets...");
|
startGroup("🔑 Uploading secrets...");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (semverCompare(config["WRANGLER_VERSION"], "3.4.0"))
|
if (semverCompare(config["WRANGLER_VERSION"], "3.4.0")) {
|
||||||
return legacyUploadSecrets(secrets, environment, workingDirectory);
|
return legacyUploadSecrets(secrets, environment, workingDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
const secretObj = secrets.reduce((acc: any, secret: string) => {
|
const args = ["wrangler", "secret:bulk"];
|
||||||
acc[secret] = getSecret(secret);
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
const environmentSuffix = !environment.length
|
if (environment) {
|
||||||
? ""
|
args.push("--env", environment);
|
||||||
: ` --env ${environment}`;
|
}
|
||||||
|
|
||||||
const secretCmd = `echo "${JSON.stringify(secretObj).replaceAll(
|
await exec(packageManager.exec, args, {
|
||||||
'"',
|
|
||||||
'\\"',
|
|
||||||
)}" | ${packageManager.exec} wrangler secret:bulk ${environmentSuffix}`;
|
|
||||||
|
|
||||||
execSync(secretCmd, {
|
|
||||||
cwd: workingDirectory,
|
cwd: workingDirectory,
|
||||||
env: process.env,
|
silent: config["QUIET_MODE"],
|
||||||
stdio: "ignore",
|
input: Buffer.from(
|
||||||
|
JSON.stringify(
|
||||||
|
Object.fromEntries(
|
||||||
|
secrets.map((secret) => [secret, getSecret(secret)]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
info(`✅ Uploaded secrets`);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error(`❌ Upload failed`);
|
|
||||||
throw new Error(`Failed to upload secrets.`);
|
throw new Error(`Failed to upload secrets.`);
|
||||||
} finally {
|
} finally {
|
||||||
endGroup();
|
endGroup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getVarArgs() {
|
|
||||||
const vars = config["VARS"];
|
|
||||||
const envVarArray = vars.map((envVar: string) => {
|
|
||||||
if (process.env[envVar] && process.env[envVar]?.length !== 0) {
|
|
||||||
return `${envVar}:${process.env[envVar]!}`;
|
|
||||||
} else {
|
|
||||||
throw new Error(`Value for var ${envVar} not found in environment.`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return envVarArray.length > 0 ? `--var ${envVarArray.join(" ").trim()}` : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
async function wranglerCommands() {
|
async function wranglerCommands() {
|
||||||
startGroup("🚀 Running Wrangler Commands");
|
startGroup("🚀 Running Wrangler Commands");
|
||||||
try {
|
try {
|
||||||
|
@ -248,27 +224,29 @@ async function wranglerCommands() {
|
||||||
commands.push(deployCommand);
|
commands.push(deployCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
const arrPromises = commands.map(async (command) => {
|
for (let command of commands) {
|
||||||
if (environment.length > 0 && !command.includes(`--env`)) {
|
const args = [];
|
||||||
command = command.concat(` --env ${environment}`);
|
|
||||||
|
if (environment && !command.includes("--env")) {
|
||||||
|
args.push("--env", environment);
|
||||||
}
|
}
|
||||||
|
|
||||||
const cmd = `${packageManager.exec} wrangler ${command} ${
|
if (
|
||||||
|
config["VARS"].length &&
|
||||||
(command.startsWith("deploy") || command.startsWith("publish")) &&
|
(command.startsWith("deploy") || command.startsWith("publish")) &&
|
||||||
!command.includes(`--var`)
|
!command.includes("--var")
|
||||||
? getVarArgs()
|
) {
|
||||||
: ""
|
args.push("--var");
|
||||||
}`.trim();
|
for (const v of config["VARS"]) {
|
||||||
|
args.push(`${v}:${getEnvVar(v)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
info(`🚀 Executing command: ${cmd}`);
|
await exec(`${packageManager.exec} wrangler ${command}`, args, {
|
||||||
|
|
||||||
return await runProcess(cmd, {
|
|
||||||
cwd: config["workingDirectory"],
|
cwd: config["workingDirectory"],
|
||||||
env: process.env,
|
silent: config["QUIET_MODE"],
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
await Promise.all(arrPromises);
|
|
||||||
} finally {
|
} finally {
|
||||||
endGroup();
|
endGroup();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue