mirror of
https://github.com/cloudflare/wrangler-action.git
synced 2024-11-23 10:33:24 +01:00
Merge pull request #325 from cloudflare/maximo/add-github-deployments-parity-for-pages-deployments-in-wrangler-action
Add GitHub deployments for parity with pages-action
This commit is contained in:
commit
b19342b08c
66 changed files with 1584 additions and 2959 deletions
5
.changeset/bright-dryers-kneel.md
Normal file
5
.changeset/bright-dryers-kneel.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"wrangler-action": minor
|
||||
---
|
||||
|
||||
Add GitHub deployments and job summaries for parity with pages-action
|
26
.github/workflows/deploy.yml
vendored
26
.github/workflows/deploy.yml
vendored
|
@ -30,7 +30,7 @@ jobs:
|
|||
- name: Only build app
|
||||
uses: ./
|
||||
with:
|
||||
workingDirectory: "./test/only-build"
|
||||
workingDirectory: "./src/test/fixtures/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: "./test/build-quiet"
|
||||
workingDirectory: "./src/test/fixtures/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: "./test/environment"
|
||||
workingDirectory: "./src/test/fixtures/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: "./test/secrets-v2"
|
||||
workingDirectory: "./src/test/fixtures/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: "./test/secrets-default"
|
||||
workingDirectory: "./src/test/fixtures/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: "./test/secrets-default"
|
||||
workingDirectory: "./src/test/fixtures/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: "./test/specify-package-manager"
|
||||
workingDirectory: "./src/test/fixtures/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: "./test/unspecified-package-manager"
|
||||
workingDirectory: "./src/test/fixtures/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: "./test/npm"
|
||||
workingDirectory: "./src/test/fixtures/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: "./test/yarn"
|
||||
workingDirectory: "./src/test/fixtures/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: "./test/pnpm"
|
||||
workingDirectory: "./src/test/fixtures/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 ./test/pre-installed-wrangler
|
||||
cd ./src/test/fixtures/pre-installed-wrangler
|
||||
npm install
|
||||
|
||||
- name: Support pre-installed wrangler
|
||||
uses: ./
|
||||
with:
|
||||
workingDirectory: "./test/pre-installed-wrangler"
|
||||
workingDirectory: "./src/test/fixtures/pre-installed-wrangler"
|
||||
command: action-test
|
||||
|
|
|
@ -173,6 +173,9 @@ jobs:
|
|||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
name: Deploy
|
||||
permissions:
|
||||
contents: read
|
||||
deployments: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Deploy
|
||||
|
@ -181,6 +184,8 @@ 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
|
||||
|
|
|
@ -44,6 +44,9 @@ 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)"
|
||||
|
|
3978
package-lock.json
generated
3978
package-lock.json
generated
File diff suppressed because it is too large
Load diff
26
package.json
26
package.json
|
@ -29,20 +29,24 @@
|
|||
"check": "prettier --check ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/core": "^1.11.1",
|
||||
"@actions/exec": "^1.1.1",
|
||||
"@actions/github": "^6.0.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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"
|
||||
"@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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ export async function execShell(
|
|||
|
||||
await promise;
|
||||
return child.exitCode;
|
||||
} catch (err: any) {
|
||||
} catch (err) {
|
||||
if (isExecAsyncException(err)) {
|
||||
process.stderr.write(err.stderr);
|
||||
throw new Error(`Process failed with exit code ${err.code}`);
|
||||
|
|
|
@ -26,6 +26,7 @@ const config: WranglerActionConfig = {
|
|||
tmpdir(),
|
||||
`wranglerArtifacts-${crypto.randomUUID()}`,
|
||||
)}`,
|
||||
GITHUB_TOKEN: getInput("gitHubToken", { required: false }),
|
||||
} as const;
|
||||
|
||||
const packageManager = getPackageManager(config.PACKAGE_MANAGER, {
|
||||
|
|
|
@ -3,8 +3,9 @@ import { getPackageManager } from "./packageManagers";
|
|||
|
||||
describe("getPackageManager", () => {
|
||||
test("should use provided value instead of inferring from lockfile", () => {
|
||||
expect(getPackageManager("npm", { workingDirectory: "test/npm" }))
|
||||
.toMatchInlineSnapshot(`
|
||||
expect(
|
||||
getPackageManager("npm", { workingDirectory: "src/test/fixtures/npm" }),
|
||||
).toMatchInlineSnapshot(`
|
||||
{
|
||||
"exec": "npx",
|
||||
"execNoInstall": "npx --no-install",
|
||||
|
@ -12,8 +13,9 @@ describe("getPackageManager", () => {
|
|||
}
|
||||
`);
|
||||
|
||||
expect(getPackageManager("yarn", { workingDirectory: "test/npm" }))
|
||||
.toMatchInlineSnapshot(`
|
||||
expect(
|
||||
getPackageManager("yarn", { workingDirectory: "src/test/fixtures/npm" }),
|
||||
).toMatchInlineSnapshot(`
|
||||
{
|
||||
"exec": "yarn",
|
||||
"execNoInstall": "yarn",
|
||||
|
@ -21,8 +23,9 @@ describe("getPackageManager", () => {
|
|||
}
|
||||
`);
|
||||
|
||||
expect(getPackageManager("pnpm", { workingDirectory: "test/npm" }))
|
||||
.toMatchInlineSnapshot(`
|
||||
expect(
|
||||
getPackageManager("pnpm", { workingDirectory: "src/test/fixtures/npm" }),
|
||||
).toMatchInlineSnapshot(`
|
||||
{
|
||||
"exec": "pnpm exec",
|
||||
"execNoInstall": "pnpm exec",
|
||||
|
@ -30,8 +33,9 @@ describe("getPackageManager", () => {
|
|||
}
|
||||
`);
|
||||
|
||||
expect(getPackageManager("bun", { workingDirectory: "test/bun" }))
|
||||
.toMatchInlineSnapshot(`
|
||||
expect(
|
||||
getPackageManager("bun", { workingDirectory: "src/test/fixtures/bun" }),
|
||||
).toMatchInlineSnapshot(`
|
||||
{
|
||||
"exec": "bunx",
|
||||
"execNoInstall": "bun run",
|
||||
|
@ -41,7 +45,7 @@ describe("getPackageManager", () => {
|
|||
});
|
||||
|
||||
test("should use npm if no value provided and package-lock.json exists", () => {
|
||||
expect(getPackageManager("", { workingDirectory: "test/npm" }))
|
||||
expect(getPackageManager("", { workingDirectory: "src/test/fixtures/npm" }))
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"exec": "npx",
|
||||
|
@ -52,8 +56,9 @@ describe("getPackageManager", () => {
|
|||
});
|
||||
|
||||
test("should use yarn if no value provided and yarn.lock exists", () => {
|
||||
expect(getPackageManager("", { workingDirectory: "test/yarn" }))
|
||||
.toMatchInlineSnapshot(`
|
||||
expect(
|
||||
getPackageManager("", { workingDirectory: "src/test/fixtures/yarn" }),
|
||||
).toMatchInlineSnapshot(`
|
||||
{
|
||||
"exec": "yarn",
|
||||
"execNoInstall": "yarn",
|
||||
|
@ -63,8 +68,9 @@ describe("getPackageManager", () => {
|
|||
});
|
||||
|
||||
test("should use pnpm if no value provided and pnpm-lock.yaml exists", () => {
|
||||
expect(getPackageManager("", { workingDirectory: "test/pnpm" }))
|
||||
.toMatchInlineSnapshot(`
|
||||
expect(
|
||||
getPackageManager("", { workingDirectory: "src/test/fixtures/pnpm" }),
|
||||
).toMatchInlineSnapshot(`
|
||||
{
|
||||
"exec": "pnpm exec",
|
||||
"execNoInstall": "pnpm exec",
|
||||
|
@ -74,7 +80,7 @@ describe("getPackageManager", () => {
|
|||
});
|
||||
|
||||
test("should use bun if no value provided and bun.lockb exists", () => {
|
||||
expect(getPackageManager("", { workingDirectory: "test/bun" }))
|
||||
expect(getPackageManager("", { workingDirectory: "src/test/fixtures/bun" }))
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"exec": "bunx",
|
||||
|
@ -85,8 +91,9 @@ describe("getPackageManager", () => {
|
|||
});
|
||||
|
||||
test("should use npm if no value provided and no lockfile is present", () => {
|
||||
expect(getPackageManager("", { workingDirectory: "test/empty" }))
|
||||
.toMatchInlineSnapshot(`
|
||||
expect(
|
||||
getPackageManager("", { workingDirectory: "src/test/fixtures/empty" }),
|
||||
).toMatchInlineSnapshot(`
|
||||
{
|
||||
"exec": "npx",
|
||||
"execNoInstall": "npx --no-install",
|
||||
|
@ -97,7 +104,7 @@ describe("getPackageManager", () => {
|
|||
|
||||
test("should throw if an invalid value is provided", () => {
|
||||
expect(() =>
|
||||
getPackageManager("cargo", { workingDirectory: "test/npm" }),
|
||||
getPackageManager("cargo", { workingDirectory: "src/test/fixtures/npm" }),
|
||||
).toThrowError();
|
||||
});
|
||||
});
|
||||
|
|
59
src/service/github.spec.ts
Normal file
59
src/service/github.spec.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
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
src/service/github.ts
Normal file
120
src/service/github.ts
Normal file
|
@ -0,0 +1,120 @@
|
|||
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,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
0
test/build-quiet/package-lock.json → src/test/fixtures/build-quiet/package-lock.json
generated
vendored
0
test/build-quiet/package-lock.json → src/test/fixtures/build-quiet/package-lock.json
generated
vendored
0
test/environment/package-lock.json → src/test/fixtures/environment/package-lock.json
generated
vendored
0
test/environment/package-lock.json → src/test/fixtures/environment/package-lock.json
generated
vendored
0
test/npm/package-lock.json → src/test/fixtures/npm/package-lock.json
generated
vendored
0
test/npm/package-lock.json → src/test/fixtures/npm/package-lock.json
generated
vendored
0
test/only-build/package-lock.json → src/test/fixtures/only-build/package-lock.json
generated
vendored
0
test/only-build/package-lock.json → src/test/fixtures/only-build/package-lock.json
generated
vendored
0
test/secrets-v2/package-lock.json → src/test/fixtures/secrets-v2/package-lock.json
generated
vendored
0
test/secrets-v2/package-lock.json → src/test/fixtures/secrets-v2/package-lock.json
generated
vendored
34
src/test/mocks.ts
Normal file
34
src/test/mocks.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
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
src/test/test-utils.ts
Normal file
26
src/test/test-utils.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
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
src/utils.ts
22
src/utils.ts
|
@ -1,6 +1,8 @@
|
|||
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.
|
||||
|
@ -19,3 +21,23 @@ 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ 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 = {
|
||||
|
@ -11,20 +12,7 @@ describe("installWrangler", () => {
|
|||
};
|
||||
|
||||
it("Errors on unsupported wrangler version", async () => {
|
||||
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",
|
||||
};
|
||||
const testConfig = getTestConfig({ config: { WRANGLER_VERSION: "1" } });
|
||||
await expect(
|
||||
installWrangler(testConfig, testPackageManager),
|
||||
).rejects.toThrowError(
|
||||
|
@ -33,29 +21,14 @@ describe("installWrangler", () => {
|
|||
});
|
||||
|
||||
it("Does nothing if no wrangler version is specified and wrangler is already installed", async () => {
|
||||
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[]) => {
|
||||
const testConfig = getTestConfig();
|
||||
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(
|
||||
|
@ -64,58 +37,38 @@ describe("installWrangler", () => {
|
|||
});
|
||||
|
||||
it("Does nothing if the wrangler version specified is the same as the one installed", async () => {
|
||||
const testConfig = {
|
||||
const testConfig = getTestConfig({
|
||||
config: {
|
||||
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[]) => {
|
||||
},
|
||||
});
|
||||
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 = {
|
||||
const testConfig = getTestConfig({
|
||||
config: {
|
||||
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[]) => {
|
||||
},
|
||||
});
|
||||
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 (commandLine: string) => {
|
||||
});
|
||||
vi.spyOn(exec, "exec").mockImplementation(async () => {
|
||||
return 0;
|
||||
});
|
||||
const infoSpy = vi.spyOn(core, "info");
|
||||
|
|
|
@ -2,8 +2,6 @@ import {
|
|||
debug,
|
||||
getMultilineInput,
|
||||
endGroup as originalEndGroup,
|
||||
error as originalError,
|
||||
info as originalInfo,
|
||||
startGroup as originalStartGroup,
|
||||
setFailed,
|
||||
setOutput,
|
||||
|
@ -13,8 +11,9 @@ import semverEq from "semver/functions/eq";
|
|||
import { z } from "zod";
|
||||
import { exec, execShell } from "./exec";
|
||||
import { PackageManager } from "./packageManagers";
|
||||
import { semverCompare } from "./utils";
|
||||
import { error, info, semverCompare } from "./utils";
|
||||
import { getDetailedPagesDeployOutput } from "./wranglerArtifactManager";
|
||||
import { createGitHubDeploymentAndJobSummary } from "./service/github";
|
||||
|
||||
export type WranglerActionConfig = z.infer<typeof wranglerActionConfig>;
|
||||
export const wranglerActionConfig = z.object({
|
||||
|
@ -30,28 +29,9 @@ 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);
|
||||
|
@ -401,12 +381,8 @@ async function wranglerCommands(
|
|||
|
||||
// Check if this command is a workers deployment
|
||||
if (command.startsWith("deploy") || command.startsWith("publish")) {
|
||||
const { deploymentUrl, aliasUrl } =
|
||||
extractDeploymentUrlsFromStdout(stdOut);
|
||||
const { deploymentUrl } = 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 (
|
||||
|
@ -424,6 +400,11 @@ 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,
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import mock from "mock-fs";
|
||||
import mockfs from "mock-fs";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
getDetailedPagesDeployOutput,
|
||||
getWranglerArtifacts,
|
||||
} from "./wranglerArtifactManager";
|
||||
|
||||
afterEach(async () => {
|
||||
mock.restore();
|
||||
afterEach(() => {
|
||||
mockfs.restore();
|
||||
});
|
||||
describe("wranglerArtifactsManager", () => {
|
||||
describe("getWranglerArtifacts()", async () => {
|
||||
it("Returns only wrangler output files from a given directory", async () => {
|
||||
mock({
|
||||
mockfs({
|
||||
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 () => {
|
||||
mock({
|
||||
mockfs({
|
||||
notTheDirWeWant: {},
|
||||
});
|
||||
|
||||
|
@ -38,7 +38,7 @@ describe("wranglerArtifactsManager", () => {
|
|||
|
||||
describe("getDetailedPagesDeployOutput()", async () => {
|
||||
it("Returns only detailed pages deploy output from wrangler artifacts", async () => {
|
||||
mock({
|
||||
mockfs({
|
||||
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 () => {
|
||||
mock({
|
||||
mockfs({
|
||||
testOutputDir: {
|
||||
"wrangler-output-2024-10-17_18-48-40_463-2e6e83.json": `
|
||||
this line is invalid json.
|
||||
|
|
|
@ -14,10 +14,47 @@ 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(),
|
||||
}),
|
||||
);
|
||||
|
||||
type OutputEntryPagesDeployment = z.infer<typeof OutputEntryPagesDeployment>;
|
||||
export type OutputEntryPagesDeployment = z.infer<
|
||||
typeof OutputEntryPagesDeployment
|
||||
>;
|
||||
|
||||
/**
|
||||
* Parses file names in a directory to find wrangler artifact files
|
||||
|
|
|
@ -15,6 +15,6 @@
|
|||
"lib": ["ESNext"],
|
||||
"types": ["node", "@cloudflare/workers-types"]
|
||||
},
|
||||
"exclude": ["node_modules", "**/*.test.ts"],
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["src"]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue