Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/workers-previews.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler-action": minor
---

Add support for Workers Previews, including Preview artifact parsing, Preview outputs, GitHub Deployments, and job summaries for `wrangler preview`.
74 changes: 74 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,80 @@ jobs:
command: versions upload
```

### Deploy a Workers Preview

Workers Previews let you test non-production branches with their own URLs, variables, secrets, and bindings. Use `command: preview` instead of the default `deploy` command.

Before using this workflow, add your Cloudflare credentials as GitHub Actions secrets:

```sh
gh auth login
gh secret set CLOUDFLARE_API_TOKEN
gh secret set CLOUDFLARE_ACCOUNT_ID
```

GitHub Actions provides `${{ secrets.GITHUB_TOKEN }}` automatically. You do not need to create it yourself.

If you prefer plain YAML without this action, run Wrangler directly:

```yaml
name: Preview

on: [pull_request]

jobs:
preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: npx wrangler preview --json
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
```

Add install or build steps before `npx wrangler preview --json` if your Worker needs them. To use `wrangler-action` instead, use this workflow:

```yaml
name: Preview

on:
pull_request:
types: [opened, synchronize, reopened, closed]

permissions:
contents: read
deployments: write

jobs:
preview:
if: github.event.action != 'closed'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Deploy preview
id: preview
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: preview
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
```

The action sets the following outputs for preview commands:

| Output | Description |
| ------------------------ | -------------------------------------------------- |
| `deployment-url` | The stable preview URL (same as `preview-url`) |
| `preview-url` | The stable Preview URL for the branch |
| `preview-deployment-url` | The immutable URL for this specific deployment |
| `preview-name` | The Preview name (defaults to the git branch name) |
| `preview-id` | The Preview resource ID |
| `preview-deployment-id` | The deployment ID within the Preview |

When `gitHubToken` is provided, the action creates a GitHub Deployment with the Preview URL linked as the environment URL and writes a job summary. For the plain `wrangler preview --json` workflow, PR comments, and cleanup examples, refer to [Automation examples](https://developers.cloudflare.com/workers/previews/automation-examples/).

## Advanced Usage

### Setting A Worker Secret for A Specific Environment
Expand Down
10 changes: 10 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,13 @@ outputs:
description: "If the command was a Pages deployment, this will be the ID of the deployment - needs wrangler >= 3.81.0"
pages-environment:
description: "If the command was a Pages deployment, this will be the environment of the deployment - needs wrangler >= 3.81.0"
preview-url:
description: "If the command was a Workers Preview deployment, this will be the stable Preview URL for the branch"
preview-deployment-url:
description: "If the command was a Workers Preview deployment, this will be the immutable deployment URL"
preview-name:
description: "If the command was a Workers Preview deployment, this will be the Preview name (typically the branch name)"
preview-id:
description: "If the command was a Workers Preview deployment, this will be the Preview resource ID"
preview-deployment-id:
description: "If the command was a Workers Preview deployment, this will be the deployment ID within the Preview"
41 changes: 40 additions & 1 deletion src/commandOutputParsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ import {
getOutputEntry,
OutputEntryDeployment,
OutputEntryPagesDeployment,
OutputEntryPreview,
OutputEntryVersionUpload,
} from "./wranglerArtifactManager";
import { createGitHubDeploymentAndJobSummary } from "./service/github";
import {
createGitHubDeploymentAndJobSummary,
createPreviewGitHubDeploymentAndJobSummary,
} from "./service/github";

// fallback to trying to extract the deployment-url and pages-deployment-alias-url from stdout for wranglerVersion < 3.81.0
function extractDeploymentUrlsFromStdout(stdOut: string): {
Expand Down Expand Up @@ -114,6 +118,27 @@ function handleVersionsUploadOutputEntry(
setOutput("deployment-url", versionsOutputEntry.preview_url);
}

async function handlePreviewOutputEntry(
config: WranglerActionConfig,
previewOutputEntry: OutputEntryPreview,
) {
const previewUrl = previewOutputEntry.preview_urls?.[0] ?? undefined;
const deploymentUrl = previewOutputEntry.deployment_urls?.[0] ?? undefined;

// Set the primary deployment-url to the preview URL (stable branch URL)
setOutput("deployment-url", previewUrl);

// Set preview-specific outputs
setOutput("preview-url", previewUrl);
setOutput("preview-deployment-url", deploymentUrl);
setOutput("preview-name", previewOutputEntry.preview_name);
setOutput("preview-id", previewOutputEntry.preview_id);
setOutput("preview-deployment-id", previewOutputEntry.deployment_id);

// Create GitHub Deployment and Job Summary for the preview
await createPreviewGitHubDeploymentAndJobSummary(config, previewOutputEntry);
}

/**
* If no wrangler output file found, log a message stating deployment-url will be unavailable for output.
* @deprecated Use {@link handleVersionsOutputEntry} instead.
Expand Down Expand Up @@ -150,6 +175,17 @@ function handleDeprectatedStdoutParsing(
handleVersionsOutputCommand(config);
return;
}

// Check if this command is a preview deployment
if (command.startsWith("preview")) {
info(
config,
"Unable to find a WRANGLER_OUTPUT_DIR, preview outputs will be unavailable. Have you updated wrangler to the latest version?",
);
const { deploymentUrl } = extractDeploymentUrlsFromStdout(stdOut);
setOutput("deployment-url", deploymentUrl);
return;
}
}

export async function handleCommandOutputParsing(
Expand All @@ -176,5 +212,8 @@ export async function handleCommandOutputParsing(
case "version-upload":
handleVersionsUploadOutputEntry(outputEntry);
break;
case "preview":
await handlePreviewOutputEntry(config, outputEntry);
break;
}
}
110 changes: 109 additions & 1 deletion src/service/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { summary } from "@actions/core";
import { context, getOctokit } from "@actions/github";
import { env } from "process";
import { info, warn } from "../utils";
import { OutputEntryPagesDeployment } from "../wranglerArtifactManager";
import {
OutputEntryPagesDeployment,
OutputEntryPreview,
} from "../wranglerArtifactManager";
import { WranglerActionConfig } from "../wranglerAction";

type Octokit = ReturnType<typeof getOctokit>;
Expand Down Expand Up @@ -126,3 +129,108 @@ export async function createGitHubDeploymentAndJobSummary(
}
}
}

export async function createPreviewJobSummary({
previewName,
previewUrl,
deploymentUrl,
workerName,
}: {
previewName: string;
previewUrl?: string;
deploymentUrl?: string;
workerName: string | null;
}) {
await summary
.addRaw(
`
# Workers Preview Deployment

| Name | Result |
| ----------------------- | - |
| **Worker:** | ${workerName ?? "unknown"} |
| **Preview:** | ${previewName} |
| **Preview URL**: | ${previewUrl ?? "N/A"} |
| **Deployment URL**: | ${deploymentUrl ?? "N/A"} |
`,
)
.write();
}

/**
* Create GitHub deployment and job summary for a Workers Preview, if GITHUB_TOKEN is present
*/
export async function createPreviewGitHubDeploymentAndJobSummary(
config: WranglerActionConfig,
previewFields: OutputEntryPreview,
) {
const previewUrl = previewFields.preview_urls?.[0];
const deploymentUrl = previewFields.deployment_urls?.[0];

if (config.GITHUB_TOKEN) {
const octokit = getOctokit(config.GITHUB_TOKEN);
const githubBranch = env.GITHUB_HEAD_REF || env.GITHUB_REF_NAME;
const environmentName = `preview: ${previewFields.preview_name}`;

const [createDeploymentRes, createSummaryRes] = await Promise.allSettled([
(async () => {
const deployment = await octokit.rest.repos.createDeployment({
owner: context.repo.owner,
repo: context.repo.repo,
ref: githubBranch || context.ref,
auto_merge: false,
description: "Cloudflare Workers Preview",
required_contexts: [],
environment: environmentName,
production_environment: false,
});

if (deployment.status !== 201) {
info(config, "Error creating GitHub deployment for preview");
return;
}

await octokit.rest.repos.createDeploymentStatus({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: deployment.data.id,
environment: environmentName,
environment_url: previewUrl,
production_environment: false,
log_url: previewFields.worker_name
? `https://dash.cloudflare.com/${config.CLOUDFLARE_ACCOUNT_ID}/workers/services/view/${previewFields.worker_name}`
: `https://dash.cloudflare.com/${config.CLOUDFLARE_ACCOUNT_ID}/workers`,
description: "Cloudflare Workers Preview",
state: "success",
auto_inactive: false,
});
})(),
createPreviewJobSummary({
previewName: previewFields.preview_name,
previewUrl,
deploymentUrl,
workerName: previewFields.worker_name,
}),
]);

if (createDeploymentRes.status === "rejected") {
warn(config, "Creating Github Deployment for preview failed");
}

if (createSummaryRes.status === "rejected") {
warn(config, "Creating Github Job summary for preview failed");
}
} else {
// Still create job summary even without GitHub token
try {
await createPreviewJobSummary({
previewName: previewFields.preview_name,
previewUrl,
deploymentUrl,
workerName: previewFields.worker_name,
});
} catch {
warn(config, "Creating Github Job summary for preview failed");
}
}
}
Loading
Loading