Skip to content
Draft
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
24 changes: 18 additions & 6 deletions .github/workflows/compute-preview.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
name: compute preview

on:
push:
branches:
- main
pull_request:
types:
- opened
Expand All @@ -19,12 +22,15 @@ concurrency:

jobs:
deploy-preview:
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
if: >-
(github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository) ||
(github.event_name == 'push' && github.ref == 'refs/heads/main')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.pull_request.head.sha }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}

- name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
Expand Down Expand Up @@ -52,21 +58,27 @@ jobs:
- name: Deploy preview
id: deploy
env:
ANTHROPIC_API_KEY: ${{ secrets.STUDIO_PREVIEW_ANTHROPIC_API_KEY }}
PRISMA_API_TOKEN: ${{ secrets.STUDIO_PREVIEW_COMPUTE_TOKEN }}
PREVIEW_BRANCH_NAME: ${{ github.event.pull_request.head.ref }}
PREVIEW_BRANCH_NAME: ${{ github.event.pull_request.head.ref || github.ref_name }}
run: node scripts/compute-preview/compute-preview-deploy.mjs

- name: Comment preview URL on PR
if: github.event_name == 'pull_request'
env:
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_TOKEN: ${{ github.token }}
PREVIEW_BRANCH_NAME: ${{ github.event.pull_request.head.ref }}
PREVIEW_PR_NUMBER: ${{ github.event.pull_request.number }}
PREVIEW_SERVICE_NAME: ${{ steps.deploy.outputs.preview_service_name }}
PREVIEW_SERVICE_URL: ${{ steps.deploy.outputs.preview_service_url }}
PREVIEW_VERSION_URL: ${{ steps.deploy.outputs.preview_version_url }}
run: node scripts/compute-preview/compute-preview-comment.mjs

- name: Summarize main preview URL
if: github.event_name == 'push'
run: |
echo "### Main Compute preview" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "${{ steps.deploy.outputs.preview_service_url }}" >> "$GITHUB_STEP_SUMMARY"

destroy-preview:
if: github.event_name == 'delete' && github.event.ref_type == 'branch'
runs-on: ubuntu-latest
Expand Down
34 changes: 26 additions & 8 deletions Architecture/compute-preview-deploy.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# Compute Preview Deploys

This document is normative for branch-scoped Compute preview deployments.
This document is normative for branch-scoped and latest-main Compute preview
deployments.

## Purpose

Pull requests need a live Studio preview without manually creating and cleaning up
Compute services for every branch.
Pull requests and the latest `main` branch need live Studio previews without
manually creating and cleaning up Compute services.

The preview deployment path uses the existing `pnpm build:deploy` artifact and
publishes it into the dedicated Compute project named `studio-preview`.
Expand All @@ -14,6 +15,7 @@ publishes it into the dedicated Compute project named `studio-preview`.

- A preview deploy MUST run when a pull request is opened, reopened, or updated
with new commits.
- A stable `main` preview deploy MUST run whenever `main` receives new commits.
- Preview deploys MUST only run for branches inside this repository. Forked pull
requests MUST NOT receive the Compute token.
- A preview service MUST be destroyed when the corresponding Git branch is
Expand All @@ -24,7 +26,9 @@ publishes it into the dedicated Compute project named `studio-preview`.

## Service Naming

- Preview services MUST be keyed by the pull request branch name.
- Pull request preview services MUST be keyed by the pull request branch name.
- The latest-main preview service MUST be keyed by the literal branch name
`main`.
- Because Compute service names need a filesystem- and URL-safe shape, the raw
branch name MUST be normalized to a lowercase slug containing only
alphanumeric segments separated by `-`.
Expand All @@ -45,14 +49,28 @@ publishes it into the dedicated Compute project named `studio-preview`.
- If the service already exists, the helper MUST deploy a new version to that
same service.
- Deployments MUST use the published CLI entrypoint:
- `bunx @prisma/compute-cli@latest deploy --skip-build --path deploy --entrypoint bundle/compute-entrypoint.js --http-port 8080`.
- The Compute artifact MUST include `bundle/compute-entrypoint.js`, which defaults `STUDIO_DEMO_PORT` to `8080` before importing `bundle/server.bundle.js`. Preview deploys MUST NOT pass runtime environment variables through the Compute CLI while its deploy API rejects the CLI's `envVars` request key.
`bunx @prisma/compute-cli@latest deploy --skip-build --path deploy --entrypoint bundle/compute-entrypoint.js --http-port 8080`.
- The Compute artifact MUST include `bundle/compute-entrypoint.js`, which
defaults `STUDIO_DEMO_PORT` to `8080` before importing
`bundle/server.bundle.js`.
- Preview deploys MUST pass runtime environment through a temporary Compute CLI
env file rather than embedding secrets into the artifact.
- Preview runtime env MUST include `STUDIO_DEMO_PORT=8080`,
`STUDIO_DEMO_AI_ENABLED=true`, and `ANTHROPIC_API_KEY` from the GitHub
Actions secret `STUDIO_PREVIEW_ANTHROPIC_API_KEY`.

## Main Preview

- The stable preview for the latest `main` MUST use `main` as the service key so
the Compute service URL remains stable across pushes.
- Successful `main` preview deploys SHOULD write the live service URL to the
GitHub Actions job summary.

## PR Feedback

- Successful preview deploys MUST post the live service URL back to the pull
request.
- The PR comment MUST be sticky: later deploys for the same PR update the
existing preview comment instead of creating duplicates.
- The comment MUST include the original branch name plus the resolved Compute
service name so any slug normalization stays visible.
- The visible PR comment content MUST include only one preview URL. Branch,
service, and version identifiers MUST stay out of the visible comment body.
10 changes: 7 additions & 3 deletions Architecture/demo-compute-bundling.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@ It is responsible for:
4. copying Prisma Dev runtime assets into `bundle/` with their stable filenames
5. bundling Prisma Streams local's worker into `touch/processor_worker.js`
6. copying the worker's vendored `hash_vendor/` files into `touch/`
7. writing a self-contained output directory whose entrypoint is `bundle/server.bundle.js`
7. writing a self-contained output directory whose Compute entrypoint is
`bundle/compute-entrypoint.js`
8. writing `bundle/compute-entrypoint.js` so it defaults `STUDIO_DEMO_PORT` to
`8080` before importing `bundle/server.bundle.js`

## Prisma Dev Runtime Assets

`@prisma/dev@0.24.6` exposes a Bun runtime-asset manifest for PGlite and also
`@prisma/dev@0.24.13` exposes a Bun runtime-asset manifest for PGlite and also
exports `copyPrismaDevRuntimeAssets()`.

That means when `build-compute.ts` bundles `demo/ppg-dev/server.ts` with Bun:
Expand All @@ -49,7 +52,8 @@ hashed Bun-emitted assets and the canonical names.

For the Compute artifact that means:

- the server entrypoint stays at `deploy/bundle/server.bundle.js`
- the Compute entrypoint stays at `deploy/bundle/compute-entrypoint.js`
- the bundled server stays at `deploy/bundle/server.bundle.js`
- stable PGlite assets live in `deploy/bundle/`
- the Streams worker must live at `deploy/touch/processor_worker.js`
- the worker's vendored hashing modules must live at `deploy/touch/hash_vendor/`
Expand Down
4 changes: 4 additions & 0 deletions FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ Compute project without hand-creating services for each branch.
The preview workflow derives a stable Compute-safe service name from the branch,
reuses that service across later pushes, posts the live URL back to the PR, and
destroys the preview service when the branch is deleted.
The same workflow deploys the latest `main` branch to a stable `main` Compute
service URL, and preview deployments carry the configured Anthropic key so the
demo's AI filtering, SQL generation, visualization, and Query Insights
recommendation features are available in previews.

## Introspection Recovery and Retry

Expand Down
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -676,12 +676,17 @@ This repo also maintains branch-scoped Compute previews for pull requests.
duplicates.
- The workflow updates one sticky PR comment with the live preview URL after a
successful deploy.
- Pushes to `main` deploy the latest default-branch build to the stable `main`
Compute service and write that service URL to the GitHub Actions job summary.
- When a Git branch is deleted, the same workflow destroys the matching preview
service.

The workflow expects the GitHub Actions secret
`STUDIO_PREVIEW_COMPUTE_TOKEN`, which should contain a Compute API token for the
`studio-preview` project.
The workflow expects these GitHub Actions secrets:

- `STUDIO_PREVIEW_COMPUTE_TOKEN`, a Compute API token for the `studio-preview`
project
- `STUDIO_PREVIEW_ANTHROPIC_API_KEY`, the Anthropic key used to enable Studio AI
features in Compute previews

For branch-deletion cleanup to happen automatically, the workflow must be
present on the default branch. In practice that means merging the preview
Expand Down
6 changes: 0 additions & 6 deletions scripts/compute-preview/compute-preview-comment.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,15 @@ async function main() {
const githubToken = getRequiredEnv("GITHUB_TOKEN");
const repository = getRequiredEnv("GITHUB_REPOSITORY");
const prNumber = getRequiredEnv("PREVIEW_PR_NUMBER");
const branchName = getRequiredEnv("PREVIEW_BRANCH_NAME");
const serviceName = getRequiredEnv("PREVIEW_SERVICE_NAME");
const serviceUrl = getRequiredEnv("PREVIEW_SERVICE_URL");
const versionUrl = process.env.PREVIEW_VERSION_URL?.trim();
const [owner, repo] = repository.split("/");

if (!owner || !repo) {
throw new Error(`Invalid GITHUB_REPOSITORY value "${repository}".`);
}

const body = buildPreviewCommentBody({
branchName,
serviceName,
serviceUrl,
versionUrl,
});
const comments = await githubRequest({
githubToken,
Expand Down
69 changes: 48 additions & 21 deletions scripts/compute-preview/compute-preview-deploy.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,27 @@

import { execFile } from "node:child_process";
import { appendFileSync } from "node:fs";
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { promisify } from "node:util";

import {
PREVIEW_PROJECT_NAME,
buildComputeDeployArgs,
buildPreviewDeployResult,
buildPreviewRuntimeEnv,
findNamedProject,
findNamedService,
formatDotenvFile,
sanitizeComputeServiceName,
} from "./compute-preview-utils.mjs";

const execFileAsync = promisify(execFile);

async function main() {
const branchName = getRequiredEnv("PREVIEW_BRANCH_NAME");
const anthropicApiKey = getRequiredEnv("ANTHROPIC_API_KEY");
const projectName = process.env.PREVIEW_PROJECT_NAME ?? PREVIEW_PROJECT_NAME;
const deployPath = process.env.PREVIEW_DEPLOY_PATH ?? "deploy";
const entrypoint =
Expand All @@ -28,32 +36,49 @@ async function main() {
region: project.defaultRegion ?? "eu-west-3",
serviceName,
});
const deployResult = await runComputeJson([
"deploy",
"--skip-build",
"--path",
deployPath,
"--entrypoint",
entrypoint,
"--http-port",
httpPort,
"--service",
service.id,
]);
const runtimeEnvDir = await mkdtemp(
join(tmpdir(), "studio-compute-preview-env-"),
);
const runtimeEnvPath = join(runtimeEnvDir, ".env");
let deployResult;

try {
await writeFile(
runtimeEnvPath,
formatDotenvFile(
buildPreviewRuntimeEnv({
anthropicApiKey,
httpPort,
}),
),
{ mode: 0o600 },
);
deployResult = await runComputeJson(
buildComputeDeployArgs({
deployPath,
entrypoint,
envFilePath: runtimeEnvPath,
httpPort,
serviceId: service.id,
}),
);
} finally {
await rm(runtimeEnvDir, { force: true, recursive: true });
}

const result = {
const result = buildPreviewDeployResult({
branchName,
projectId: project.id,
region: project.defaultRegion ?? "eu-west-3",
serviceId: service.id,
project,
deployResult,
service,
serviceName,
serviceUrl: deployResult.serviceEndpointDomain,
versionId: deployResult.versionId,
versionUrl: deployResult.versionEndpointDomain,
};
});

writeOutputs(result);
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
process.stdout.write(
`${JSON.stringify({ previewUrl: result.serviceUrl }, null, 2)}\n`,
);
}

async function resolveProject(projectName) {
Expand Down Expand Up @@ -139,7 +164,9 @@ function writeOutputs(result) {
preview_service_url: result.serviceUrl,
preview_version_id: result.versionId,
preview_version_url: result.versionUrl,
}).map(([key, value]) => `${key}=${value}`);
})
.filter(([, value]) => value != null)
.map(([key, value]) => `${key}=${value}`);

appendFileSync(outputPath, `${lines.join("\n")}\n`);
}
Expand Down
Loading