Skip to content

Commit 574175e

Browse files
ankur-archclaude
andcommitted
fix(docs): correct Deploy Button contract details from Console audit
- prisma.compute.json example uses the real schema (app.httpPort, not top-level name/port — the old example failed validation verbatim). - Document the validation limits (file count/size caps, no workflows, no symlinks/submodules, single-app, region key rejected). - Match the Console's actual copy for the name-conflict error, the 15-minute authorization window, commit display and pinning behavior, the workspace picker, and the private-by-default visibility. - Qualify push-to-recover advice by whether the project was created. - Generator: project-name field shows its own validation hint, copy buttons get distinct accessible names, HTML snippet escapes ampersands in the href. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 7dc58b6 commit 574175e

23 files changed

Lines changed: 557 additions & 339 deletions

File tree

apps/docs/content/docs/compute/deploy-button.mdx

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,34 @@ The Deploy Button is in early access. It supports public GitHub repositories; su
2121
Any public GitHub repository works with the Deploy Button when it meets this minimal contract:
2222

2323
- **Public GitHub repository.** The button deploys the repository's default branch.
24-
- **A `prisma.compute.json` file at the repository root.** This is the same file Prisma Compute reads for any connected repository — it names the app and sets its HTTP port. Don't set a region in it; the person deploying picks the region, and it's used for both the database and the app. See [configuration](/compute/configuration).
25-
- **A TypeScript project with a `package.json` and a lockfile** (`bun.lock`, `package-lock.json`, `pnpm-lock.yaml`, or `yarn.lock`) at the root. If it builds on Prisma Compute, it deploys.
24+
- **A `prisma.compute.json` file at the repository root.** This is the same file Prisma Compute reads for any connected repository — it names the app and sets its HTTP port. It must describe a single app, and it must not set a `region`: the person deploying picks the region, which is used for both the database and the app. See [configuration](/compute/configuration).
25+
- **A TypeScript project with a `package.json` and a lockfile** (`bun.lock`, `package-lock.json`, `pnpm-lock.yaml`, or `yarn.lock`) at the root.
2626

27-
That's it — no registration, API key, or approval process. A minimal working example you can copy is [`prisma/compute-deploy-example`](https://github.com/prisma/compute-deploy-example), whose README carries its own Deploy Button.
27+
The repository is validated before it is copied, so a few shapes are rejected:
28+
29+
- More than 100 files, a file over 5 MB, or more than 25 MB in total.
30+
- GitHub Actions workflows (anything under `.github/workflows/`).
31+
- Symlinks or Git submodules.
32+
33+
There is no registration, API key, or approval process. A minimal working example you can copy is [`prisma/compute-deploy-example`](https://github.com/prisma/compute-deploy-example), whose README carries its own Deploy Button.
2834

2935
A minimal `prisma.compute.json`:
3036

3137
```json
3238
{
33-
"name": "my-app",
34-
"port": 3000
39+
"app": {
40+
"name": "my-app",
41+
"httpPort": 3000
42+
}
3543
}
3644
```
3745

3846
## How it works
3947

40-
1. **Click the button.** The user lands on the clone page in the Prisma Console, which always shows exactly what will be deployed: your repository's name and the resolved commit, linked back to the source on GitHub. If they're signed out, they sign in first and return to the same page automatically.
41-
2. **Choose where it goes.** They pick a GitHub account or organization, a name and visibility for their new repository, and a deployment region. If the Prisma GitHub App isn't installed yet, the flow walks them through installing it.
48+
1. **Click the button.** The user lands on the clone page in the Prisma Console, which shows what will be deployed: your repository's name and, once a GitHub account is connected, the resolved commit linked back to the source on GitHub. If they're signed out, they sign in first and return to the same page automatically.
49+
2. **Choose where it goes.** They pick a workspace (when they have more than one), a GitHub account or organization, a name and visibility for their new repository (private by default), and a deployment region. If the Prisma GitHub App isn't installed yet, the flow walks them through installing it.
4250
3. **Authorize once.** GitHub asks the user to authorize the repository creation. This authorization is used for that single deployment and is not stored.
43-
4. **Prisma sets everything up.** Your code is copied — at the exact commit shown on the clone page — into a new repository under the user's account. A project with a Prisma Postgres database is provisioned, the database connection is configured automatically, and the first deployment starts.
51+
4. **Prisma sets everything up.** Your code is copied — pinned to the default-branch commit resolved when they click Deploy — into a new repository under the user's account. A project with a Prisma Postgres database is provisioned, the database connection is configured automatically, and the first deployment starts.
4452
5. **Push to deploy.** The new repository is connected to the project, so every later push deploys through the normal Git workflow.
4553

4654
The user owns the copy and the project. Prisma adds no credentials or platform files to the repository — database connection settings are managed in the Console, not committed to code.
@@ -69,9 +77,9 @@ https://console.prisma.io/new/clone?repository-url=<your repository URL>
6977
| --- | --- | --- |
7078
| `repository-url` | Yes | The public GitHub repository to deploy, in the form `https://github.com/owner/repo`. URL-encode it when composing the link. |
7179
| `project-name` | No | Prefills the name of the repository the user creates (they can change it). Defaults to your repository's name. |
72-
| `utm_source` | No | Attribution for where the click came from, for example `github-readme`. |
73-
| `utm_medium` | No | Attribution for the kind of placement, for example `button`. |
74-
| `utm_campaign` | No | Attribution for a specific campaign or launch. |
80+
| `utm_source` | No | Attribution for where the click came from, for example `github-readme`. This is the value Prisma's own funnel reporting records. |
81+
| `utm_medium` | No | Attribution for the kind of placement, for example `button`. Carried on the link for your own analytics. |
82+
| `utm_campaign` | No | Attribution for a specific campaign or launch. Carried on the link for your own analytics. |
7583

7684
Example with a project name and attribution:
7785

@@ -96,7 +104,7 @@ A complete Markdown example for a README:
96104
The same button in HTML:
97105

98106
```html
99-
<a href="https://console.prisma.io/new/clone?repository-url=https%3A%2F%2Fgithub.qkg1.top%2Facme%2Fshop-api&utm_source=github-readme">
107+
<a href="https://console.prisma.io/new/clone?repository-url=https%3A%2F%2Fgithub.qkg1.top%2Facme%2Fshop-api&amp;utm_source=github-readme">
100108
<img src="https://www.prisma.io/docs/img/deploy-button.svg" alt="Deploy with Prisma" width="172" height="36" />
101109
</a>
102110
```
@@ -107,12 +115,12 @@ The same button in HTML:
107115

108116
**The clone page says the repository must be public.** The button only supports public source repositories today. The user's *copy* can still be private — that's their choice in the deploy form.
109117

110-
**"A repository with this name already exists."** The user already has a repository with the chosen name. They can pick a different name in the form and deploy again.
118+
**"The repository changed before Prisma could copy the template. Choose another repository name and try again."** The chosen name usually collides with a repository the user already has. Picking a different name in the form and deploying again resolves it.
111119

112-
**GitHub authorization was canceled or expired.** The authorization step must be completed within a few minutes. Returning to the clone page and clicking Deploy again starts a fresh attempt.
120+
**GitHub authorization was canceled or expired.** The authorization step must be completed within 15 minutes. Returning to the clone page and clicking Deploy again starts a fresh attempt.
113121

114122
**The GitHub account isn't listed.** The Prisma GitHub App isn't installed on that account or organization. The "Connect GitHub" option in the deploy form starts the installation; after completing it on GitHub, the user continues from the clone page.
115123

116-
**The repository was created, but the deployment didn't start.** The repository and project are preserved — nothing is deleted on a partial failure. Pushing any commit to the new repository's default branch triggers a deployment through the normal Git flow. See [deployments](/compute/deployments).
124+
**The repository was created, but the deployment didn't start.** Nothing is deleted on a partial failure. If the Prisma project was created and connected to the repository, pushing any commit to the new repository's default branch triggers a deployment through the normal Git flow. If the failure happened before the project existed, return to the clone page and deploy again with a different repository name. See [deployments](/compute/deployments).
117125

118126
**The app deployed but shows an error page.** Check the build and runtime logs on the project's deployment page, confirm the database finished provisioning in the Console, and verify the app listens on the port declared in `prisma.compute.json`. The [FAQ](/compute/faq) covers common runtime issues.
Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,5 @@
11
{
22
"title": "Frameworks",
33
"defaultOpen": true,
4-
"pages": [
5-
"nextjs",
6-
"astro",
7-
"nuxt",
8-
"sveltekit",
9-
"tanstack-start",
10-
"nestjs",
11-
"hono",
12-
"elysia"
13-
]
4+
"pages": ["nextjs", "astro", "nuxt", "sveltekit", "tanstack-start", "nestjs", "hono", "elysia"]
145
}
Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
11
{
22
"title": "Next",
3-
"pages": [
4-
"index",
5-
"frameworks",
6-
"runtimes",
7-
"upgrade-prisma-orm"
8-
]
3+
"pages": ["index", "frameworks", "runtimes", "upgrade-prisma-orm"]
94
}
Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
{
22
"title": "Upgrade Prisma ORM",
3-
"pages": [
4-
"mongodb"
5-
]
3+
"pages": ["mongodb"]
64
}
Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
{
22
"title": "Prisma Migrate",
33
"defaultOpen": true,
4-
"pages": [
5-
"index",
6-
"getting-started",
7-
"...understanding-prisma-migrate",
8-
"workflows"
9-
]
4+
"pages": ["index", "getting-started", "...understanding-prisma-migrate", "workflows"]
105
}
Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
{
22
"title": "Prisma Schema",
33
"defaultOpen": true,
4-
"pages": [
5-
"overview",
6-
"data-model",
7-
"introspection",
8-
"postgresql-extensions"
9-
]
4+
"pages": ["overview", "data-model", "introspection", "postgresql-extensions"]
105
}

apps/docs/cspell.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -439,8 +439,6 @@
439439
"pattern": "/videoId=\"[A-Za-z0-9_-]{6,}\"/g"
440440
}
441441
],
442-
"ignoreRegExpList": [
443-
"mdxVideoIdAttribute"
444-
],
442+
"ignoreRegExpList": ["mdxVideoIdAttribute"],
445443
"ignorePaths": []
446444
}

apps/docs/scripts/audit-redirects.mjs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,10 @@ function extractFrontmatterUrl(raw) {
110110

111111
const value = match[1].trim();
112112

113-
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
113+
if (
114+
(value.startsWith('"') && value.endsWith('"')) ||
115+
(value.startsWith("'") && value.endsWith("'"))
116+
) {
114117
return value.slice(1, -1);
115118
}
116119

@@ -151,9 +154,7 @@ async function collectDocsRoutes() {
151154
async function main() {
152155
const strict = process.argv.includes("--strict");
153156
const routes = await collectDocsRoutes();
154-
const vercelConfig = JSON.parse(
155-
await readFile(path.join(docsRoot, "vercel.json"), "utf8"),
156-
);
157+
const vercelConfig = JSON.parse(await readFile(path.join(docsRoot, "vercel.json"), "utf8"));
157158

158159
const missing = [];
159160
const broad = [];
@@ -174,7 +175,9 @@ async function main() {
174175
}
175176
}
176177

177-
console.log(`Checked ${vercelConfig.redirects.length} redirects against ${routes.size} docs routes.`);
178+
console.log(
179+
`Checked ${vercelConfig.redirects.length} redirects against ${routes.size} docs routes.`,
180+
);
178181

179182
if (missing.length > 0) {
180183
console.log("\nMissing redirect destinations:");

apps/docs/scripts/generate-docs.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ function escapeRegExp(s: string) {
3030

3131
// Remove compact single-line redirect entries by source without touching the rest of the file.
3232
// Returns the modified string and the count of lines actually removed.
33-
function removeRedirectLines(raw: string, sources: Set<string>): { result: string; removed: number } {
33+
function removeRedirectLines(
34+
raw: string,
35+
sources: Set<string>,
36+
): { result: string; removed: number } {
3437
let removed = 0;
3538
for (const source of sources) {
3639
const pattern = new RegExp(
@@ -217,9 +220,7 @@ async function main() {
217220
{
218221
const removedUrls = [...oldUrlToFile.keys()].filter((url) => !newUrlToRelPath.has(url));
219222
// Endpoints that were previously redirected but are now live again
220-
const restoredSources = new Set(
221-
[...newUrlToRelPath.keys()].map((url) => `/docs${url}`),
222-
);
223+
const restoredSources = new Set([...newUrlToRelPath.keys()].map((url) => `/docs${url}`));
223224

224225
const toAdd = removedUrls.map((url) => {
225226
const tag = url.split("/")[3];
@@ -244,7 +245,10 @@ async function main() {
244245
// Prepend new entries for removed endpoints, compact single-line format
245246
if (toAdd.length > 0) {
246247
const lines = toAdd
247-
.map((r) => ` { "source": "${r.source}", "destination": "${r.destination}", "permanent": true },`)
248+
.map(
249+
(r) =>
250+
` { "source": "${r.source}", "destination": "${r.destination}", "permanent": true },`,
251+
)
248252
.join("\n");
249253
raw = raw.replace(/("redirects"\s*:\s*\[)/, `$1\n${lines}`);
250254
console.log(`Prepended ${toAdd.length} redirects to vercel.json`);
@@ -278,9 +282,7 @@ async function main() {
278282

279283
// Sync endpoints/meta.json: keep existing order, append new dirs alphabetically, [experimental] last
280284
const afterDirEntries = await readdir(endpointsDir, { withFileTypes: true });
281-
const currentTagDirs = new Set(
282-
afterDirEntries.filter((d) => d.isDirectory()).map((d) => d.name),
283-
);
285+
const currentTagDirs = new Set(afterDirEntries.filter((d) => d.isDirectory()).map((d) => d.name));
284286

285287
const currentMeta = JSON.parse(await readFile(metaJsonPath, "utf-8"));
286288
const existingPages: string[] = currentMeta.pages ?? [];

apps/docs/src/app/api/search/route.ts

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -36,29 +36,31 @@ function slugger(value: string): string {
3636
function removeMd(md: string): string {
3737
if (typeof md !== "string") return "";
3838
try {
39-
return md
40-
.replace(/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/gm, "")
41-
.replace(/^([\s\t]*)([*\-+]|\d+\.)\s+/gm, "$1")
42-
.replace(/\n={2,}/g, "\n")
43-
.replace(/^[=-]{2,}\s*$/gm, "")
44-
.replace(/~{3}.*\n/g, "")
45-
.replace(/```[^\n]*\n([\s\S]*?)```/g, (_: string, c: string) => c.trim())
46-
.replace(/~~/g, "")
47-
.replace(/\[\^.+?\](: .*?$)?/g, "")
48-
.replace(/\s{0,2}\[.*?\]: .*?$/g, "")
49-
.replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/gm, "")
50-
.replace(/!\[(.*?)\][[(].*?[\])]/g, "")
51-
.replace(/\[([\s\S]*?)\]\s*[([].*?[)\]]/g, "$1")
52-
.replace(/^(\n)?\s{0,3}>\s?/gm, "$1")
53-
.replace(/^(\n)?\s{0,}#{1,6}\s*( (.+))? +#+$|^(\n)?\s{0,}#{1,6}\s*( (.+))?$/gm, "$1$3$4$6")
54-
.replace(/([*]+)(\S)(.*?\S)??\1/g, "$2$3")
55-
.replace(/(^|\W)([_]+)(\S)(.*?\S)??\2($|\W)/g, "$1$3$4$5")
56-
.replace(/(`{3,})(.*?)\1/gm, "$2")
57-
.replace(/`(.+?)`/g, "$1")
58-
.replace(/~(.*?)~/g, "$1")
59-
// Remove any remaining angle brackets so malformed HTML-like input
60-
// cannot survive as executable-looking text in search results.
61-
.replace(/[<>]/g, "");
39+
return (
40+
md
41+
.replace(/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/gm, "")
42+
.replace(/^([\s\t]*)([*\-+]|\d+\.)\s+/gm, "$1")
43+
.replace(/\n={2,}/g, "\n")
44+
.replace(/^[=-]{2,}\s*$/gm, "")
45+
.replace(/~{3}.*\n/g, "")
46+
.replace(/```[^\n]*\n([\s\S]*?)```/g, (_: string, c: string) => c.trim())
47+
.replace(/~~/g, "")
48+
.replace(/\[\^.+?\](: .*?$)?/g, "")
49+
.replace(/\s{0,2}\[.*?\]: .*?$/g, "")
50+
.replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/gm, "")
51+
.replace(/!\[(.*?)\][[(].*?[\])]/g, "")
52+
.replace(/\[([\s\S]*?)\]\s*[([].*?[)\]]/g, "$1")
53+
.replace(/^(\n)?\s{0,3}>\s?/gm, "$1")
54+
.replace(/^(\n)?\s{0,}#{1,6}\s*( (.+))? +#+$|^(\n)?\s{0,}#{1,6}\s*( (.+))?$/gm, "$1$3$4$6")
55+
.replace(/([*]+)(\S)(.*?\S)??\1/g, "$2$3")
56+
.replace(/(^|\W)([_]+)(\S)(.*?\S)??\2($|\W)/g, "$1$3$4$5")
57+
.replace(/(`{3,})(.*?)\1/gm, "$2")
58+
.replace(/`(.+?)`/g, "$1")
59+
.replace(/~(.*?)~/g, "$1")
60+
// Remove any remaining angle brackets so malformed HTML-like input
61+
// cannot survive as executable-looking text in search results.
62+
.replace(/[<>]/g, "")
63+
);
6264
} catch {
6365
return md;
6466
}

0 commit comments

Comments
 (0)