Compare commits

..

1 Commits

Author SHA1 Message Date
Jacob Hands 0ca18d5d22 fix: Use wrangler secret put instead of deprecated secret:bulk command
`wrangler secret:bulk` is deprecated and will be removed in a future
version. This also improves logging in cases where a secret is failing
to upload because an environment variable with the same name already
exists (see: #240).
2024-11-18 09:33:26 -06:00
97 changed files with 5007 additions and 39223 deletions
-5
View File
@@ -1,5 +0,0 @@
---
"wrangler-action": minor
---
Add GitHub deployments and job summaries for parity with pages-action
+7
View File
@@ -0,0 +1,7 @@
---
"wrangler-action": patch
---
fix: Use wrangler secret put instead of deprecated secret:bulk command
`wrangler secret:bulk` is deprecated and will be removed in a future version. This also improves logging in cases where a secret is failing to upload because an environment variable with the same name already exists (see: #240).
+13 -13
View File
@@ -30,7 +30,7 @@ jobs:
- name: Only build app
uses: ./
with:
workingDirectory: "./src/test/fixtures/only-build"
workingDirectory: "./test/only-build"
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --dry-run
@@ -39,7 +39,7 @@ jobs:
uses: ./
with:
quiet: true
workingDirectory: "./src/test/fixtures/build-quiet"
workingDirectory: "./test/build-quiet"
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --dry-run
@@ -47,7 +47,7 @@ jobs:
- name: Environment support
uses: ./
with:
workingDirectory: "./src/test/fixtures/environment"
workingDirectory: "./test/environment"
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
environment: dev
@@ -65,7 +65,7 @@ jobs:
uses: ./
with:
wranglerVersion: "2.20.0"
workingDirectory: "./src/test/fixtures/secrets-v2"
workingDirectory: "./test/secrets-v2"
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
secrets: |
@@ -82,7 +82,7 @@ jobs:
- name: Deploy app secrets w/ default version
uses: ./
with:
workingDirectory: "./src/test/fixtures/secrets-default"
workingDirectory: "./test/secrets-default"
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
secrets: |
@@ -99,7 +99,7 @@ jobs:
- name: Clean Up Deployed Workers
uses: ./
with:
workingDirectory: "./src/test/fixtures/secrets-default"
workingDirectory: "./test/secrets-default"
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: delete --name wrangler-action-test-secrets-v2 --force
@@ -109,7 +109,7 @@ jobs:
- name: Support packageManager variable
uses: ./
with:
workingDirectory: "./src/test/fixtures/specify-package-manager"
workingDirectory: "./test/specify-package-manager"
packageManager: "npm"
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
@@ -118,7 +118,7 @@ jobs:
- name: Support unspecified packageManager with no lockfile
uses: ./
with:
workingDirectory: "./src/test/fixtures/unspecified-package-manager"
workingDirectory: "./test/unspecified-package-manager"
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --dry-run
@@ -126,7 +126,7 @@ jobs:
- name: Support npm package manager
uses: ./
with:
workingDirectory: "./src/test/fixtures/npm"
workingDirectory: "./test/npm"
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --dry-run
@@ -137,7 +137,7 @@ jobs:
- name: Support yarn package manager
uses: ./
with:
workingDirectory: "./src/test/fixtures/yarn"
workingDirectory: "./test/yarn"
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --dry-run
@@ -148,18 +148,18 @@ jobs:
- name: Support pnpm package manager
uses: ./
with:
workingDirectory: "./src/test/fixtures/pnpm"
workingDirectory: "./test/pnpm"
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --dry-run
- name: Change directory to pre-installed-wrangler and install dependencies
run: |
cd ./src/test/fixtures/pre-installed-wrangler
cd ./test/pre-installed-wrangler
npm install
- name: Support pre-installed wrangler
uses: ./
with:
workingDirectory: "./src/test/fixtures/pre-installed-wrangler"
workingDirectory: "./test/pre-installed-wrangler"
command: action-test
+1
View File
@@ -1,3 +1,4 @@
dist
.idea
.vscode
+1 -7
View File
@@ -76,7 +76,7 @@ jobs:
workingDirectory: "subfoldername"
```
[Worker secrets](https://developers.cloudflare.com/workers/tooling/wrangler/secrets/) can optionally be passed in via `secrets` as a string of names separated by newlines. Each secret name must match the name of an environment variable specified in the `env` field. This creates or replaces the value for the Worker secret using the `wrangler secret put` command. It's also possible to specify worker environment using environment parameter.
[Worker secrets](https://developers.cloudflare.com/workers/tooling/wrangler/secrets/) can optionally be passed in via `secrets` as a string of names separated by newlines. Each secret name must match the name of an environment variable specified in the `env` field. This creates or replaces the value for the Worker secret using the `wrangler secret put` command.
```yaml
jobs:
@@ -85,7 +85,6 @@ jobs:
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
environment: production
secrets: |
SECRET1
SECRET2
@@ -173,9 +172,6 @@ jobs:
deploy:
runs-on: ubuntu-latest
name: Deploy
permissions:
contents: read
deployments: write
steps:
- uses: actions/checkout@v4
- name: Deploy
@@ -184,8 +180,6 @@ jobs:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy YOUR_DIST_FOLDER --project-name=example
# Optional: Enable this if you want to have GitHub Deployments triggered
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
```
### Deploying on a schedule
-3
View File
@@ -44,9 +44,6 @@ inputs:
packageManager:
description: "The package manager you'd like to use to install and run wrangler. If not specified, the preferred package manager will be inferred based on the presence of a lockfile or fallback to using npm if no lockfile is found. Valid values are `npm` | `pnpm` | `yarn` | `bun`."
required: false
gitHubToken:
description: "GitHub Token"
required: false
outputs:
command-output:
description: "The output of the Wrangler command (comes from stdout)"
-6
View File
@@ -1,6 +0,0 @@
import { exec as _childProcessExec } from "node:child_process";
export { exec } from "@actions/exec";
declare const childProcessExec: typeof _childProcessExec.__promisify__;
export declare function execShell(command: string, { silent, ...options }?: Parameters<typeof childProcessExec>[1] & {
silent?: boolean;
}): Promise<number | null>;
-1
View File
@@ -1 +0,0 @@
export {};
-37255
View File
File diff suppressed because one or more lines are too long
-3
View File
@@ -1,3 +0,0 @@
{
"type": "module"
}
-24
View File
@@ -1,24 +0,0 @@
export interface PackageManager {
install: string;
exec: string;
execNoInstall: string;
}
export declare function getPackageManager(name: string, { workingDirectory }?: {
workingDirectory?: string;
}): {
readonly install: "npm i";
readonly exec: "npx";
readonly execNoInstall: "npx --no-install";
} | {
readonly install: "yarn add";
readonly exec: "yarn";
readonly execNoInstall: "yarn";
} | {
readonly install: "pnpm add";
readonly exec: "pnpm exec";
readonly execNoInstall: "pnpm exec";
} | {
readonly install: "bun i";
readonly exec: "bunx";
readonly execNoInstall: "bun run";
};
-1
View File
@@ -1 +0,0 @@
export {};
-23
View File
@@ -1,23 +0,0 @@
import { getOctokit } from "@actions/github";
import { OutputEntryPagesDeployment } from "../wranglerArtifactManager";
import { WranglerActionConfig } from "../wranglerAction";
type Octokit = ReturnType<typeof getOctokit>;
export declare function createGitHubDeployment({ config, octokit, productionBranch, environment, deploymentId, projectName, deploymentUrl, }: {
config: WranglerActionConfig;
octokit: Octokit;
productionBranch: string;
environment: string;
deploymentId: string | null;
projectName: string;
deploymentUrl?: string;
}): Promise<void>;
export declare function createJobSummary({ commitHash, deploymentUrl, aliasUrl, }: {
commitHash: string;
deploymentUrl?: string;
aliasUrl?: string;
}): Promise<void>;
/**
* Create github deployment, if GITHUB_TOKEN is present in config
*/
export declare function createGitHubDeploymentAndJobSummary(config: WranglerActionConfig, pagesArtifactFields: OutputEntryPagesDeployment): Promise<void>;
export {};
-1
View File
@@ -1 +0,0 @@
export {};
-8
View File
@@ -1,8 +0,0 @@
type Env = {
SECRET1?: string;
SECRET2?: string;
};
declare const _default: {
fetch(request: Request, env: Env): Response;
};
export default _default;
-8
View File
@@ -1,8 +0,0 @@
type Env = {
SECRET1?: string;
SECRET2?: string;
};
declare const _default: {
fetch(request: Request, env: Env): Response;
};
export default _default;
-8
View File
@@ -1,8 +0,0 @@
type Env = {
SECRET1?: string;
SECRET2?: string;
};
declare const _default: {
fetch(request: Request, env: Env): Response;
};
export default _default;
-8
View File
@@ -1,8 +0,0 @@
type Env = {
SECRET1?: string;
SECRET2?: string;
};
declare const _default: {
fetch(request: Request, env: Env): Response;
};
export default _default;
-8
View File
@@ -1,8 +0,0 @@
type Env = {
SECRET1?: string;
SECRET2?: string;
};
declare const _default: {
fetch(request: Request, env: Env): Response;
};
export default _default;
-8
View File
@@ -1,8 +0,0 @@
type Env = {
SECRET1?: string;
SECRET2?: string;
};
declare const _default: {
fetch(request: Request, env: Env): Response;
};
export default _default;
-8
View File
@@ -1,8 +0,0 @@
type Env = {
SECRET1?: string;
SECRET2?: string;
};
declare const _default: {
fetch(request: Request, env: Env): Response;
};
export default _default;
-8
View File
@@ -1,8 +0,0 @@
type Env = {
SECRET1?: string;
SECRET2?: string;
};
declare const _default: {
fetch(request: Request, env: Env): Response;
};
export default _default;
-8
View File
@@ -1,8 +0,0 @@
type Env = {
SECRET1?: string;
SECRET2?: string;
};
declare const _default: {
fetch(request: Request, env: Env): Response;
};
export default _default;
-2
View File
@@ -1,2 +0,0 @@
declare const _default: {};
export default _default;
@@ -1,2 +0,0 @@
declare const _default: {};
export default _default;
-8
View File
@@ -1,8 +0,0 @@
type Env = {
SECRET1?: string;
SECRET2?: string;
};
declare const _default: {
fetch(request: Request, env: Env): Response;
};
export default _default;
-6
View File
@@ -1,6 +0,0 @@
export declare function mockGithubDeployments({ githubUser, githubRepoName, }: {
githubUser: string;
githubRepoName: string;
}): {
handlers: import("msw").HttpHandler[];
};
-4
View File
@@ -1,4 +0,0 @@
import { WranglerActionConfig } from "../wranglerAction";
export declare function getTestConfig({ config, }?: {
config?: Partial<WranglerActionConfig>;
}): WranglerActionConfig;
-8
View File
@@ -1,8 +0,0 @@
import { WranglerActionConfig } from "./wranglerAction";
/**
* A helper function to compare two semver versions. If the second arg is greater than the first arg, it returns true.
*/
export declare function semverCompare(version1: string, version2: string): boolean;
export declare function checkWorkingDirectory(workingDirectory?: string): string;
export declare function info(config: WranglerActionConfig, message: string, bypass?: boolean): void;
export declare function error(config: WranglerActionConfig, message: string, bypass?: boolean): void;
-1
View File
@@ -1 +0,0 @@
export {};
-54
View File
@@ -1,54 +0,0 @@
import { z } from "zod";
import { PackageManager } from "./packageManagers";
import { info } from "./utils";
export type WranglerActionConfig = z.infer<typeof wranglerActionConfig>;
export declare const wranglerActionConfig: z.ZodObject<{
WRANGLER_VERSION: z.ZodString;
didUserProvideWranglerVersion: z.ZodBoolean;
secrets: z.ZodArray<z.ZodString, "many">;
workingDirectory: z.ZodString;
CLOUDFLARE_API_TOKEN: z.ZodString;
CLOUDFLARE_ACCOUNT_ID: z.ZodString;
ENVIRONMENT: z.ZodString;
VARS: z.ZodArray<z.ZodString, "many">;
COMMANDS: z.ZodArray<z.ZodString, "many">;
QUIET_MODE: z.ZodBoolean;
PACKAGE_MANAGER: z.ZodString;
WRANGLER_OUTPUT_DIR: z.ZodString;
GITHUB_TOKEN: z.ZodString;
}, "strip", z.ZodTypeAny, {
WRANGLER_VERSION: string;
didUserProvideWranglerVersion: boolean;
secrets: string[];
workingDirectory: string;
CLOUDFLARE_API_TOKEN: string;
CLOUDFLARE_ACCOUNT_ID: string;
ENVIRONMENT: string;
VARS: string[];
COMMANDS: string[];
QUIET_MODE: boolean;
PACKAGE_MANAGER: string;
WRANGLER_OUTPUT_DIR: string;
GITHUB_TOKEN: string;
}, {
WRANGLER_VERSION: string;
didUserProvideWranglerVersion: boolean;
secrets: string[];
workingDirectory: string;
CLOUDFLARE_API_TOKEN: string;
CLOUDFLARE_ACCOUNT_ID: string;
ENVIRONMENT: string;
VARS: string[];
COMMANDS: string[];
QUIET_MODE: boolean;
PACKAGE_MANAGER: string;
WRANGLER_OUTPUT_DIR: string;
GITHUB_TOKEN: string;
}>;
declare function main(config: WranglerActionConfig, packageManager: PackageManager): Promise<void>;
declare function installWrangler(config: WranglerActionConfig, packageManager: PackageManager): Promise<void>;
declare function authenticationSetup(config: WranglerActionConfig): void;
declare function execCommands(config: WranglerActionConfig, packageManager: PackageManager, commands: string[], cmdType: string): Promise<void>;
declare function uploadSecrets(config: WranglerActionConfig, packageManager: PackageManager): Promise<void>;
declare function wranglerCommands(config: WranglerActionConfig, packageManager: PackageManager): Promise<void>;
export { authenticationSetup, execCommands, info, installWrangler, main, uploadSecrets, wranglerCommands, };
-1
View File
@@ -1 +0,0 @@
export {};
-103
View File
@@ -1,103 +0,0 @@
import { z } from "zod";
declare const OutputEntryPagesDeployment: z.ZodObject<z.objectUtil.extendShape<{
version: z.ZodLiteral<1>;
type: z.ZodString;
}, {
type: z.ZodLiteral<"pages-deploy-detailed">;
pages_project: z.ZodNullable<z.ZodString>;
deployment_id: z.ZodNullable<z.ZodString>;
url: z.ZodOptional<z.ZodString>;
alias: z.ZodOptional<z.ZodString>;
environment: z.ZodEnum<["production", "preview"]>;
production_branch: z.ZodOptional<z.ZodString>;
stages: z.ZodOptional<z.ZodArray<z.ZodObject<{
name: z.ZodEnum<["queued", "initialize", "clone_repo", "build", "deploy"]>;
status: z.ZodEnum<["idle", "active", "canceled", "success", "failure", "skipped"]>;
started_on: z.ZodNullable<z.ZodString>;
ended_on: z.ZodNullable<z.ZodString>;
}, "strip", z.ZodTypeAny, {
status: "idle" | "active" | "canceled" | "success" | "failure" | "skipped";
name: "deploy" | "queued" | "initialize" | "clone_repo" | "build";
started_on: string | null;
ended_on: string | null;
}, {
status: "idle" | "active" | "canceled" | "success" | "failure" | "skipped";
name: "deploy" | "queued" | "initialize" | "clone_repo" | "build";
started_on: string | null;
ended_on: string | null;
}>, "many">>;
deployment_trigger: z.ZodOptional<z.ZodObject<{
metadata: z.ZodObject<{
/** Commit hash of the deployment trigger metadata for the pages project */
commit_hash: z.ZodString;
}, "strip", z.ZodTypeAny, {
commit_hash: string;
}, {
commit_hash: string;
}>;
}, "strip", z.ZodTypeAny, {
metadata: {
commit_hash: string;
};
}, {
metadata: {
commit_hash: string;
};
}>>;
}>, "strip", z.ZodTypeAny, {
type: "pages-deploy-detailed";
environment: "production" | "preview";
version: 1;
pages_project: string | null;
deployment_id: string | null;
url?: string | undefined;
alias?: string | undefined;
production_branch?: string | undefined;
stages?: {
status: "idle" | "active" | "canceled" | "success" | "failure" | "skipped";
name: "deploy" | "queued" | "initialize" | "clone_repo" | "build";
started_on: string | null;
ended_on: string | null;
}[] | undefined;
deployment_trigger?: {
metadata: {
commit_hash: string;
};
} | undefined;
}, {
type: "pages-deploy-detailed";
environment: "production" | "preview";
version: 1;
pages_project: string | null;
deployment_id: string | null;
url?: string | undefined;
alias?: string | undefined;
production_branch?: string | undefined;
stages?: {
status: "idle" | "active" | "canceled" | "success" | "failure" | "skipped";
name: "deploy" | "queued" | "initialize" | "clone_repo" | "build";
started_on: string | null;
ended_on: string | null;
}[] | undefined;
deployment_trigger?: {
metadata: {
commit_hash: string;
};
} | undefined;
}>;
export type OutputEntryPagesDeployment = z.infer<typeof OutputEntryPagesDeployment>;
/**
* Parses file names in a directory to find wrangler artifact files
*
* @param artifactDirectory
* @returns All artifact files from the directory
*/
export declare function getWranglerArtifacts(artifactDirectory: string): Promise<string[]>;
/**
* Searches for detailed wrangler output from a pages deploy
*
* @param artifactDirectory
* @returns The first pages-output-detailed found within a wrangler artifact directory
*/
export declare function getDetailedPagesDeployOutput(artifactDirectory: string): Promise<OutputEntryPagesDeployment | null>;
export {};
-1
View File
@@ -1 +0,0 @@
export {};
+2813 -1181
View File
File diff suppressed because it is too large Load Diff
+11 -15
View File
@@ -29,24 +29,20 @@
"check": "prettier --check ."
},
"dependencies": {
"@actions/core": "^1.11.1",
"@actions/core": "^1.10.1",
"@actions/exec": "^1.1.1",
"@actions/github": "^6.0.0",
"zod": "^3.23.8"
},
"devDependencies": {
"@changesets/changelog-github": "^0.5.0",
"@changesets/cli": "^2.27.9",
"@cloudflare/workers-types": "^4.20241022.0",
"@types/mock-fs": "^4.13.4",
"@types/node": "^22.9.0",
"@types/semver": "^7.5.8",
"@vercel/ncc": "^0.38.2",
"mock-fs": "^5.4.1",
"msw": "^2.6.4",
"prettier": "^3.3.3",
"semver": "^7.6.3",
"typescript": "^5.6.3",
"vitest": "^2.1.4"
"@changesets/changelog-github": "^0.4.8",
"@changesets/cli": "^2.26.2",
"@cloudflare/workers-types": "^4.20231121.0",
"@types/node": "^20.10.4",
"@vercel/ncc": "^0.38.1",
"prettier": "^3.1.0",
"mock-fs": "^5.4.0",
"semver": "^7.5.4",
"typescript": "^5.3.3",
"vitest": "^1.0.3"
}
}
+2013
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -43,7 +43,7 @@ export async function execShell(
await promise;
return child.exitCode;
} catch (err) {
} catch (err: any) {
if (isExecAsyncException(err)) {
process.stderr.write(err.stderr);
throw new Error(`Process failed with exit code ${err.code}`);
-1
View File
@@ -26,7 +26,6 @@ const config: WranglerActionConfig = {
tmpdir(),
`wranglerArtifacts-${crypto.randomUUID()}`,
)}`,
GITHUB_TOKEN: getInput("gitHubToken", { required: false }),
} as const;
const packageManager = getPackageManager(config.PACKAGE_MANAGER, {
+17 -24
View File
@@ -3,9 +3,8 @@ import { getPackageManager } from "./packageManagers";
describe("getPackageManager", () => {
test("should use provided value instead of inferring from lockfile", () => {
expect(
getPackageManager("npm", { workingDirectory: "src/test/fixtures/npm" }),
).toMatchInlineSnapshot(`
expect(getPackageManager("npm", { workingDirectory: "test/npm" }))
.toMatchInlineSnapshot(`
{
"exec": "npx",
"execNoInstall": "npx --no-install",
@@ -13,9 +12,8 @@ describe("getPackageManager", () => {
}
`);
expect(
getPackageManager("yarn", { workingDirectory: "src/test/fixtures/npm" }),
).toMatchInlineSnapshot(`
expect(getPackageManager("yarn", { workingDirectory: "test/npm" }))
.toMatchInlineSnapshot(`
{
"exec": "yarn",
"execNoInstall": "yarn",
@@ -23,9 +21,8 @@ describe("getPackageManager", () => {
}
`);
expect(
getPackageManager("pnpm", { workingDirectory: "src/test/fixtures/npm" }),
).toMatchInlineSnapshot(`
expect(getPackageManager("pnpm", { workingDirectory: "test/npm" }))
.toMatchInlineSnapshot(`
{
"exec": "pnpm exec",
"execNoInstall": "pnpm exec",
@@ -33,9 +30,8 @@ describe("getPackageManager", () => {
}
`);
expect(
getPackageManager("bun", { workingDirectory: "src/test/fixtures/bun" }),
).toMatchInlineSnapshot(`
expect(getPackageManager("bun", { workingDirectory: "test/bun" }))
.toMatchInlineSnapshot(`
{
"exec": "bunx",
"execNoInstall": "bun run",
@@ -45,7 +41,7 @@ describe("getPackageManager", () => {
});
test("should use npm if no value provided and package-lock.json exists", () => {
expect(getPackageManager("", { workingDirectory: "src/test/fixtures/npm" }))
expect(getPackageManager("", { workingDirectory: "test/npm" }))
.toMatchInlineSnapshot(`
{
"exec": "npx",
@@ -56,9 +52,8 @@ describe("getPackageManager", () => {
});
test("should use yarn if no value provided and yarn.lock exists", () => {
expect(
getPackageManager("", { workingDirectory: "src/test/fixtures/yarn" }),
).toMatchInlineSnapshot(`
expect(getPackageManager("", { workingDirectory: "test/yarn" }))
.toMatchInlineSnapshot(`
{
"exec": "yarn",
"execNoInstall": "yarn",
@@ -68,9 +63,8 @@ describe("getPackageManager", () => {
});
test("should use pnpm if no value provided and pnpm-lock.yaml exists", () => {
expect(
getPackageManager("", { workingDirectory: "src/test/fixtures/pnpm" }),
).toMatchInlineSnapshot(`
expect(getPackageManager("", { workingDirectory: "test/pnpm" }))
.toMatchInlineSnapshot(`
{
"exec": "pnpm exec",
"execNoInstall": "pnpm exec",
@@ -80,7 +74,7 @@ describe("getPackageManager", () => {
});
test("should use bun if no value provided and bun.lockb exists", () => {
expect(getPackageManager("", { workingDirectory: "src/test/fixtures/bun" }))
expect(getPackageManager("", { workingDirectory: "test/bun" }))
.toMatchInlineSnapshot(`
{
"exec": "bunx",
@@ -91,9 +85,8 @@ describe("getPackageManager", () => {
});
test("should use npm if no value provided and no lockfile is present", () => {
expect(
getPackageManager("", { workingDirectory: "src/test/fixtures/empty" }),
).toMatchInlineSnapshot(`
expect(getPackageManager("", { workingDirectory: "test/empty" }))
.toMatchInlineSnapshot(`
{
"exec": "npx",
"execNoInstall": "npx --no-install",
@@ -104,7 +97,7 @@ describe("getPackageManager", () => {
test("should throw if an invalid value is provided", () => {
expect(() =>
getPackageManager("cargo", { workingDirectory: "src/test/fixtures/npm" }),
getPackageManager("cargo", { workingDirectory: "test/npm" }),
).toThrowError();
});
});
-59
View File
@@ -1,59 +0,0 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { setupServer } from "msw/node";
import { createGitHubDeployment, createJobSummary } from "./github";
import { getOctokit } from "@actions/github";
import { mockGithubDeployments } from "../test/mocks";
import { getTestConfig } from "../test/test-utils";
import mockfs from "mock-fs";
import { readFile } from "fs/promises";
afterEach(() => {
mockfs.restore();
});
describe("github", () => {
it("Calls createGitHubDeployment successfully", async () => {
const githubUser = "mock-user";
const githubRepoName = "wrangler-action";
const server = setupServer(
...mockGithubDeployments({ githubUser, githubRepoName }).handlers,
);
server.listen({ onUnhandledRequest: "error" });
vi.stubEnv("GITHUB_REPOSITORY", `${githubUser}/${githubRepoName}`);
const testConfig = getTestConfig();
const octokit = getOctokit(testConfig.GITHUB_TOKEN, { request: fetch });
await createGitHubDeployment({
config: testConfig,
octokit,
productionBranch: "production-branch",
deploymentId: "fake-deployment-id",
projectName: "fake-project-name",
deploymentUrl: "https://fake-deployment-url.com",
environment: "production",
});
server.close();
});
it("Calls createJobSummary successfully", async () => {
vi.stubEnv("GITHUB_STEP_SUMMARY", "summary");
mockfs({
summary: mockfs.file(),
});
await createJobSummary({
commitHash: "fake-commit-hash",
deploymentUrl: "https://fake-deployment-url.com",
aliasUrl: "https://fake-alias-url.com",
});
expect((await readFile("summary")).toString()).toMatchInlineSnapshot(`
"
# Deploying with Cloudflare Pages
| Name | Result |
| ----------------------- | - |
| **Last commit:** | fake-commit-hash |
| **Preview URL**: | https://fake-deployment-url.com |
| **Branch Preview URL**: | https://fake-alias-url.com |
"
`);
});
});
-120
View File
@@ -1,120 +0,0 @@
import { summary } from "@actions/core";
import { context, getOctokit } from "@actions/github";
import { env } from "process";
import { info } from "../utils";
import { OutputEntryPagesDeployment } from "../wranglerArtifactManager";
import { WranglerActionConfig } from "../wranglerAction";
type Octokit = ReturnType<typeof getOctokit>;
export async function createGitHubDeployment({
config,
octokit,
productionBranch,
environment,
deploymentId,
projectName,
deploymentUrl,
}: {
config: WranglerActionConfig;
octokit: Octokit;
productionBranch: string;
environment: string;
deploymentId: string | null;
projectName: string;
deploymentUrl?: string;
}) {
const githubBranch = env.GITHUB_HEAD_REF || env.GITHUB_REF_NAME;
const productionEnvironment = githubBranch === productionBranch;
const deployment = await octokit.rest.repos.createDeployment({
owner: context.repo.owner,
repo: context.repo.repo,
ref: githubBranch || context.ref,
auto_merge: false,
description: "Cloudflare Pages",
required_contexts: [],
environment,
production_environment: productionEnvironment,
});
if (deployment.status !== 201) {
info(config, "Error creating GitHub deployment");
return;
}
await octokit.rest.repos.createDeploymentStatus({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: deployment.data.id,
environment,
environment_url: deploymentUrl,
production_environment: productionEnvironment,
// don't have project_name or deployment_id I think
log_url: `https://dash.cloudflare.com/${config.CLOUDFLARE_ACCOUNT_ID}/pages/view/${projectName}/${deploymentId}`,
description: "Cloudflare Pages",
state: "success",
auto_inactive: false,
});
}
export async function createJobSummary({
commitHash,
deploymentUrl,
aliasUrl,
}: {
commitHash: string;
deploymentUrl?: string;
aliasUrl?: string;
}) {
await summary
.addRaw(
`
# Deploying with Cloudflare Pages
| Name | Result |
| ----------------------- | - |
| **Last commit:** | ${commitHash} |
| **Preview URL**: | ${deploymentUrl} |
| **Branch Preview URL**: | ${aliasUrl} |
`,
)
.write();
}
/**
* Create github deployment, if GITHUB_TOKEN is present in config
*/
export async function createGitHubDeploymentAndJobSummary(
config: WranglerActionConfig,
pagesArtifactFields: OutputEntryPagesDeployment,
) {
if (
config.GITHUB_TOKEN &&
pagesArtifactFields.production_branch &&
pagesArtifactFields.pages_project &&
pagesArtifactFields.deployment_trigger &&
pagesArtifactFields.stages
) {
const octokit = getOctokit(config.GITHUB_TOKEN);
await Promise.all([
createGitHubDeployment({
config,
octokit,
deploymentUrl: pagesArtifactFields.url,
productionBranch: pagesArtifactFields.production_branch,
environment: pagesArtifactFields.environment,
deploymentId: pagesArtifactFields.deployment_id,
projectName: pagesArtifactFields.pages_project,
}),
createJobSummary({
commitHash:
pagesArtifactFields.deployment_trigger.metadata.commit_hash.substring(
0,
8,
),
deploymentUrl: pagesArtifactFields.url,
aliasUrl: pagesArtifactFields.alias,
}),
]);
}
}
-34
View File
@@ -1,34 +0,0 @@
import { http, HttpResponse } from "msw";
import { z } from "zod";
export function mockGithubDeployments({
githubUser,
githubRepoName,
}: {
githubUser: string;
githubRepoName: string;
}) {
return {
handlers: [
http.post(
`https://api.github.com/repos/${githubUser}/${githubRepoName}/deployments`,
async ({ request }) => {
if (request.headers.get("Authorization") === null) {
return HttpResponse.text("error: no auth token", { status: 400 });
}
const GithubDeploymentsRequest = z.object({
auto_merge: z.literal(false),
description: z.literal("Cloudflare Pages"),
required_contexts: z.array(z.string()).length(0),
environment: z.literal("production"),
production_environment: z.literal(false),
});
// validate request body
GithubDeploymentsRequest.parse(await request.json());
return HttpResponse.json(null);
},
),
],
};
}
-26
View File
@@ -1,26 +0,0 @@
import { WranglerActionConfig } from "../wranglerAction";
export function getTestConfig({
config = {},
}: {
config?: Partial<WranglerActionConfig>;
} = {}): WranglerActionConfig {
return Object.assign(
{
WRANGLER_VERSION: "3.81.0",
didUserProvideWranglerVersion: false,
secrets: [],
workingDirectory: "/src/test/fixtures",
CLOUDFLARE_API_TOKEN: "foo",
CLOUDFLARE_ACCOUNT_ID: "bar",
ENVIRONMENT: "dev",
VARS: [],
COMMANDS: [],
QUIET_MODE: false,
PACKAGE_MANAGER: "npm",
WRANGLER_OUTPUT_DIR: "/tmp/wranglerArtifacts",
GITHUB_TOKEN: "xxxxyy23213123132131",
} as const satisfies WranglerActionConfig,
config,
);
}
-22
View File
@@ -1,8 +1,6 @@
import { existsSync } from "node:fs";
import * as path from "node:path";
import semverGt from "semver/functions/gt";
import { info as originalInfo, error as originalError } from "@actions/core";
import { WranglerActionConfig } from "./wranglerAction";
/**
* A helper function to compare two semver versions. If the second arg is greater than the first arg, it returns true.
@@ -21,23 +19,3 @@ export function checkWorkingDirectory(workingDirectory = ".") {
throw new Error(`Directory ${workingDirectory} does not exist.`);
}
}
export function info(
config: WranglerActionConfig,
message: string,
bypass?: boolean,
): void {
if (!config.QUIET_MODE || bypass) {
originalInfo(message);
}
}
export function error(
config: WranglerActionConfig,
message: string,
bypass?: boolean,
): void {
if (!config.QUIET_MODE || bypass) {
originalError(message);
}
}
+82 -35
View File
@@ -2,7 +2,6 @@ import * as core from "@actions/core";
import * as exec from "@actions/exec";
import { describe, expect, it, vi } from "vitest";
import { installWrangler } from "./wranglerAction";
import { getTestConfig } from "./test/test-utils";
describe("installWrangler", () => {
const testPackageManager = {
@@ -12,7 +11,20 @@ describe("installWrangler", () => {
};
it("Errors on unsupported wrangler version", async () => {
const testConfig = getTestConfig({ config: { WRANGLER_VERSION: "1" } });
const testConfig = {
WRANGLER_VERSION: "1",
didUserProvideWranglerVersion: false,
secrets: [],
workingDirectory: "/test",
CLOUDFLARE_API_TOKEN: "foo",
CLOUDFLARE_ACCOUNT_ID: "bar",
ENVIRONMENT: "dev",
VARS: [],
COMMANDS: [],
QUIET_MODE: false,
PACKAGE_MANAGER: "npm",
WRANGLER_OUTPUT_DIR: "/tmp/wranglerArtifacts",
};
await expect(
installWrangler(testConfig, testPackageManager),
).rejects.toThrowError(
@@ -21,14 +33,29 @@ describe("installWrangler", () => {
});
it("Does nothing if no wrangler version is specified and wrangler is already installed", async () => {
const testConfig = getTestConfig();
vi.spyOn(exec, "getExecOutput").mockImplementation(async () => {
return {
exitCode: 0,
stderr: "",
stdout: ` ⛅️ wrangler 3.48.0 (update available 3.53.1)`,
};
});
const testConfig = {
WRANGLER_VERSION: "3.81.0",
didUserProvideWranglerVersion: false,
secrets: [],
workingDirectory: "/test",
CLOUDFLARE_API_TOKEN: "foo",
CLOUDFLARE_ACCOUNT_ID: "bar",
ENVIRONMENT: "dev",
VARS: [],
COMMANDS: [],
QUIET_MODE: false,
PACKAGE_MANAGER: "npm",
WRANGLER_OUTPUT_DIR: "/tmp/wranglerArtifacts",
};
vi.spyOn(exec, "getExecOutput").mockImplementation(
async (commandLine: string, args?: string[]) => {
return {
exitCode: 0,
stderr: "",
stdout: ` ⛅️ wrangler 3.48.0 (update available 3.53.1)`,
};
},
);
const infoSpy = vi.spyOn(core, "info");
await installWrangler(testConfig, testPackageManager);
expect(infoSpy).toBeCalledWith(
@@ -37,38 +64,58 @@ describe("installWrangler", () => {
});
it("Does nothing if the wrangler version specified is the same as the one installed", async () => {
const testConfig = getTestConfig({
config: {
WRANGLER_VERSION: "3.48.0",
didUserProvideWranglerVersion: true,
const testConfig = {
WRANGLER_VERSION: "3.48.0",
didUserProvideWranglerVersion: true,
secrets: [],
workingDirectory: "/test",
CLOUDFLARE_API_TOKEN: "foo",
CLOUDFLARE_ACCOUNT_ID: "bar",
ENVIRONMENT: "dev",
VARS: [],
COMMANDS: [],
QUIET_MODE: false,
PACKAGE_MANAGER: "npm",
WRANGLER_OUTPUT_DIR: "/tmp/wranglerArtifacts",
};
vi.spyOn(exec, "getExecOutput").mockImplementation(
async (commandLine: string, args?: string[]) => {
return {
exitCode: 0,
stderr: "",
stdout: ` ⛅️ wrangler 3.48.0 (update available 3.53.1)`,
};
},
});
vi.spyOn(exec, "getExecOutput").mockImplementation(async () => {
return {
exitCode: 0,
stderr: "",
stdout: ` ⛅️ wrangler 3.48.0 (update available 3.53.1)`,
};
});
);
const infoSpy = vi.spyOn(core, "info");
await installWrangler(testConfig, testPackageManager);
expect(infoSpy).toBeCalledWith("✅ Using Wrangler 3.48.0");
});
it("Should install wrangler if the version specified is not already available", async () => {
const testConfig = getTestConfig({
config: {
WRANGLER_VERSION: "3.48.0",
didUserProvideWranglerVersion: true,
const testConfig = {
WRANGLER_VERSION: "3.48.0",
didUserProvideWranglerVersion: true,
secrets: [],
workingDirectory: "/test",
CLOUDFLARE_API_TOKEN: "foo",
CLOUDFLARE_ACCOUNT_ID: "bar",
ENVIRONMENT: "dev",
VARS: [],
COMMANDS: [],
QUIET_MODE: false,
PACKAGE_MANAGER: "npm",
WRANGLER_OUTPUT_DIR: "/tmp/wranglerArtifacts",
};
vi.spyOn(exec, "getExecOutput").mockImplementation(
async (commandLine: string, args?: string[]) => {
return {
exitCode: 0,
stderr: "",
stdout: ` ⛅️ wrangler 3.20.0 (update available 3.53.1)`,
};
},
});
vi.spyOn(exec, "getExecOutput").mockImplementation(async () => {
return {
exitCode: 0,
stderr: "",
stdout: ` ⛅️ wrangler 3.20.0 (update available 3.53.1)`,
};
});
vi.spyOn(exec, "exec").mockImplementation(async () => {
);
vi.spyOn(exec, "exec").mockImplementation(async (commandLine: string) => {
return 0;
});
const infoSpy = vi.spyOn(core, "info");
+39 -55
View File
@@ -2,6 +2,8 @@ import {
debug,
getMultilineInput,
endGroup as originalEndGroup,
error as originalError,
info as originalInfo,
startGroup as originalStartGroup,
setFailed,
setOutput,
@@ -11,9 +13,8 @@ import semverEq from "semver/functions/eq";
import { z } from "zod";
import { exec, execShell } from "./exec";
import { PackageManager } from "./packageManagers";
import { error, info, semverCompare } from "./utils";
import { semverCompare } from "./utils";
import { getDetailedPagesDeployOutput } from "./wranglerArtifactManager";
import { createGitHubDeploymentAndJobSummary } from "./service/github";
export type WranglerActionConfig = z.infer<typeof wranglerActionConfig>;
export const wranglerActionConfig = z.object({
@@ -29,9 +30,28 @@ export const wranglerActionConfig = z.object({
QUIET_MODE: z.boolean(),
PACKAGE_MANAGER: z.string(),
WRANGLER_OUTPUT_DIR: z.string(),
GITHUB_TOKEN: z.string(),
});
function info(
config: WranglerActionConfig,
message: string,
bypass?: boolean,
): void {
if (!config.QUIET_MODE || bypass) {
originalInfo(message);
}
}
function error(
config: WranglerActionConfig,
message: string,
bypass?: boolean,
): void {
if (!config.QUIET_MODE || bypass) {
originalError(message);
}
}
function startGroup(config: WranglerActionConfig, name: string): void {
if (!config.QUIET_MODE) {
originalStartGroup(name);
@@ -221,26 +241,6 @@ function getEnvVar(envVar: string) {
return value;
}
async function legacyUploadSecrets(
config: WranglerActionConfig,
packageManager: PackageManager,
secrets: string[],
environment?: string,
workingDirectory?: string,
) {
for (const secret of secrets) {
const args = ["wrangler", "secret", "put", secret];
if (environment) {
args.push("--env", environment);
}
await exec(packageManager.exec, args, {
cwd: workingDirectory,
silent: config["QUIET_MODE"],
input: Buffer.from(getSecret(secret)),
});
}
}
async function uploadSecrets(
config: WranglerActionConfig,
packageManager: PackageManager,
@@ -256,33 +256,18 @@ async function uploadSecrets(
startGroup(config, "🔑 Uploading secrets...");
try {
if (semverCompare(config["WRANGLER_VERSION"], "3.4.0")) {
return legacyUploadSecrets(
config,
packageManager,
secrets,
environment,
workingDirectory,
);
for (const secret of secrets) {
const args = ["wrangler", "secret", "put", secret];
if (environment) {
args.push("--env", environment);
}
await exec(packageManager.exec, args, {
cwd: workingDirectory,
silent: config["QUIET_MODE"],
input: Buffer.from(getSecret(secret)),
});
}
const args = ["wrangler", "secret:bulk"];
if (environment) {
args.push("--env", environment);
}
await exec(packageManager.exec, args, {
cwd: workingDirectory,
silent: config["QUIET_MODE"],
input: Buffer.from(
JSON.stringify(
Object.fromEntries(
secrets.map((secret) => [secret, getSecret(secret)]),
),
),
),
});
} catch (err: unknown) {
if (err instanceof Error) {
error(config, err.message);
@@ -381,8 +366,12 @@ async function wranglerCommands(
// Check if this command is a workers deployment
if (command.startsWith("deploy") || command.startsWith("publish")) {
const { deploymentUrl } = extractDeploymentUrlsFromStdout(stdOut);
const { deploymentUrl, aliasUrl } =
extractDeploymentUrlsFromStdout(stdOut);
setOutput("deployment-url", deploymentUrl);
// DEPRECATED: deployment-alias-url in favour of pages-deployment-alias, drop in next wrangler-action major version change
setOutput("deployment-alias-url", aliasUrl);
setOutput("pages-deployment-alias-url", aliasUrl);
}
// Check if this command is a pages deployment
if (
@@ -400,11 +389,6 @@ async function wranglerCommands(
setOutput("pages-deployment-alias-url", pagesArtifactFields.alias);
setOutput("pages-deployment-id", pagesArtifactFields.deployment_id);
setOutput("pages-environment", pagesArtifactFields.environment);
// Create github deployment, if GITHUB_TOKEN is present in config
await createGitHubDeploymentAndJobSummary(
config,
pagesArtifactFields,
);
} else {
info(
config,
+7 -7
View File
@@ -1,17 +1,17 @@
import mockfs from "mock-fs";
import mock from "mock-fs";
import { afterEach, describe, expect, it } from "vitest";
import {
getDetailedPagesDeployOutput,
getWranglerArtifacts,
} from "./wranglerArtifactManager";
afterEach(() => {
mockfs.restore();
afterEach(async () => {
mock.restore();
});
describe("wranglerArtifactsManager", () => {
describe("getWranglerArtifacts()", async () => {
it("Returns only wrangler output files from a given directory", async () => {
mockfs({
mock({
testOutputDir: {
"wrangler-output-2024-10-17_18-48-40_463-2e6e83.json": `
{"version": 1, "type":"wrangler-session", "wrangler_version":"3.81.0", "command_line_args":["what's up"], "log_file_path": "/here"}
@@ -27,7 +27,7 @@ describe("wranglerArtifactsManager", () => {
]);
});
it("Returns an empty list when the output directory doesn't exist", async () => {
mockfs({
mock({
notTheDirWeWant: {},
});
@@ -38,7 +38,7 @@ describe("wranglerArtifactsManager", () => {
describe("getDetailedPagesDeployOutput()", async () => {
it("Returns only detailed pages deploy output from wrangler artifacts", async () => {
mockfs({
mock({
testOutputDir: {
"wrangler-output-2024-10-17_18-48-40_463-2e6e83.json": `
{"version": 1, "type":"wrangler-session", "wrangler_version":"3.81.0", "command_line_args":["what's up"], "log_file_path": "/here"}
@@ -60,7 +60,7 @@ describe("wranglerArtifactsManager", () => {
});
}),
it("Skips artifact entries that are not parseable", async () => {
mockfs({
mock({
testOutputDir: {
"wrangler-output-2024-10-17_18-48-40_463-2e6e83.json": `
this line is invalid json.
+1 -38
View File
@@ -14,47 +14,10 @@ const OutputEntryPagesDeployment = OutputEntryBase.merge(
url: z.string().optional(),
alias: z.string().optional(),
environment: z.enum(["production", "preview"]),
// optional, added in wrangler@TBD
production_branch: z.string().optional(),
// optional, added in wrangler@TBD
stages: z
.array(
z.object({
name: z.enum([
"queued",
"initialize",
"clone_repo",
"build",
"deploy",
]),
status: z.enum([
"idle",
"active",
"canceled",
"success",
"failure",
"skipped",
]),
started_on: z.string().nullable(),
ended_on: z.string().nullable(),
}),
)
.optional(),
// optional, added in wrangler@TBD
deployment_trigger: z
.object({
metadata: z.object({
/** Commit hash of the deployment trigger metadata for the pages project */
commit_hash: z.string(),
}),
})
.optional(),
}),
);
export type OutputEntryPagesDeployment = z.infer<
typeof OutputEntryPagesDeployment
>;
type OutputEntryPagesDeployment = z.infer<typeof OutputEntryPagesDeployment>;
/**
* Parses file names in a directory to find wrangler artifact files
+1 -1
View File
@@ -15,6 +15,6 @@
"lib": ["ESNext"],
"types": ["node", "@cloudflare/workers-types"]
},
"exclude": ["node_modules"],
"exclude": ["node_modules", "**/*.test.ts"],
"include": ["src"]
}