Merge pull request #161 from cloudflare/cina/throw-on-failures

Refactor error handling to stop execution on errors
This commit is contained in:
Cina Saffary 2023-08-30 09:04:22 -05:00 committed by GitHub
commit 47d4afdbd8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 138 additions and 166 deletions

View file

@ -0,0 +1,5 @@
---
"wrangler-action": patch
---
Refactored error handling to stop execution when action fails. Previously, the action would continue executing to completion if one of the steps encountered an error. Fixes #160.

View file

@ -51,6 +51,7 @@ jobs:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
environment: dev
preCommands: npx wrangler deploy --env dev # https://github.com/cloudflare/wrangler-action/issues/162
secrets: |
SECRET1
SECRET2

View file

@ -1 +0,0 @@
process.env.INPUT_QUIET ??= "false";

View file

@ -25,8 +25,8 @@
"scripts": {
"build": "npx ncc build ./src/index.ts && mv ./dist/index.js ./dist/index.mjs",
"test": "vitest",
"format": "prettier --write . --ignore-path ./dist/**",
"check": "prettier --check . --ignore-path ./dist/**"
"format": "prettier --write .",
"check": "prettier --check ."
},
"dependencies": {
"@actions/core": "^1.10.0"

View file

@ -9,8 +9,7 @@ import {
getBooleanInput,
} from "@actions/core";
import { execSync, exec } from "node:child_process";
import { existsSync } from "node:fs";
import * as path from "node:path";
import { checkWorkingDirectory, getNpxCmd, semverCompare } from "./utils";
import * as util from "node:util";
const execAsync = util.promisify(exec);
@ -31,10 +30,6 @@ const config = {
QUIET_MODE: getBooleanInput("quiet"),
} as const;
function getNpxCmd() {
return process.env.RUNNER_OS === "Windows" ? "npx.cmd" : "npx";
}
function info(message: string, bypass?: boolean): void {
if (!config.QUIET_MODE || bypass) {
originalInfo(message);
@ -59,27 +54,8 @@ function endGroup(): void {
}
}
/**
* A helper function to compare two semver versions. If the second arg is greater than the first arg, it returns true.
*/
function semverCompare(version1: string, version2: string) {
if (version2 === "latest") return true;
const version1Parts = version1.split(".");
const version2Parts = version2.split(".");
for (const version1Part of version1Parts) {
const version2Part = version2Parts.shift();
if (version1Part !== version2Part && version2Part) {
return version1Part < version2Part ? true : false;
}
}
return false;
}
async function main() {
try {
installWrangler();
authenticationSetup();
await execCommands(getMultilineInput("preCommands"), "pre");
@ -87,6 +63,10 @@ async function main() {
await wranglerCommands();
await execCommands(getMultilineInput("postCommands"), "post");
info("🏁 Wrangler Action completed", true);
} catch (err: unknown) {
err instanceof Error && error(err.message);
setFailed("🚨 Action failed");
}
}
async function runProcess(
@ -103,29 +83,14 @@ async function runProcess(
} catch (err: any) {
err.stdout && info(err.stdout.toString());
err.stderr && error(err.stderr.toString(), true);
throw err;
}
}
function checkWorkingDirectory(workingDirectory = ".") {
try {
const normalizedPath = path.normalize(workingDirectory);
if (existsSync(normalizedPath)) {
return normalizedPath;
} else {
setFailed(`🚨 Directory ${workingDirectory} does not exist.`);
}
} catch (error) {
setFailed(
`🚨 While checking/creating directory ${workingDirectory} received ${error}`,
);
throw new Error(`\`${command}\` returned non-zero exit code.`);
}
}
function installWrangler() {
if (config["WRANGLER_VERSION"].startsWith("1")) {
setFailed(
`🚨 Wrangler v1 is no longer supported by this action. Please use major version 2 or greater`,
throw new Error(
`Wrangler v1 is no longer supported by this action. Please use major version 2 or greater`,
);
}
startGroup("📥 Installing Wrangler");
@ -145,8 +110,9 @@ async function execCommands(commands: string[], cmdType: string) {
if (!commands.length) {
return;
}
startGroup(`🚀 Running ${cmdType}Commands`);
startGroup(`🚀 Running ${cmdType}Commands`);
try {
const arrPromises = commands.map(async (command) => {
const cmd = command.startsWith("wrangler")
? `${getNpxCmd()} ${command}`
@ -160,12 +126,10 @@ async function execCommands(commands: string[], cmdType: string) {
});
});
await Promise.all(arrPromises).catch((result) => {
result.stdout && info(result.stdout.toString());
result.stderr && error(result.stderr.toString(), true);
setFailed(`🚨 ${cmdType}Commands failed`);
});
await Promise.all(arrPromises);
} finally {
endGroup();
}
}
/**
@ -173,12 +137,12 @@ async function execCommands(commands: string[], cmdType: string) {
*/
function getSecret(secret: string) {
if (!secret) {
setFailed("No secret provided");
throw new Error("Secret name cannot be blank.");
}
const value = process.env[secret];
if (!value) {
setFailed(`Secret ${secret} not found`);
throw new Error(`Value for secret ${secret} not found.`);
}
return value;
@ -189,7 +153,6 @@ async function legacyUploadSecrets(
environment?: string,
workingDirectory?: string,
) {
try {
const arrPromises = secrets
.map((secret) => {
const command = `echo ${getSecret(
@ -206,9 +169,6 @@ async function legacyUploadSecrets(
);
await Promise.all(arrPromises);
} catch {
setFailed(`🚨 Error uploading secrets`);
}
}
async function uploadSecrets() {
@ -219,9 +179,10 @@ async function uploadSecrets() {
if (!secrets.length) {
return;
}
try {
startGroup("🔑 Uploading Secrets");
startGroup("🔑 Uploading secrets...");
try {
if (semverCompare(config["WRANGLER_VERSION"], "3.4.0"))
return legacyUploadSecrets(secrets, environment, workingDirectory);
@ -244,9 +205,11 @@ async function uploadSecrets() {
env: process.env,
stdio: "ignore",
});
info(`✅ Uploaded secrets`);
} catch {
setFailed(`🚨 Error uploading secrets`);
} catch (err) {
error(`❌ Upload failed`);
throw new Error(`Failed to upload secrets.`);
} finally {
endGroup();
}
@ -258,7 +221,7 @@ function getVarArgs() {
if (process.env[envVar] && process.env[envVar]?.length !== 0) {
return `${envVar}:${process.env[envVar]!}`;
} else {
setFailed(`🚨 ${envVar} not found in variables.`);
throw new Error(`Value for var ${envVar} not found in environment.`);
}
});
@ -267,6 +230,7 @@ function getVarArgs() {
async function wranglerCommands() {
startGroup("🚀 Running Wrangler Commands");
try {
const commands = config["COMMANDS"];
const environment = config["ENVIRONMENT"];
@ -298,16 +262,13 @@ async function wranglerCommands() {
});
});
await Promise.all(arrPromises).catch((result) => {
result.stdout && info(result.stdout.toString());
result.stderr && error(result.stderr.toString());
setFailed(`🚨 Command failed`);
});
await Promise.all(arrPromises);
} finally {
endGroup();
}
}
main().catch(() => setFailed("🚨 Action failed"));
main();
export {
wranglerCommands,
@ -315,7 +276,4 @@ export {
uploadSecrets,
authenticationSetup,
installWrangler,
checkWorkingDirectory,
getNpxCmd,
semverCompare,
};

View file

@ -1,18 +1,7 @@
import { expect, test, describe } from "vitest";
import { checkWorkingDirectory, getNpxCmd, semverCompare } from "./index";
import { checkWorkingDirectory, getNpxCmd, semverCompare } from "./utils";
import path from "node:path";
const config = {
WRANGLER_VERSION: "mockVersion",
secrets: ["mockSercret", "mockSecretAgain"],
workingDirectory: "./mockWorkingDirectory",
CLOUDFLARE_API_TOKEN: "mockAPIToken",
CLOUDFLARE_ACCOUNT_ID: "mockAccountID",
ENVIRONMENT: undefined,
VARS: ["mockVar", "mockVarAgain"],
COMMANDS: ["mockCommand", "mockCommandAgain"],
};
test("getNpxCmd ", async () => {
process.env.RUNNER_OS = "Windows";
expect(getNpxCmd()).toBe("npx.cmd");
@ -47,18 +36,10 @@ describe("checkWorkingDirectory", () => {
});
test("should fail if the directory does not exist", () => {
try {
checkWorkingDirectory("/does/not/exist");
} catch (error) {
expect(error.message).toMatchInlineSnapshot();
}
});
test("should fail if an error occurs while checking/creating the directory", () => {
try {
checkWorkingDirectory("/does/not/exist");
} catch (error) {
expect(error.message).toMatchInlineSnapshot();
}
expect(() =>
checkWorkingDirectory("/does/not/exist"),
).toThrowErrorMatchingInlineSnapshot(
'"Directory /does/not/exist does not exist."',
);
});
});

35
src/utils.ts Normal file
View file

@ -0,0 +1,35 @@
import { existsSync } from "node:fs";
import * as path from "node:path";
export function getNpxCmd() {
return process.env.RUNNER_OS === "Windows" ? "npx.cmd" : "npx";
}
/**
* A helper function to compare two semver versions. If the second arg is greater than the first arg, it returns true.
*/
export function semverCompare(version1: string, version2: string) {
if (version2 === "latest") return true;
const version1Parts = version1.split(".");
const version2Parts = version2.split(".");
for (const version1Part of version1Parts) {
const version2Part = version2Parts.shift();
if (version1Part !== version2Part && version2Part) {
return version1Part < version2Part ? true : false;
}
}
return false;
}
export function checkWorkingDirectory(workingDirectory = ".") {
const normalizedPath = path.normalize(workingDirectory);
if (existsSync(normalizedPath)) {
return normalizedPath;
} else {
throw new Error(`Directory ${workingDirectory} does not exist.`);
}
}

View file

@ -9,12 +9,12 @@
"isolatedModules": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "NodeNext",
"moduleResolution": "Bundler",
"rootDir": "./src",
"outDir": "./dist",
"lib": ["ESNext"],
"types": ["node", "@cloudflare/workers-types"]
},
"exclude": ["node_modules", "**/*.test.ts"],
"include": ["src/index.ts"]
"include": ["src"]
}

View file

@ -1,7 +0,0 @@
import { defineConfig } from "vitest/dist/config";
export default defineConfig({
test: {
setupFiles: ["./action-env-setup.ts"],
},
});