diff --git a/.changeset/stale-spiders-turn.md b/.changeset/stale-spiders-turn.md new file mode 100644 index 0000000..d682a4b --- /dev/null +++ b/.changeset/stale-spiders-turn.md @@ -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. diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 4fde508..2050e36 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -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 diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml index e129dab..8b469dc 100644 --- a/.github/workflows/issues.yml +++ b/.github/workflows/issues.yml @@ -13,4 +13,4 @@ jobs: - uses: actions/add-to-project@v0.5.0 with: project-url: https://github.com/orgs/cloudflare/projects/1 - github-token: ${{ secrets.GH_ACCESS_TOKEN }} \ No newline at end of file + github-token: ${{ secrets.GH_ACCESS_TOKEN }} diff --git a/action-env-setup.ts b/action-env-setup.ts deleted file mode 100644 index 75c7e92..0000000 --- a/action-env-setup.ts +++ /dev/null @@ -1 +0,0 @@ -process.env.INPUT_QUIET ??= "false"; diff --git a/package.json b/package.json index 9e1179d..ceb9feb 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/src/index.ts b/src/index.ts index f21f3a8..927c727 100755 --- a/src/index.ts +++ b/src/index.ts @@ -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,34 +54,19 @@ 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() { - installWrangler(); - authenticationSetup(); - await execCommands(getMultilineInput("preCommands"), "pre"); - await uploadSecrets(); - await wranglerCommands(); - await execCommands(getMultilineInput("postCommands"), "post"); - info("🏁 Wrangler Action completed", true); + try { + installWrangler(); + authenticationSetup(); + await execCommands(getMultilineInput("preCommands"), "pre"); + await uploadSecrets(); + 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,27 +110,26 @@ async function execCommands(commands: string[], cmdType: string) { if (!commands.length) { return; } + startGroup(`🚀 Running ${cmdType}Commands`); + try { + const arrPromises = commands.map(async (command) => { + const cmd = command.startsWith("wrangler") + ? `${getNpxCmd()} ${command}` + : command; - const arrPromises = commands.map(async (command) => { - const cmd = command.startsWith("wrangler") - ? `${getNpxCmd()} ${command}` - : command; + info(`🚀 Executing command: ${cmd}`); - info(`🚀 Executing command: ${cmd}`); - - return await runProcess(cmd, { - cwd: config["workingDirectory"], - env: process.env, + return await runProcess(cmd, { + cwd: config["workingDirectory"], + env: process.env, + }); }); - }); - await Promise.all(arrPromises).catch((result) => { - result.stdout && info(result.stdout.toString()); - result.stderr && error(result.stderr.toString(), true); - setFailed(`🚨 ${cmdType}Commands failed`); - }); - endGroup(); + 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,26 +153,22 @@ async function legacyUploadSecrets( environment?: string, workingDirectory?: string, ) { - try { - const arrPromises = secrets - .map((secret) => { - const command = `echo ${getSecret( - secret, - )} | ${getNpxCmd()} wrangler secret put ${secret}`; - return environment ? command.concat(` --env ${environment}`) : command; - }) - .map( - async (command) => - await execAsync(command, { - cwd: workingDirectory, - env: process.env, - }), - ); + const arrPromises = secrets + .map((secret) => { + const command = `echo ${getSecret( + secret, + )} | ${getNpxCmd()} wrangler secret put ${secret}`; + return environment ? command.concat(` --env ${environment}`) : command; + }) + .map( + async (command) => + await execAsync(command, { + cwd: workingDirectory, + env: process.env, + }), + ); - await Promise.all(arrPromises); - } catch { - setFailed(`🚨 Error uploading secrets`); - } + await Promise.all(arrPromises); } 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,47 +230,45 @@ function getVarArgs() { async function wranglerCommands() { startGroup("🚀 Running Wrangler Commands"); - const commands = config["COMMANDS"]; - const environment = config["ENVIRONMENT"]; + try { + const commands = config["COMMANDS"]; + const environment = config["ENVIRONMENT"]; - if (!commands.length) { - const wranglerVersion = config["WRANGLER_VERSION"]; - const deployCommand = semverCompare("2.20.0", wranglerVersion) - ? "deploy" - : "publish"; - commands.push(deployCommand); - } - - const arrPromises = commands.map(async (command) => { - if (environment.length > 0 && !command.includes(`--env`)) { - command = command.concat(` --env ${environment}`); + if (!commands.length) { + const wranglerVersion = config["WRANGLER_VERSION"]; + const deployCommand = semverCompare("2.20.0", wranglerVersion) + ? "deploy" + : "publish"; + commands.push(deployCommand); } - const cmd = `${getNpxCmd()} wrangler ${command} ${ - (command.startsWith("deploy") || command.startsWith("publish")) && - !command.includes(`--var`) - ? getVarArgs() - : "" - }`.trim(); + const arrPromises = commands.map(async (command) => { + if (environment.length > 0 && !command.includes(`--env`)) { + command = command.concat(` --env ${environment}`); + } - info(`🚀 Executing command: ${cmd}`); + const cmd = `${getNpxCmd()} wrangler ${command} ${ + (command.startsWith("deploy") || command.startsWith("publish")) && + !command.includes(`--var`) + ? getVarArgs() + : "" + }`.trim(); - return await runProcess(cmd, { - cwd: config["workingDirectory"], - env: process.env, + info(`🚀 Executing command: ${cmd}`); + + return await runProcess(cmd, { + cwd: config["workingDirectory"], + env: process.env, + }); }); - }); - await Promise.all(arrPromises).catch((result) => { - result.stdout && info(result.stdout.toString()); - result.stderr && error(result.stderr.toString()); - setFailed(`🚨 Command failed`); - }); - - endGroup(); + 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, }; diff --git a/src/index.test.ts b/src/utils.test.ts similarity index 63% rename from src/index.test.ts rename to src/utils.test.ts index f698741..cfe65cf 100644 --- a/src/index.test.ts +++ b/src/utils.test.ts @@ -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."', + ); }); }); diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..ab6d0d2 --- /dev/null +++ b/src/utils.ts @@ -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.`); + } +} diff --git a/tsconfig.json b/tsconfig.json index 99fbbbf..03e6f29 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -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"] } diff --git a/vitest.config.ts b/vitest.config.ts deleted file mode 100644 index d5d5cd3..0000000 --- a/vitest.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from "vitest/dist/config"; - -export default defineConfig({ - test: { - setupFiles: ["./action-env-setup.ts"], - }, -});