Files
emdash-cms--emdash/.github/workflows/playground-preview-comment.yml
wehub-resource-sync b3a7f98e5a
CI / E2E Cloudflare (4/8) (push) Failing after 0s
CI / E2E Cloudflare (8/8) (push) Failing after 1s
CI / Lint (push) Failing after 1s
Auto Extract / Extract (push) Failing after 4s
CI / Version Check (push) Failing after 9s
CI / Integration Tests (push) Failing after 1s
CI / E2E tests (1/8) (push) Failing after 1s
CI / E2E tests (2/8) (push) Failing after 2s
CI / E2E tests (3/8) (push) Failing after 2s
CI / Browser Tests (push) Failing after 1s
CI / E2E tests (5/8) (push) Failing after 1s
CI / E2E Cloudflare (2/8) (push) Failing after 2s
CI / Typecheck (push) Failing after 1s
CI / Changeset Validation (push) Failing after 2s
CI / E2E Cloudflare (5/8) (push) Failing after 1s
CI / E2E Cloudflare (6/8) (push) Failing after 1s
CI / E2E Cloudflare (7/8) (push) Failing after 1s
CodeQL / Analyze (javascript-typescript) (push) Failing after 1s
Format / Format (push) Failing after 0s
CodeQL / Analyze (actions) (push) Failing after 4s
CI / E2E tests (4/8) (push) Failing after 1s
CI / E2E tests (6/8) (push) Failing after 1s
CI / E2E tests (7/8) (push) Failing after 2s
CI / E2E tests (8/8) (push) Failing after 1s
CI / E2E Cloudflare (1/8) (push) Failing after 1s
CI / E2E Cloudflare (3/8) (push) Failing after 2s
Preview Releases / Publish Preview (push) Failing after 0s
zizmor / Run zizmor (push) Failing after 1s
Release / Release (push) Failing after 2s
CI / Smoke Tests (push) Failing after 5m36s
CI / Tests (push) Failing after 6m36s
Release / Sync Templates (push) Has been skipped
CI / E2E Tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:23:53 +08:00

174 lines
7.5 KiB
YAML

name: Playground Preview
# Workers Builds runs `wrangler preview` for the playground demo (it can't use
# the standard preview-URL flow because the playground has a Durable Object).
# These are a private beta feature, and don't currently support automatic PR
# comments with the preview URL.
# The cloudflare-workers-and-pages bot posts a "build successful" comment when
# the build finishes, but it doesn't include the preview URL (preview URLs are
# a `wrangler versions upload` concept, not `wrangler preview`).
# This workflow is a workaround to post the preview URL in the PR description.
#
# The playground is the primary "try this PR" surface for emdash: each visit
# gets its own session-scoped Durable Object, so reviewers can poke at a full
# working admin without signup, login, or shared state. That makes the preview
# link the single most useful thing in the PR -- but a sticky comment posted
# after the build would land below the fold (CF bot, pkg-pr-new, changeset-bot,
# etc.). So instead we edit the PR description to insert a managed block.
#
# Trigger: the CF bot's "Deployment successful" edit, scoped to emdash-playground.
# This means we comment when the deploy is genuinely live, not just when the
# commit was pushed.
#
# The branch preview URL is fully deterministic from the branch name and the
# worker/account names, so no Cloudflare API token is required -- only the
# default GITHUB_TOKEN.
#
# Caveats:
# - Branch slugs longer than the (private-beta, undocumented) max length get
# truncated server-side; collisions get a random 6-char suffix appended.
# In practice this is fine for emdash branch names. If the URL 404s, check
# the dash.
# - issue_comment workflows run from `main`, not the PR branch. Changes to
# this file only take effect once merged.
# - This won't fire for PRs from forks where the fork doesn't have the
# workflow file. That's fine -- the playground builds only run for the
# internal repo, not forks.
on:
issue_comment:
types: [created, edited]
permissions: {}
# The Cloudflare bot edits its comment 4-5 times during a build (queued ->
# initializing -> running -> ... -> successful). The "successful" edit is
# usually the terminal state, but a subsequent edit could race a still-running
# workflow that's mid-fetch. Serialize per PR; cancel in-flight runs so only
# the latest comment state is processed.
concurrency:
group: ${{ github.workflow }}-${{ github.event.issue.number }}
cancel-in-progress: true
jobs:
update-body:
name: Update PR body
runs-on: ubuntu-latest
# Only react to:
# - PR comments (not issue comments)
# - the CF bot's comment
# - that mentions the playground worker
# - and contains the deployment-success marker
if: >-
github.event.issue.pull_request != null &&
github.event.comment.user.login == 'cloudflare-workers-and-pages[bot]' &&
contains(github.event.comment.body, 'emdash-playground') &&
contains(github.event.comment.body, 'Deployment successful!')
permissions:
pull-requests: write # read PR body and update it with the playground block; no PR code is checked out
steps:
- name: Update PR description with playground link
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
BOT_COMMENT_BODY: ${{ github.event.comment.body }}
PR_NUMBER: ${{ github.event.issue.number }}
with:
script: |
const { BOT_COMMENT_BODY, PR_NUMBER } = process.env;
const prNumber = Number(PR_NUMBER);
// Confirm the playground row itself is in the successful state.
// Each row in the bot's comment looks like:
// | ✅ Deployment successful! ... | emdash-playground | <sha> | ... |
const playgroundRow = BOT_COMMENT_BODY.split("\n").find((line) =>
line.includes("| emdash-playground |"),
);
if (!playgroundRow || !playgroundRow.includes("✅ Deployment successful!")) {
core.info("Playground row not in successful state; skipping.");
return;
}
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
});
const branch = pr.head.ref;
// Slug rules (from SPEC: Worker Previews): lowercase, with /, .,
// +, =, _ replaced by -. We widen this to any non-DNS-safe char
// (handles Renovate-style `@`, unusual community branches, etc.):
// anything outside [a-z0-9-] becomes -, repeated -- collapsed,
// leading/trailing - trimmed. If we end up with an empty slug
// (e.g. branch was all special chars), bail rather than emit a
// broken URL.
const slug = branch
.toLowerCase()
.replace(/[^a-z0-9-]+/g, "-")
.replace(/-+/g, "-")
.replace(/^-+|-+$/g, "");
if (!slug) {
core.warning(`Branch "${branch}" produced an empty slug; skipping.`);
return;
}
const url = `https://${slug}-emdash-playground.emdash-cms.workers.dev`;
const START = "<!-- emdash:playground-preview:start -->";
const END = "<!-- emdash:playground-preview:end -->";
// The managed block. Kept short and inviting -- the link is the
// point. Each visit to the playground gets its own session-scoped
// Durable Object, so reviewers can play freely.
const block = [
START,
"",
"---",
"",
`### Try this PR`,
"",
`**[Open a fresh playground →](${url})**`,
"",
`A full working EmDash site, deployed from this branch. Each visit gets its own session-scoped sandbox: no login needed and no shared state. Try the admin, edit content, hit the public site.`,
"",
`<sub>Tracks \`${branch}\`. Updated automatically when the playground redeploys.</sub>`,
"",
END,
].join("\n");
const existingBody = pr.body ?? "";
// If a block already exists, replace it in place (preserves
// whatever position the author or a previous run put it in).
// Otherwise append it to the end of the description.
const blockRegex = new RegExp(
`\\n*${escapeRegex(START)}[\\s\\S]*?${escapeRegex(END)}\\n*`,
);
let newBody;
if (blockRegex.test(existingBody)) {
newBody = existingBody.replace(blockRegex, `\n\n${block}\n`);
core.info("Replaced existing playground block in PR body.");
} else {
// Append, with a blank line separator.
const trimmed = existingBody.replace(/\s+$/, "");
newBody = trimmed.length > 0 ? `${trimmed}\n\n${block}\n` : `${block}\n`;
core.info("Appended playground block to PR body.");
}
if (newBody === existingBody) {
core.info("PR body unchanged; skipping update.");
return;
}
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
body: newBody,
});
function escapeRegex(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}