Skip to content

Commit 66e34a1

Browse files
committed
Add Compute preview automation for pull requests
1 parent 2b96fbd commit 66e34a1

10 files changed

Lines changed: 669 additions & 4 deletions
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
name: compute preview
2+
3+
on:
4+
pull_request:
5+
types:
6+
- opened
7+
- reopened
8+
- synchronize
9+
delete:
10+
11+
permissions:
12+
contents: read
13+
issues: write
14+
15+
concurrency:
16+
group: ${{ github.workflow }}-${{ github.head_ref || github.event.ref }}
17+
cancel-in-progress: true
18+
19+
jobs:
20+
deploy-preview:
21+
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
22+
runs-on: ubuntu-latest
23+
steps:
24+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
25+
with:
26+
ref: ${{ github.event.pull_request.head.sha }}
27+
28+
- name: Setup pnpm
29+
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
30+
31+
- name: Setup Node
32+
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
33+
with:
34+
node-version-file: .node-version
35+
cache: pnpm
36+
37+
- name: Setup Bun
38+
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
39+
40+
- name: Install dependencies
41+
run: pnpm install --frozen-lockfile
42+
43+
- name: Build Compute artifact
44+
run: pnpm build:deploy
45+
46+
- name: Deploy preview
47+
id: deploy
48+
env:
49+
PRISMA_API_TOKEN: ${{ secrets.STUDIO_PREVIEW_COMPUTE_TOKEN }}
50+
PREVIEW_BRANCH_NAME: ${{ github.event.pull_request.head.ref }}
51+
run: node scripts/compute-preview/compute-preview-deploy.mjs
52+
53+
- name: Comment preview URL on PR
54+
env:
55+
GITHUB_REPOSITORY: ${{ github.repository }}
56+
GITHUB_TOKEN: ${{ github.token }}
57+
PREVIEW_BRANCH_NAME: ${{ github.event.pull_request.head.ref }}
58+
PREVIEW_PR_NUMBER: ${{ github.event.pull_request.number }}
59+
PREVIEW_SERVICE_NAME: ${{ steps.deploy.outputs.preview_service_name }}
60+
PREVIEW_SERVICE_URL: ${{ steps.deploy.outputs.preview_service_url }}
61+
PREVIEW_VERSION_URL: ${{ steps.deploy.outputs.preview_version_url }}
62+
run: node scripts/compute-preview/compute-preview-comment.mjs
63+
64+
destroy-preview:
65+
if: github.event_name == 'delete' && github.event.ref_type == 'branch'
66+
runs-on: ubuntu-latest
67+
steps:
68+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
69+
with:
70+
ref: ${{ github.event.repository.default_branch }}
71+
72+
- name: Setup Node
73+
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
74+
with:
75+
node-version-file: .node-version
76+
77+
- name: Setup Bun
78+
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
79+
80+
- name: Destroy preview service for deleted branch
81+
env:
82+
PRISMA_API_TOKEN: ${{ secrets.STUDIO_PREVIEW_COMPUTE_TOKEN }}
83+
PREVIEW_BRANCH_NAME: ${{ github.event.ref }}
84+
run: node scripts/compute-preview/compute-preview-destroy.mjs
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Compute Preview Deploys
2+
3+
This document is normative for branch-scoped Compute preview deployments.
4+
5+
## Purpose
6+
7+
Pull requests need a live Studio preview without manually creating and cleaning up
8+
Compute services for every branch.
9+
10+
The preview deployment path uses the existing `pnpm build:deploy` artifact and
11+
publishes it into the dedicated Compute project named `studio-preview`.
12+
13+
## Triggering
14+
15+
- A preview deploy MUST run when a pull request is opened, reopened, or updated
16+
with new commits.
17+
- Preview deploys MUST only run for branches inside this repository. Forked pull
18+
requests MUST NOT receive the Compute token.
19+
- A preview service MUST be destroyed when the corresponding Git branch is
20+
deleted.
21+
- Because the GitHub `delete` event is evaluated from the default branch
22+
workflow set, this workflow MUST be merged to `main` before branch-deletion
23+
cleanup becomes automatic for later branches.
24+
25+
## Service Naming
26+
27+
- Preview services MUST be keyed by the pull request branch name.
28+
- Because Compute service names need a filesystem- and URL-safe shape, the raw
29+
branch name MUST be normalized to a lowercase slug containing only
30+
alphanumeric segments separated by `-`.
31+
- If the normalized branch slug exceeds the Compute name budget, it MUST be
32+
truncated and keep a stable hash suffix so repeated deploys resolve to the
33+
same service.
34+
- The same normalization MUST be used for deploy and destroy flows.
35+
36+
## Deploy Flow
37+
38+
- The workflow MUST build the preview artifact with `pnpm build:deploy`.
39+
- The workflow MUST authenticate with Compute through the GitHub Actions secret
40+
`STUDIO_PREVIEW_COMPUTE_TOKEN`, exposed to the CLI as `PRISMA_API_TOKEN`.
41+
- The deploy helper MUST resolve the `studio-preview` Compute project by name at
42+
runtime instead of hardcoding an opaque service id.
43+
- If the branch preview service does not exist, the helper MUST create it in the
44+
project's default region.
45+
- If the service already exists, the helper MUST deploy a new version to that
46+
same service.
47+
- Deployments MUST use the published CLI entrypoint:
48+
`bunx @prisma/compute-cli@latest deploy --skip-build --path deploy --entrypoint bundle/server.bundle.js --http-port 8080 --env STUDIO_DEMO_PORT=8080`.
49+
50+
## PR Feedback
51+
52+
- Successful preview deploys MUST post the live service URL back to the pull
53+
request.
54+
- The PR comment MUST be sticky: later deploys for the same PR update the
55+
existing preview comment instead of creating duplicates.
56+
- The comment MUST include the original branch name plus the resolved Compute
57+
service name so any slug normalization stays visible.

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
- Add optional Prisma Streams setup support, wire the `ppg-dev` demo to Prisma Dev's Streams server, and show live stream names in a new sidebar `Streams` section.
1515
- Add a dedicated stream event view with infinite scrolling, expandable rows, and summary columns for time, key, indexed fields, preview text, and payload size.
1616
- Keep stream event counts live while a stream is open, and reveal newly arrived events in 50-row batches without snapping the current list.
17+
- Work around the current `@prisma/dev` Compute asset-resolution gap by copying stable PGlite runtime filenames into the deploy bundle and bundling the Prisma Streams local worker, so the packaged demo can boot correctly on Compute with WAL syncing still enabled.
18+
- Add automatic Compute preview deploys for pull requests, so branch builds land in the `studio-preview` project, comment their live URL on the PR, and clean themselves up when the branch is deleted.
1719

1820
## 0.27.3
1921

FEATURES.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ This gives users an accurate live model of the database and keeps table navigati
1313
## Deployable Prisma Postgres Demo
1414

1515
The local `ppg-dev` demo can be packaged into a Compute-ready artifact instead of requiring the repo checkout at runtime.
16-
The deploy builder precompiles the browser JS/CSS, injects those assets into the bundled server, and relies on `@prisma/dev`'s Bun runtime-asset manifest so PGlite's WASM, data, and extension archives are emitted automatically beside the server bundle.
17-
When that bundled demo also starts its embedded local Prisma Streams runtime, Studio now relies on the published `@prisma/streams-local` package to carry its own runtime tuning defaults instead of layering a second demo-specific memory policy on top.
16+
The deploy builder precompiles the browser JS/CSS, injects those assets into the bundled server, copies Prisma Dev's PGlite runtime assets into the bundle with stable filenames, and bundles the Prisma Streams worker into `touch/` so the Compute artifact can boot and keep WAL-to-stream syncing alive outside the repo checkout.
1817
The same demo entrypoint can also run against external development infrastructure through `pnpm demo:ppg -- --database-url <postgres-url> --streams-server-url <streams-url>`, or in streams-only mode through `pnpm demo:ppg -- --streams-server-url <streams-url>`. In those modes, Studio keeps serving the local shell and `/api/streams` proxy, but skips local Prisma Dev startup, local Streams startup, WAL wiring, and local seeding so you can point the demo at an already-running backend stack.
1918

2019
## Streams-Only Studio Shell
@@ -27,6 +26,14 @@ In that mode the shell hides schema selection, table navigation, and database-on
2726
Studio's local development workflow can temporarily replace the published npm `@prisma/dev` package with the sibling source package from `../team-expansion/dev/server`, while also swapping its `@prisma/streams-local` dependency over to a built local Streams checkout.
2827
That override stays opt-in, rebuilds from the sibling repos by default, and can be reverted without rewriting the tracked lockfile, so experimental Prisma Dev and Durable Streams work can stay local to one Studio checkout.
2928

29+
## Compute PR Preview Deploys
30+
31+
Pull requests can publish the current branch into the dedicated `studio-preview`
32+
Compute project without hand-creating services for each branch.
33+
The preview workflow derives a stable Compute-safe service name from the branch,
34+
reuses that service across later pushes, posts the live URL back to the PR, and
35+
destroys the preview service when the branch is deleted.
36+
3037
## Introspection Recovery and Retry
3138

3239
Startup introspection failures show retryable diagnostics in both the sidebar and the main table panel instead of pretending the database has no tables.

README.md

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -400,8 +400,46 @@ Revert to the published npm packages with `pnpm streams:use-npm`.
400400
`@prisma/dev` now emits its own PGlite runtime assets during Bun bundling, so
401401
plain `bun build` no longer needs `--packages external` just to keep Prisma
402402
Postgres dev working. For a source-free Compute artifact, use `pnpm build:deploy`:
403-
that path still prebuilds the browser JS/CSS and injects those assets into the
404-
server bundle so the deployed demo does not need the repo checkout at runtime.
403+
that path prebuilds the browser JS/CSS, injects those assets into the server
404+
bundle, and copies Prisma Dev's runtime assets into `deploy/bundle/` with
405+
stable filenames so the deployed demo does not need the repo checkout at
406+
runtime. It also Bun-bundles the Prisma Streams local worker into `deploy/touch/`
407+
so Compute can keep Prisma Dev's WAL-to-stream sidecar alive in the source-free artifact.
408+
409+
Deploy that artifact with:
410+
411+
```sh
412+
bunx @prisma/compute-cli deploy --skip-build \
413+
--path deploy \
414+
--entrypoint bundle/server.bundle.js \
415+
--http-port 8080 \
416+
--env STUDIO_DEMO_PORT=8080 \
417+
--service <service-id>
418+
```
419+
420+
## Compute Preview Deploys
421+
422+
This repo also maintains branch-scoped Compute previews for pull requests.
423+
424+
- `.github/workflows/compute-preview.yml` deploys the current PR branch into the
425+
dedicated `studio-preview` Compute project whenever a PR is opened,
426+
reopened, or updated with new commits.
427+
- The preview service name is derived from the branch name through a stable
428+
Compute-safe slug, so later pushes reuse the same service instead of creating
429+
duplicates.
430+
- The workflow updates one sticky PR comment with the live preview URL after a
431+
successful deploy.
432+
- When a Git branch is deleted, the same workflow destroys the matching preview
433+
service.
434+
435+
The workflow expects the GitHub Actions secret
436+
`STUDIO_PREVIEW_COMPUTE_TOKEN`, which should contain a Compute API token for the
437+
`studio-preview` project.
438+
439+
For branch-deletion cleanup to happen automatically, the workflow must be
440+
present on the default branch. In practice that means merging the preview
441+
workflow to `main` once, after which later PR branches will get full automatic
442+
create/update/delete behavior.
405443

406444
## Development Workflow
407445

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#!/usr/bin/env node
2+
3+
import {
4+
PREVIEW_COMMENT_MARKER,
5+
buildPreviewCommentBody,
6+
} from "./compute-preview-utils.mjs";
7+
8+
async function main() {
9+
const githubToken = getRequiredEnv("GITHUB_TOKEN");
10+
const repository = getRequiredEnv("GITHUB_REPOSITORY");
11+
const prNumber = getRequiredEnv("PREVIEW_PR_NUMBER");
12+
const branchName = getRequiredEnv("PREVIEW_BRANCH_NAME");
13+
const serviceName = getRequiredEnv("PREVIEW_SERVICE_NAME");
14+
const serviceUrl = getRequiredEnv("PREVIEW_SERVICE_URL");
15+
const versionUrl = process.env.PREVIEW_VERSION_URL?.trim();
16+
const [owner, repo] = repository.split("/");
17+
18+
if (!owner || !repo) {
19+
throw new Error(`Invalid GITHUB_REPOSITORY value "${repository}".`);
20+
}
21+
22+
const body = buildPreviewCommentBody({
23+
branchName,
24+
serviceName,
25+
serviceUrl,
26+
versionUrl,
27+
});
28+
const comments = await githubRequest({
29+
githubToken,
30+
method: "GET",
31+
path: `/repos/${owner}/${repo}/issues/${prNumber}/comments?per_page=100`,
32+
});
33+
const existingComment = comments.find((comment) =>
34+
typeof comment.body === "string" &&
35+
comment.body.includes(PREVIEW_COMMENT_MARKER),
36+
);
37+
38+
if (existingComment) {
39+
await githubRequest({
40+
body: { body },
41+
githubToken,
42+
method: "PATCH",
43+
path: `/repos/${owner}/${repo}/issues/comments/${existingComment.id}`,
44+
});
45+
return;
46+
}
47+
48+
await githubRequest({
49+
body: { body },
50+
githubToken,
51+
method: "POST",
52+
path: `/repos/${owner}/${repo}/issues/${prNumber}/comments`,
53+
});
54+
}
55+
56+
async function githubRequest(args) {
57+
const { body, githubToken, method, path } = args;
58+
const response = await fetch(`https://api.github.com${path}`, {
59+
body: body ? JSON.stringify(body) : undefined,
60+
headers: {
61+
Accept: "application/vnd.github+json",
62+
Authorization: `Bearer ${githubToken}`,
63+
"Content-Type": "application/json",
64+
"User-Agent": "studio-compute-preview",
65+
"X-GitHub-Api-Version": "2022-11-28",
66+
},
67+
method,
68+
});
69+
70+
if (!response.ok) {
71+
throw new Error(
72+
`GitHub API request failed (${response.status} ${response.statusText}): ${await response.text()}`,
73+
);
74+
}
75+
76+
return method === "GET" ? await response.json() : null;
77+
}
78+
79+
function getRequiredEnv(name) {
80+
const value = process.env[name]?.trim();
81+
82+
if (!value) {
83+
throw new Error(`Missing required environment variable ${name}.`);
84+
}
85+
86+
return value;
87+
}
88+
89+
await main();

0 commit comments

Comments
 (0)