@polardbx/pxbaas 0.0.0-stage → 0.1.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -2
- package/bin/pxbaas.js +13 -0
- package/dist/app/preflight.d.ts +38 -0
- package/dist/app/preflight.d.ts.map +1 -0
- package/dist/app/preflight.js +79 -0
- package/dist/app/preflight.js.map +1 -0
- package/dist/app/spec-lint.d.ts +45 -0
- package/dist/app/spec-lint.d.ts.map +1 -0
- package/dist/app/spec-lint.js +407 -0
- package/dist/app/spec-lint.js.map +1 -0
- package/dist/app/templates.d.ts +143 -0
- package/dist/app/templates.d.ts.map +1 -0
- package/dist/app/templates.js +151 -0
- package/dist/app/templates.js.map +1 -0
- package/dist/cli/root.d.ts +3 -0
- package/dist/cli/root.d.ts.map +1 -0
- package/dist/cli/root.js +69 -0
- package/dist/cli/root.js.map +1 -0
- package/dist/commands/app/index.d.ts +8 -0
- package/dist/commands/app/index.d.ts.map +1 -0
- package/dist/commands/app/index.js +1476 -0
- package/dist/commands/app/index.js.map +1 -0
- package/dist/commands/auth/index.d.ts +3 -0
- package/dist/commands/auth/index.d.ts.map +1 -0
- package/dist/commands/auth/index.js +198 -0
- package/dist/commands/auth/index.js.map +1 -0
- package/dist/commands/completion/index.d.ts +3 -0
- package/dist/commands/completion/index.d.ts.map +1 -0
- package/dist/commands/completion/index.js +10 -0
- package/dist/commands/completion/index.js.map +1 -0
- package/dist/commands/data/index.d.ts +3 -0
- package/dist/commands/data/index.d.ts.map +1 -0
- package/dist/commands/data/index.js +245 -0
- package/dist/commands/data/index.js.map +1 -0
- package/dist/commands/db/index.d.ts +15 -0
- package/dist/commands/db/index.d.ts.map +1 -0
- package/dist/commands/db/index.js +389 -0
- package/dist/commands/db/index.js.map +1 -0
- package/dist/commands/doctor/index.d.ts +3 -0
- package/dist/commands/doctor/index.d.ts.map +1 -0
- package/dist/commands/doctor/index.js +305 -0
- package/dist/commands/doctor/index.js.map +1 -0
- package/dist/commands/functions/delete.d.ts +3 -0
- package/dist/commands/functions/delete.d.ts.map +1 -0
- package/dist/commands/functions/delete.js +55 -0
- package/dist/commands/functions/delete.js.map +1 -0
- package/dist/commands/functions/deploy.d.ts +3 -0
- package/dist/commands/functions/deploy.d.ts.map +1 -0
- package/dist/commands/functions/deploy.js +118 -0
- package/dist/commands/functions/deploy.js.map +1 -0
- package/dist/commands/functions/index.d.ts +3 -0
- package/dist/commands/functions/index.d.ts.map +1 -0
- package/dist/commands/functions/index.js +15 -0
- package/dist/commands/functions/index.js.map +1 -0
- package/dist/commands/functions/list.d.ts +6 -0
- package/dist/commands/functions/list.d.ts.map +1 -0
- package/dist/commands/functions/list.js +63 -0
- package/dist/commands/functions/list.js.map +1 -0
- package/dist/commands/functions/new.d.ts +3 -0
- package/dist/commands/functions/new.d.ts.map +1 -0
- package/dist/commands/functions/new.js +57 -0
- package/dist/commands/functions/new.js.map +1 -0
- package/dist/commands/gen/index.d.ts +3 -0
- package/dist/commands/gen/index.d.ts.map +1 -0
- package/dist/commands/gen/index.js +320 -0
- package/dist/commands/gen/index.js.map +1 -0
- package/dist/commands/instance/index.d.ts +8 -0
- package/dist/commands/instance/index.d.ts.map +1 -0
- package/dist/commands/instance/index.js +361 -0
- package/dist/commands/instance/index.js.map +1 -0
- package/dist/commands/login/index.d.ts +3 -0
- package/dist/commands/login/index.d.ts.map +1 -0
- package/dist/commands/login/index.js +160 -0
- package/dist/commands/login/index.js.map +1 -0
- package/dist/commands/login/login.d.ts +3 -0
- package/dist/commands/login/login.d.ts.map +1 -0
- package/dist/commands/login/login.js +185 -0
- package/dist/commands/login/login.js.map +1 -0
- package/dist/commands/logout/index.d.ts +3 -0
- package/dist/commands/logout/index.d.ts.map +1 -0
- package/dist/commands/logout/index.js +41 -0
- package/dist/commands/logout/index.js.map +1 -0
- package/dist/commands/profile/import-env.d.ts +13 -0
- package/dist/commands/profile/import-env.d.ts.map +1 -0
- package/dist/commands/profile/import-env.js +81 -0
- package/dist/commands/profile/import-env.js.map +1 -0
- package/dist/commands/profile/index.d.ts +3 -0
- package/dist/commands/profile/index.d.ts.map +1 -0
- package/dist/commands/profile/index.js +112 -0
- package/dist/commands/profile/index.js.map +1 -0
- package/dist/commands/rest/index.d.ts +3 -0
- package/dist/commands/rest/index.d.ts.map +1 -0
- package/dist/commands/rest/index.js +102 -0
- package/dist/commands/rest/index.js.map +1 -0
- package/dist/commands/skill/index.d.ts +11 -0
- package/dist/commands/skill/index.d.ts.map +1 -0
- package/dist/commands/skill/index.js +153 -0
- package/dist/commands/skill/index.js.map +1 -0
- package/dist/commands/status/index.d.ts +3 -0
- package/dist/commands/status/index.d.ts.map +1 -0
- package/dist/commands/status/index.js +59 -0
- package/dist/commands/status/index.js.map +1 -0
- package/dist/commands/storage/index.d.ts +3 -0
- package/dist/commands/storage/index.d.ts.map +1 -0
- package/dist/commands/storage/index.js +198 -0
- package/dist/commands/storage/index.js.map +1 -0
- package/dist/commands/whoami/index.d.ts +3 -0
- package/dist/commands/whoami/index.d.ts.map +1 -0
- package/dist/commands/whoami/index.js +82 -0
- package/dist/commands/whoami/index.js.map +1 -0
- package/dist/credentials/commit.d.ts +38 -0
- package/dist/credentials/commit.d.ts.map +1 -0
- package/dist/credentials/commit.js +57 -0
- package/dist/credentials/commit.js.map +1 -0
- package/dist/credentials/credential-store.d.ts +57 -0
- package/dist/credentials/credential-store.d.ts.map +1 -0
- package/dist/credentials/credential-store.js +180 -0
- package/dist/credentials/credential-store.js.map +1 -0
- package/dist/credentials/env-file.d.ts +44 -0
- package/dist/credentials/env-file.d.ts.map +1 -0
- package/dist/credentials/env-file.js +118 -0
- package/dist/credentials/env-file.js.map +1 -0
- package/dist/credentials/formatter.d.ts +39 -0
- package/dist/credentials/formatter.d.ts.map +1 -0
- package/dist/credentials/formatter.js +79 -0
- package/dist/credentials/formatter.js.map +1 -0
- package/dist/credentials/index.d.ts +13 -0
- package/dist/credentials/index.d.ts.map +1 -0
- package/dist/credentials/index.js +13 -0
- package/dist/credentials/index.js.map +1 -0
- package/dist/credentials/jwt-introspect.d.ts +32 -0
- package/dist/credentials/jwt-introspect.d.ts.map +1 -0
- package/dist/credentials/jwt-introspect.js +64 -0
- package/dist/credentials/jwt-introspect.js.map +1 -0
- package/dist/credentials/masker.d.ts +20 -0
- package/dist/credentials/masker.d.ts.map +1 -0
- package/dist/credentials/masker.js +37 -0
- package/dist/credentials/masker.js.map +1 -0
- package/dist/credentials/profile.d.ts +83 -0
- package/dist/credentials/profile.d.ts.map +1 -0
- package/dist/credentials/profile.js +184 -0
- package/dist/credentials/profile.js.map +1 -0
- package/dist/credentials/tenant-guard.d.ts +16 -0
- package/dist/credentials/tenant-guard.d.ts.map +1 -0
- package/dist/credentials/tenant-guard.js +53 -0
- package/dist/credentials/tenant-guard.js.map +1 -0
- package/dist/credentials/tty-guard.d.ts +19 -0
- package/dist/credentials/tty-guard.d.ts.map +1 -0
- package/dist/credentials/tty-guard.js +47 -0
- package/dist/credentials/tty-guard.js.map +1 -0
- package/dist/db/apply-schema.d.ts +144 -0
- package/dist/db/apply-schema.d.ts.map +1 -0
- package/dist/db/apply-schema.js +359 -0
- package/dist/db/apply-schema.js.map +1 -0
- package/dist/db/sql-file.d.ts +104 -0
- package/dist/db/sql-file.d.ts.map +1 -0
- package/dist/db/sql-file.js +584 -0
- package/dist/db/sql-file.js.map +1 -0
- package/dist/instance/provision.d.ts +89 -0
- package/dist/instance/provision.d.ts.map +1 -0
- package/dist/instance/provision.js +139 -0
- package/dist/instance/provision.js.map +1 -0
- package/dist/instance/zero-store.d.ts +47 -0
- package/dist/instance/zero-store.d.ts.map +1 -0
- package/dist/instance/zero-store.js +91 -0
- package/dist/instance/zero-store.js.map +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +46 -0
- package/dist/main.js.map +1 -0
- package/dist/skill/assets.d.ts +38 -0
- package/dist/skill/assets.d.ts.map +1 -0
- package/dist/skill/assets.js +111 -0
- package/dist/skill/assets.js.map +1 -0
- package/dist/skill/hint.d.ts +39 -0
- package/dist/skill/hint.d.ts.map +1 -0
- package/dist/skill/hint.js +109 -0
- package/dist/skill/hint.js.map +1 -0
- package/dist/skill/hosts.d.ts +21 -0
- package/dist/skill/hosts.d.ts.map +1 -0
- package/dist/skill/hosts.js +38 -0
- package/dist/skill/hosts.js.map +1 -0
- package/dist/skill/installer.d.ts +88 -0
- package/dist/skill/installer.d.ts.map +1 -0
- package/dist/skill/installer.js +203 -0
- package/dist/skill/installer.js.map +1 -0
- package/dist/skill/ownership.d.ts +35 -0
- package/dist/skill/ownership.d.ts.map +1 -0
- package/dist/skill/ownership.js +52 -0
- package/dist/skill/ownership.js.map +1 -0
- package/package.json +63 -3
- package/skills/pxbaas-app-spec/SKILL.md +163 -0
- package/skills/pxbaas-app-spec/references/deploy-options.md +157 -0
- package/skills/pxbaas-app-spec/references/guardrails-pitfalls.md +89 -0
- package/skills/pxbaas-app-spec/references/runtime-contract.md +151 -0
- package/skills/pxbaas-app-spec/references/ssr-session.md +121 -0
- package/skills/pxbaas-app-spec/references/templates-examples.md +311 -0
- package/skills/pxbaas-cli/SKILL.md +171 -0
- package/skills/pxbaas-cli/references/api-reference.md +68 -0
- package/skills/pxbaas-cli/references/app-deploy.md +170 -0
- package/skills/pxbaas-cli/references/auth-and-credentials.md +100 -0
- package/skills/pxbaas-cli/references/data-postgrest.md +85 -0
- package/skills/pxbaas-cli/references/db-and-meta.md +97 -0
- package/skills/pxbaas-cli/references/edge-functions.md +71 -0
- package/skills/pxbaas-cli/references/multi-tenant-apps.md +138 -0
- package/skills/pxbaas-cli/references/provision.md +86 -0
- package/skills/pxbaas-cli/references/safety-and-two-phase.md +49 -0
- package/skills/pxbaas-cli/references/storage.md +61 -0
- package/skills/pxbaas-cli/references/troubleshooting.md +73 -0
- package/templates/webapp/api/index.ts +55 -0
- package/templates/webapp/csr-bff/README.md +81 -0
- package/templates/webapp/csr-bff/backend/index.ts +168 -0
- package/templates/webapp/csr-bff/db/rls.json +13 -0
- package/templates/webapp/csr-bff/db/schema.sql +49 -0
- package/templates/webapp/csr-bff/frontend/dist/admin.html +101 -0
- package/templates/webapp/csr-bff/frontend/dist/article.html +44 -0
- package/templates/webapp/csr-bff/frontend/dist/blog.js +206 -0
- package/templates/webapp/csr-bff/frontend/dist/blogger.html +53 -0
- package/templates/webapp/csr-bff/frontend/dist/index.html +70 -0
- package/templates/webapp/csr-bff/frontend/dist/login.html +56 -0
- package/templates/webapp/csr-bff/frontend/dist/profile.html +59 -0
- package/templates/webapp/csr-bff/frontend/dist/signup.html +78 -0
- package/templates/webapp/csr-bff/frontend/dist/style.css +27 -0
- package/templates/webapp/manifest.json +299 -0
- package/templates/webapp/mpa/about.html +29 -0
- package/templates/webapp/mpa/app.js +42 -0
- package/templates/webapp/mpa/index.html +42 -0
- package/templates/webapp/mpa/index.ts +60 -0
- package/templates/webapp/mpa/style.css +42 -0
- package/templates/webapp/react-vite/frontend/index.html +12 -0
- package/templates/webapp/react-vite/frontend/package-lock.json +1826 -0
- package/templates/webapp/react-vite/frontend/package.json +23 -0
- package/templates/webapp/react-vite/frontend/src/App.tsx +34 -0
- package/templates/webapp/react-vite/frontend/src/main.tsx +13 -0
- package/templates/webapp/react-vite/frontend/src/style.css +46 -0
- package/templates/webapp/react-vite/frontend/tsconfig.json +20 -0
- package/templates/webapp/react-vite/frontend/vite.config.ts +10 -0
- package/templates/webapp/react-vite-hono/backend/index.ts +21 -0
- package/templates/webapp/react-vite-hono/frontend/index.html +12 -0
- package/templates/webapp/react-vite-hono/frontend/package-lock.json +1826 -0
- package/templates/webapp/react-vite-hono/frontend/package.json +23 -0
- package/templates/webapp/react-vite-hono/frontend/src/App.tsx +47 -0
- package/templates/webapp/react-vite-hono/frontend/src/main.tsx +13 -0
- package/templates/webapp/react-vite-hono/frontend/src/style.css +66 -0
- package/templates/webapp/react-vite-hono/frontend/tsconfig.json +20 -0
- package/templates/webapp/react-vite-hono/frontend/vite.config.ts +10 -0
- package/templates/webapp/spa/index.ts +119 -0
- package/templates/webapp/ssr-auth/backend/index.ts +133 -0
- package/templates/webapp/ssr-auth/frontend/dist/index.html +15 -0
- package/templates/webapp/ssr-blog/README.md +111 -0
- package/templates/webapp/ssr-blog/backend/index.ts +764 -0
- package/templates/webapp/ssr-blog/db/rls.json +13 -0
- package/templates/webapp/ssr-blog/db/schema.sql +51 -0
- package/templates/webapp/ssr-blog/frontend/dist/index.html +39 -0
- package/templates/webapp/ssr-frameworks/README.md +64 -0
- package/templates/webapp/ssr-frameworks/backend/index.ts +241 -0
- package/templates/webapp/ssr-frameworks/frontend/dist/index.html +31 -0
- package/templates/webapp/ssr-hono/backend/index.ts +92 -0
- package/templates/webapp/ssr-hono/frontend/dist/index.html +12 -0
- package/templates/webapp/ssr-shop/README.md +80 -0
- package/templates/webapp/ssr-shop/backend/index.ts +491 -0
- package/templates/webapp/ssr-shop/db/rls.json +16 -0
- package/templates/webapp/ssr-shop/db/schema.sql +90 -0
- package/templates/webapp/ssr-shop/frontend/dist/index.html +12 -0
- package/templates/webapp/vanilla-static/frontend/app.js +6 -0
- package/templates/webapp/vanilla-static/frontend/index.html +19 -0
- package/templates/webapp/vanilla-static/frontend/style.css +43 -0
package/package.json
CHANGED
|
@@ -1,6 +1,66 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polardbx/pxbaas",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0-beta.2",
|
|
4
|
+
"description": "PolarDB-X BaaS CLI application",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"polardbx",
|
|
7
|
+
"baas",
|
|
8
|
+
"cli"
|
|
9
|
+
],
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/polardbx/mysql-supabase.git",
|
|
14
|
+
"directory": "cli/apps/baas"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public",
|
|
18
|
+
"registry": "https://registry.npmjs.org"
|
|
19
|
+
},
|
|
20
|
+
"type": "module",
|
|
21
|
+
"bin": {
|
|
22
|
+
"pxbaas": "./bin/pxbaas.js"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"bin",
|
|
26
|
+
"dist",
|
|
27
|
+
"skills",
|
|
28
|
+
"templates"
|
|
29
|
+
],
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"axios": "^1.6.0",
|
|
32
|
+
"chalk": "^5.3.0",
|
|
33
|
+
"cli-table3": "^0.6.3",
|
|
34
|
+
"commander": "^12.0.0",
|
|
35
|
+
"inquirer": "^9.2.0",
|
|
36
|
+
"jsonwebtoken": "^9.0.0",
|
|
37
|
+
"keytar": "^7.9.0",
|
|
38
|
+
"@polardbx/pxbaas-client": "0.1.0-beta.2"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/inquirer": "^9.0.0",
|
|
42
|
+
"@types/jsonwebtoken": "^9.0.0",
|
|
43
|
+
"@types/node": "^20.11.0",
|
|
44
|
+
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
45
|
+
"@typescript-eslint/parser": "^7.0.0",
|
|
46
|
+
"eslint": "^8.56.0",
|
|
47
|
+
"msw": "^2.2.0",
|
|
48
|
+
"typescript": "^5.3.0",
|
|
49
|
+
"vitest": "^1.2.0"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=20.0.0"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsc --build",
|
|
56
|
+
"dev": "tsc --watch",
|
|
57
|
+
"start": "node ./bin/pxbaas.js",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"test:unit": "vitest run",
|
|
60
|
+
"test:integration": "vitest run --config vitest.integration.config.ts",
|
|
61
|
+
"lint": "eslint src --ext .ts",
|
|
62
|
+
"lint:fix": "eslint src --ext .ts --fix",
|
|
63
|
+
"typecheck": "tsc --noEmit",
|
|
64
|
+
"clean": "rm -rf dist *.tsbuildinfo"
|
|
65
|
+
}
|
|
6
66
|
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pxbaas-app-spec
|
|
3
|
+
description: Author and review PolarDB-X BaaS (MySQL-Supabase) apps in both legacy Edge Function modes and schemaVersion 2 Webapp mode. Covers prebuilt static frontends, React + Vite + HashRouter, optional Deno/Hono backends, App capabilities, immutable candidates, generation-CAS publish/rollback/unpublish, artifact guardrails, and legacy /functions/v1 behavior. This is the authoritative app spec enforced by `pxbaas app deploy`.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# pxbaas-app-spec
|
|
7
|
+
|
|
8
|
+
How to write apps for **PolarDB-X BaaS**, including legacy Edge Function demos and schemaVersion 2 Webapps with a static frontend plus an optional Deno/Hono backend.
|
|
9
|
+
|
|
10
|
+
This is an **authoring spec**. For exact CLI syntax, see the `pxbaas-cli` skill (`references/app-deploy.md`). Legacy raw Management API and Studio deployment remain documented for Edge Functions; Webapp lifecycle operations should use `pxbaas app`.
|
|
11
|
+
|
|
12
|
+
> **This spec is enforced, not just advised.** `pxbaas app deploy` runs static spec-lint before upload. In Webapp mode it checks the built frontend, project routing/build configuration, backend source, manifest policy, capability limits, and exact Hono version. Violations are blockers.
|
|
13
|
+
|
|
14
|
+
## When to use this skill
|
|
15
|
+
|
|
16
|
+
- Writing a static, React/Vite, Deno, or Hono Webapp for an App mount
|
|
17
|
+
- Writing or maintaining a legacy `spa`/`api`/`mpa` Edge Function demo
|
|
18
|
+
- Reviewing routing, build output, backend imports, Secrets, or egress policy
|
|
19
|
+
- Diagnosing capability blocks, CAS conflicts, 503/blank pages, or broken asset paths
|
|
20
|
+
|
|
21
|
+
## Mental model: two compatible delivery modes
|
|
22
|
+
|
|
23
|
+
**Manifest v2 Webapp:** the stable mount serves a prebuilt frontend and optionally routes `/api/*` to a Deno backend. The CLI uploads an immutable candidate and atomically changes the mount generation.
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
{appEndpoint}/apps/{mount}/
|
|
27
|
+
{appEndpoint}/apps/{mount}/api/*
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The public classification is capability-driven. Only `originMode=isolated`, `hostEnforced=true`, and `publicExposureAllowed=true` produce a Public URL; all other mounts are Preview.
|
|
31
|
+
|
|
32
|
+
**Legacy Edge Function:** one Deno worker remains available for `spa`/`api`/`mpa` compatibility.
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
{endpoint}/functions/v1/{slug}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Runtime and artifact contract (must satisfy)
|
|
39
|
+
|
|
40
|
+
- **Frontend:** upload build output only. Vite uses `base: "./"` and source maps off. React uses `HashRouter`, not browser-history routing. No `.env`, source maps, `node_modules`, `src/`, Service Workers, root-absolute references, or binary assets.
|
|
41
|
+
- **Backend:** Deno only, with top-level `Deno.serve(...)`; no Node APIs, arbitrary remote/npm/jsr imports, Secrets, or outbound egress. `Deno.env` is limited to the platform public three-key whitelist `SUPABASE_URL` / `SUPABASE_ANON_KEY` / `SUPABASE_INTERNAL_URL` (any other variable or `Deno.env.toObject()` fails spec-lint with `deno-env-not-allowed`); use `SUPABASE_INTERNAL_URL` as the only worker-side data-plane fetch base — `SUPABASE_URL` is render-only. The first release requires `secrets: []` and `egressPolicy: "deny"`.
|
|
42
|
+
- **Hono:** import exactly `npm:hono@{{HONO_VERSION}}` in scaffold source. Deploy replaces the placeholder with the capability's exact offline-ready version.
|
|
43
|
+
- **SSR renderers:** a worker may also import the curated server-side renderers reported by capability `ssrLibs` (e.g. `npm:react-dom@18.3.1/server`, `npm:@vue/server-renderer@3.5.13`, `npm:preact-render-to-string@6.5.11`, `npm:solid-js@1.9.3/web`). Use the exact `name@version` from `ssrLibs` (a `/subpath` such as `/server` or `/web` is allowed); off-list or version-drifted imports fail spec-lint (`ssr-lib-not-allowed`). `pxbaas app capabilities` lists what the instance actually pre-cached.
|
|
44
|
+
- **Legacy modes:** continue to return `Response` objects, set content types, and avoid binding a port.
|
|
45
|
+
- Full details + limits: `references/runtime-contract.md`.
|
|
46
|
+
|
|
47
|
+
## Minimal skeleton
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
function cors(): Record<string, string> {
|
|
51
|
+
return {
|
|
52
|
+
"Access-Control-Allow-Origin": "*",
|
|
53
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
54
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization, apikey, X-Tenant-Id",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function esc(s: string): string {
|
|
58
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
Deno.serve((req: Request) => {
|
|
62
|
+
const url = new URL(req.url);
|
|
63
|
+
if (req.method === "OPTIONS") return new Response(null, { status: 204, headers: cors() });
|
|
64
|
+
|
|
65
|
+
if (url.searchParams.get("action") === "time") {
|
|
66
|
+
return new Response(JSON.stringify({ ok: true, now: new Date().toISOString() }),
|
|
67
|
+
{ headers: { "Content-Type": "application/json", ...cors() } });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const html = `<!DOCTYPE html><html><head><meta charset="utf-8"><title>Demo</title></head><body>
|
|
71
|
+
<h1>Demo</h1><button onclick="p()">time</button><pre id="o"></pre>
|
|
72
|
+
<script>async function p(){const r=await fetch(location.pathname+"?action=time");document.getElementById("o").textContent=JSON.stringify(await r.json());}</script>
|
|
73
|
+
</body></html>`;
|
|
74
|
+
return new Response(html, { headers: { "Content-Type": "text/html; charset=utf-8", ...cors() } });
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Guardrails (the rules that make it actually work)
|
|
79
|
+
|
|
80
|
+
**MUST**
|
|
81
|
+
- Serve page(s) + API under the ONE prefix `/functions/v1/{slug}`. Single-file (`spa`/`api`): one `Deno.serve` split by `?action=`/method. Multi-page (`mpa`): the proxy serves real static files by path and the worker handles `?action=`.
|
|
82
|
+
- Use **relative URLs** client-side: `location.pathname + "?action=..."` (in `mpa`, call the directory base `./?action=...` so sub-pages don't capture the query).
|
|
83
|
+
- **Assets**: inline all CSS/JS in single-file mode; in `mpa` deploy real files, link them **relatively** and list them in `app.json` `pages`/`assets`. **Escape reflected input** (the URL is public); set **CORS** + handle `OPTIONS`.
|
|
84
|
+
- Keep it small/fast: ~150MB memory, CPU ~10s soft / 20s hard, ~5min wall; one sandbox shared per tenant; `mpa` static files ≤256KB each.
|
|
85
|
+
- **Webapp publish contract**: `app.json` must be `schemaVersion: 2` and its `name` must equal the mount's `appName` (not the candidate slug); upload statics with an explicit nested `filename=frontend/dist/<name>`. A heavy `npm:` import graph makes the first request slow (up to ~60s with four SSR renderers) and can fail the publish-time SSR probe — warm that tenant's sandbox first and retry, don't rewrite a worker that already returns 200 (see `references/deploy-options.md`).
|
|
86
|
+
|
|
87
|
+
**MUST NOT**
|
|
88
|
+
- **No root-absolute paths** (`href="/x"`, `src="/x"`, `fetch("/x")`, `url(/x)`) — they escape `/functions/v1/` onto Studio and break the share URL.
|
|
89
|
+
- **No external CDN `<script>`/`<link>`** for core assets — inline them. (Legacy edge functions may `fetch()` public APIs at runtime; **Webapp backends are `egressPolicy="deny"`** — public egress is blocked and only the platform-internal base `SUPABASE_INTERNAL_URL` is reachable.)
|
|
90
|
+
- **No Node.js APIs, heavy build output, large binaries, base64 blobs.**
|
|
91
|
+
- **No secrets in a `verify_jwt=false` function** (publicly reachable, slug enumerable).
|
|
92
|
+
- **No DDL / long jobs** in the request path.
|
|
93
|
+
- **No worker-side cookie sessions on `/apps/{mount}/`** — the proxy strips client cookies on that entry. For signed-in SSR declare `backend.session:"proxy-injected"` and read the injected `X-App-User-*` headers; never read `cookie`, never send `Set-Cookie`, never put tokens in URLs. See `references/ssr-session.md`.
|
|
94
|
+
|
|
95
|
+
Details, pitfalls and fixes: `references/guardrails-pitfalls.md`.
|
|
96
|
+
|
|
97
|
+
## App / template organization
|
|
98
|
+
|
|
99
|
+
SchemaVersion 2 Webapp:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
apps/<name>/
|
|
103
|
+
├── frontend/ # source project or vanilla static source
|
|
104
|
+
│ └── dist/ # canonical React/Vite deployment root
|
|
105
|
+
├── backend/index.ts # optional Deno/Hono API
|
|
106
|
+
└── app.json # mode=webapp, frontend/backend contracts, deployment state
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Legacy modes retain `index.ts` plus optional `mpa` static files.
|
|
110
|
+
|
|
111
|
+
- Webapp templates: `vanilla-static`, `react-vite`, `react-vite-hono`, `ssr-hono`, `ssr-auth` (signed-in SSR via proxy-injected session headers). Reference application templates (ship `db/schema.sql` + README + a DEPLOY CONFIG block to fill before deploy): `ssr-blog` (full-site SSR + proxy-injected session), `csr-bff` (front/back-separated CSR + BFF), `ssr-shop` (React SSR shop, browser-side session), `ssr-frameworks` (four-framework SSR showcase). `pxbaas app templates` is the authoritative list.
|
|
112
|
+
- Legacy templates: `spa`, `api`, `mpa`.
|
|
113
|
+
- Start from `pxbaas app new --template <id>` instead of hand-rolling the manifest. Full structures: `references/templates-examples.md`.
|
|
114
|
+
|
|
115
|
+
## Deploy paths (pick one)
|
|
116
|
+
|
|
117
|
+
| Path | When | How |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| **`pxbaas app`** (recommended and required for Webapp lifecycle) | you have the CLI + a profile | capability check, candidate upload, CAS publish/rollback/unpublish |
|
|
120
|
+
| **Management API (`curl`)** | legacy Edge Function scripting | `POST {endpoint}/v1/projects/default/functions/deploy?slug=<slug>` (multipart) |
|
|
121
|
+
| **Studio** | legacy manual deployment | Functions editor in the Dashboard |
|
|
122
|
+
|
|
123
|
+
Use `pxbaas app capabilities --json` before a Webapp deploy. Full commands + gotchas: `references/deploy-options.md`.
|
|
124
|
+
|
|
125
|
+
## Constraints & limits (state these to the user)
|
|
126
|
+
|
|
127
|
+
- **Fits:** bounded prebuilt static frontends with optional small Deno/Hono APIs, plus legacy lightweight demos.
|
|
128
|
+
- **Does not fit:** server-side Node frameworks, browser-history fallback routing, Service Workers, arbitrary egress, Secrets, large/binary bundles, or heavy/long-running jobs.
|
|
129
|
+
- Capability limits (`maxFiles`, `maxFileBytes`, `maxBundleBytes`) are authoritative.
|
|
130
|
+
- Legacy functions retain cold-start and function-quota behavior.
|
|
131
|
+
|
|
132
|
+
## Workflow
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
1. pxbaas app capabilities --json
|
|
136
|
+
2. pxbaas app new <name> --template react-vite|react-vite-hono|vanilla-static
|
|
137
|
+
3. build the frontend when required (npm ci && npm run build)
|
|
138
|
+
4. pxbaas app deploy <name> --json
|
|
139
|
+
5. pxbaas app verify <name> --json
|
|
140
|
+
6. use publish / rollback / unpublish for later generation changes
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## What NOT to do
|
|
144
|
+
|
|
145
|
+
- Do NOT use root-absolute asset/links/fetch paths — the #1 cause of a broken share URL.
|
|
146
|
+
- Do NOT deploy React/Vite source; run the pinned lockfile build and upload `dist`.
|
|
147
|
+
- Do NOT use `BrowserRouter`, a Service Worker, source maps, `.env`, or root-absolute URLs.
|
|
148
|
+
- Do NOT use Node APIs, arbitrary remote imports, Secrets, or non-deny egress in a Webapp backend. `Deno.env` is allowed ONLY for the three-key whitelist (`SUPABASE_URL` / `SUPABASE_ANON_KEY` / `SUPABASE_INTERNAL_URL`); any other variable or `Deno.env.toObject()` is blocked.
|
|
149
|
+
- Do NOT use `--skip-spec-check` to force a bundled template or a failing artifact through — it bypasses the deployment/security contract. Only bypass when the user explicitly asks, after you have listed every blocker and its impact.
|
|
150
|
+
- Do NOT bypass capability checks or retry a generation conflict with a guessed generation.
|
|
151
|
+
- Do NOT put large/binary assets in an App candidate; use Storage/CDN.
|
|
152
|
+
- For legacy modes, do NOT assume first-hit success or exceed the function quota.
|
|
153
|
+
|
|
154
|
+
## Reference guide
|
|
155
|
+
|
|
156
|
+
| Topic | Reference |
|
|
157
|
+
|---|---|
|
|
158
|
+
| Deno runtime contract, worker limits, lifecycle | references/runtime-contract.md |
|
|
159
|
+
| Guardrails, common pitfalls & fixes | references/guardrails-pitfalls.md |
|
|
160
|
+
| Legacy and Manifest v2 template/app structures | references/templates-examples.md |
|
|
161
|
+
| Deploy and Webapp lifecycle options | references/deploy-options.md |
|
|
162
|
+
| SSR webapp sessions (proxy-injected identity headers) | references/ssr-session.md |
|
|
163
|
+
| Multi-tenant provisioning, credentials, RLS placement, acceptance | ../pxbaas-cli/references/multi-tenant-apps.md |
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Deploy Options
|
|
2
|
+
|
|
3
|
+
Use `pxbaas app` for schemaVersion 2 Webapps. Legacy Edge Function apps may also use the raw Management API or Studio. Deployment requires the **service_role** key.
|
|
4
|
+
|
|
5
|
+
## Path 1 — `pxbaas app` (recommended)
|
|
6
|
+
|
|
7
|
+
Requires the pxbaas CLI with an active profile (`pxbaas provision` or `pxbaas login`). It handles capability gating, artifact lint, immutable candidate upload, generation CAS, and verification.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pxbaas app capabilities --json
|
|
11
|
+
pxbaas app new <name> --template react-vite-hono --json
|
|
12
|
+
(cd apps/<name>/frontend && npm ci && npm run build)
|
|
13
|
+
pxbaas app deploy <name> \
|
|
14
|
+
--frontend apps/<name>/frontend/dist \
|
|
15
|
+
--backend apps/<name>/backend \
|
|
16
|
+
--backend-framework hono \
|
|
17
|
+
--json
|
|
18
|
+
pxbaas app verify <name> --json
|
|
19
|
+
pxbaas app url <name> --json
|
|
20
|
+
|
|
21
|
+
# Explicit lifecycle operations use local mount/candidate/generation state:
|
|
22
|
+
pxbaas app publish <name> --json
|
|
23
|
+
pxbaas app rollback <name> --json
|
|
24
|
+
pxbaas app unpublish <name> --json
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- `vanilla-static` needs no build; React templates use their committed lockfile and fixed versions.
|
|
28
|
+
- `--frontend` must point to prebuilt output. `--backend` points to Deno source.
|
|
29
|
+
- Deploy automatically publishes. `app publish` is useful when a prior publish failed after candidate upload.
|
|
30
|
+
- Confirm deploy/publish/rollback/unpublish with the user first.
|
|
31
|
+
- JSON failures consistently contain `code` and `message`.
|
|
32
|
+
- See the `pxbaas-cli` skill `references/app-deploy.md` for the full command surface.
|
|
33
|
+
|
|
34
|
+
## Path 2 — Legacy Management API (`curl`), no CLI
|
|
35
|
+
|
|
36
|
+
Deploy is a **multipart** POST. Critical: the `metadata` field must be a **plain form field WITHOUT a filename** (sending it as a file part causes a 400/parse error). The code file is sent as `file=@index.ts` with a filename matching `entrypoint_path`.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
BASE="http://<public-endpoint>:8000" # the instance Kong URL
|
|
40
|
+
SLUG="shop-demo"
|
|
41
|
+
SVC_KEY="<SERVICE_ROLE_KEY>"
|
|
42
|
+
# multi-tenant only:
|
|
43
|
+
TENANT="-H X-Tenant-Id:<TENANT_ID>"
|
|
44
|
+
|
|
45
|
+
curl -sS -X POST "${BASE}/v1/projects/default/functions/deploy?slug=${SLUG}" \
|
|
46
|
+
-H "Authorization: Bearer ${SVC_KEY}" \
|
|
47
|
+
${TENANT} \
|
|
48
|
+
-F "metadata={\"slug\":\"${SLUG}\",\"name\":\"${SLUG}\",\"entrypoint_path\":\"index.ts\",\"verify_jwt\":false};type=application/json" \
|
|
49
|
+
-F "file=@./index.ts;type=application/typescript" \
|
|
50
|
+
-w "\nHTTP_STATUS=%{http_code}\n"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
- Share URL: `${BASE}/functions/v1/${SLUG}`.
|
|
54
|
+
- Some deployments also require Studio basic-auth (`-u <DASHBOARD_USERNAME>:<DASHBOARD_PASSWORD>`) on `/v1/projects/*`; if you get 401, add `-u`.
|
|
55
|
+
- Do not echo the service_role key into logs; prefer an env var.
|
|
56
|
+
|
|
57
|
+
## Path 3 — Legacy Studio (Dashboard), manual
|
|
58
|
+
|
|
59
|
+
Open the instance Dashboard (`{endpoint}/`, basic-auth user `supabase`), go to **Edge Functions**, create/paste the function, set **Verify JWT = off** for a public demo, and deploy. Studio then notifies the proxy to create/sync the sandbox. Good for one-off manual demos; not scriptable.
|
|
60
|
+
|
|
61
|
+
## Verify & share (legacy paths)
|
|
62
|
+
|
|
63
|
+
A paused sandbox 503s on first hit, so verify with retries:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
URL="${BASE}/functions/v1/${SLUG}"
|
|
67
|
+
for i in $(seq 1 12); do
|
|
68
|
+
code=$(curl -sS -o /dev/null -w '%{http_code}' "$URL" || echo 000)
|
|
69
|
+
ct=$(curl -sS -o /dev/null -w '%{content_type}' "$URL" || echo -)
|
|
70
|
+
echo "attempt#$i HTTP $code content-type=$ct"
|
|
71
|
+
[ "$code" = "200" ] && break
|
|
72
|
+
sleep 5
|
|
73
|
+
done
|
|
74
|
+
curl -sS "${URL}?action=time" # built-in API ping (spa template)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- Expect `HTTP 200` and `content-type: text/html...` for the page; the `?action=` ping returns JSON.
|
|
78
|
+
- **Cross-machine sharing** works only if `{endpoint}` is a public address (not localhost/private) and the caller's IP is within the instance whitelist (Zero instances whitelist your egress IP).
|
|
79
|
+
- Send the `{endpoint}/functions/v1/{slug}` URL — never the internal sandbox URL.
|
|
80
|
+
|
|
81
|
+
## Webapp publish contract (schemaVersion 2)
|
|
82
|
+
|
|
83
|
+
`publish` re-validates the candidate's `app.json` against the mount, so these are hard
|
|
84
|
+
requirements — violating any of them fails at publish time (after the upload succeeded),
|
|
85
|
+
which is the most confusing place to discover them:
|
|
86
|
+
|
|
87
|
+
- **`name` MUST equal the mount's `appName`** (not the candidate slug). A mismatch returns
|
|
88
|
+
`409 APP_MANIFEST_INVALID: Candidate app.json does not match the schemaVersion 2 webapp
|
|
89
|
+
contract` — the message does not name the field, so check `name` first.
|
|
90
|
+
- **`schemaVersion: 2` is required.** Instances report the accepted set in
|
|
91
|
+
`pxbaas app capabilities --json` → `schemaVersions`; an instance reporting `[2]` rejects
|
|
92
|
+
legacy v1 manifests (`mode: "mpa"` + `pages[]`/`assets[]`).
|
|
93
|
+
- **Re-publishing the already-active candidate** returns
|
|
94
|
+
`409 APP_ARTIFACT_INVALID: Candidate is already active`. Treat it as success (idempotent
|
|
95
|
+
re-run), not as a failure.
|
|
96
|
+
|
|
97
|
+
### Migrating a legacy v1 `mpa` manifest to v2
|
|
98
|
+
|
|
99
|
+
| v1 (`mode: "mpa"`) | v2 (`mode: "webapp"`) |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `slug`, `template`, `entrypoint`, `verifyJwt` (top level) | dropped / moved under `backend` |
|
|
102
|
+
| `pages: ["index.html", ...]`, `assets: ["style.css", ...]` | real files uploaded under `frontend.root` (e.g. `frontend/dist/`); `frontend.index` = first page |
|
|
103
|
+
| — | `frontend: { root, routing: "files", index, fallback: null \| "worker" }` |
|
|
104
|
+
| `entrypoint: "index.ts"` | `backend: { entrypoint: "backend/index.ts", framework: "deno" \| "hono", pathMode: "strip-app-api-prefix", verifyJwt, secrets, egressPolicy }` |
|
|
105
|
+
|
|
106
|
+
Upload each static file with an explicit nested `filename=frontend/dist/<name>` (multipart
|
|
107
|
+
path flattening otherwise collapses them into the bundle root and publish then reports a
|
|
108
|
+
missing `frontend/dist/index.html`).
|
|
109
|
+
|
|
110
|
+
## SSR probe and cold-start budget (platform-side)
|
|
111
|
+
|
|
112
|
+
When the manifest declares `ssrProbePath`, publish **fetches that path once through the
|
|
113
|
+
worker before activating the candidate**. If the probe does not answer in time, publish fails
|
|
114
|
+
with `409 APP_SSR_PROBE_FAILED` — first as `SSR probe unreachable: ... aborted due to
|
|
115
|
+
timeout`, then (once the sandbox half-initialises) as `SSR probe failed: http_500`.
|
|
116
|
+
|
|
117
|
+
This is a **cold-start budget problem, not an app defect**. Measured first-request cost of a
|
|
118
|
+
cold tenant sandbox scales with how many npm modules the worker imports:
|
|
119
|
+
|
|
120
|
+
| Worker shape | First request (cold) | Warm |
|
|
121
|
+
|---|---|---|
|
|
122
|
+
| Deno stdlib only, no `npm:` imports | a few seconds | < 0.2 s |
|
|
123
|
+
| Hono + React + react-dom/server | tens of seconds | < 0.2 s |
|
|
124
|
+
| Hono + React + Vue + Preact + Solid (4 renderers) | ~60 s | < 0.2 s |
|
|
125
|
+
|
|
126
|
+
Standard recovery procedure (sandboxes are reused **per tenant**, so warming once helps every
|
|
127
|
+
function of that tenant, including the mount candidate):
|
|
128
|
+
|
|
129
|
+
1. Deploy the same worker as a legacy function in the same tenant
|
|
130
|
+
(`functions deploy`, tenant service key + `X-Tenant-Id`).
|
|
131
|
+
2. `GET` it until it returns 200 — the first call may take a minute; this lands the module
|
|
132
|
+
resolution/compile results in that tenant's sandbox cache.
|
|
133
|
+
3. Re-run `publish`. On `APP_SSR_PROBE_FAILED`, wait 10–20 s and retry (3–5 attempts): each
|
|
134
|
+
failed probe attempt continues to warm the sandbox.
|
|
135
|
+
|
|
136
|
+
To decide between "broken code" and "cold start", compare the legacy function's first-call
|
|
137
|
+
latency and final status: slow-then-200 means cold start; immediate 500 with a stack means the
|
|
138
|
+
worker itself. Note `capabilities.ssrLibs` lists only the curated build-time allow-list and
|
|
139
|
+
may **under-report** what the sandbox can actually resolve — treat it as a hint, and verify by
|
|
140
|
+
running the worker.
|
|
141
|
+
|
|
142
|
+
## Deploy troubleshooting
|
|
143
|
+
|
|
144
|
+
| Symptom | Cause | Fix |
|
|
145
|
+
|---|---|---|
|
|
146
|
+
| 400 / parse error on deploy | `metadata` sent as a file part | send `metadata` as a plain field, no filename |
|
|
147
|
+
| 401 on `/v1/projects/*` | missing service_role or Studio basic-auth | add `Authorization: Bearer <svc>` (and `-u` if required) |
|
|
148
|
+
| 403 in multi-tenant | missing/incorrect `X-Tenant-Id`, or non-platform key | send the tenant header; use the right key |
|
|
149
|
+
| 429 on deploy | function quota (5/tenant) reached | remove a function or request a higher quota; do not loop |
|
|
150
|
+
| 503 after deploy | sandbox cold start / not READY | retry up to ~60s (verify loop above) |
|
|
151
|
+
| 404 on share URL | wrong slug, or function not deployed | check slug regex and `pxbaas app list` / `functions list` |
|
|
152
|
+
| `APP_CAPABILITY_UNSUPPORTED` | required Webapp/static/Deno/Hono capability is disabled | inspect `pxbaas app capabilities --json`; stop before upload |
|
|
153
|
+
| `APP_ARTIFACT_INVALID` | frontend/backend violates the v2 artifact contract | fix every spec-lint blocker; do not use `--skip-spec-check` as a workaround |
|
|
154
|
+
| `APP_ARTIFACT_INVALID: Candidate is already active` | re-publishing the active candidate | idempotent no-op — treat as success |
|
|
155
|
+
| `APP_MANIFEST_INVALID` | `app.json` `name` ≠ mount `appName`, or manifest is not schemaVersion 2 | align `name` with `appName`; migrate v1 `mpa` manifests (table above) |
|
|
156
|
+
| `APP_SSR_PROBE_FAILED` (timeout, then `http_500`) | cold tenant sandbox cannot resolve/compile the worker's npm imports within the probe budget | warm the sandbox via a legacy function, then retry publish (procedure above) — platform-side, not an app defect |
|
|
157
|
+
| `APP_PUBLISH_CONFLICT` | `expectedGeneration` is stale | reconcile mount state and retry with the returned current generation |
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Guardrails, Pitfalls & Fixes
|
|
2
|
+
|
|
3
|
+
The enforced rules for schemaVersion 2 Webapps and legacy Edge Function apps, plus common failure modes and fixes.
|
|
4
|
+
|
|
5
|
+
## Why these rules exist
|
|
6
|
+
|
|
7
|
+
Webapp static files are served from a stable `/apps/{mount}/` prefix and an optional backend receives `/apps/{mount}/api/*` with that prefix stripped. Legacy apps remain under `/functions/v1/{slug}`. Prefix safety, immutable deployment, and a deliberately restricted backend capability set drive these guardrails.
|
|
8
|
+
|
|
9
|
+
## Manifest v2 Webapp MUST
|
|
10
|
+
|
|
11
|
+
1. Check `pxbaas app capabilities --json` before deploy; do not infer App support from the gateway URL.
|
|
12
|
+
2. Upload prebuilt frontend output only. Vite uses `base: "./"` and `build.sourcemap: false`; React uses `HashRouter`.
|
|
13
|
+
3. Use relative asset and API URLs. The backend API is under relative `./api/*` from the mounted frontend.
|
|
14
|
+
4. Keep `frontend.routing="files"` and `frontend.index="index.html"`. Static-only apps use `frontend.fallback=null`; SSR apps use `frontend.fallback="worker"` **and must declare `ssrProbePath`** (see the Worker SSR section) — `fallback=null` is NOT universal.
|
|
15
|
+
5. Backend entrypoints call `Deno.serve`; Hono imports `npm:hono@{{HONO_VERSION}}` in source and the exact capability version in the staged candidate. SSR workers may additionally import only the capability `ssrLibs` renderers at their exact `name@version` (a `/subpath` is allowed); off-list or version-drifted imports fail `ssr-lib-not-allowed`.
|
|
16
|
+
6. Keep backend `secrets=[]` and `egressPolicy="deny"` for the first release.
|
|
17
|
+
|
|
18
|
+
## Manifest v2 Webapp MUST NOT
|
|
19
|
+
|
|
20
|
+
1. Do not stage `.env*`, source maps, `node_modules`, `src`, Service Workers, or binary assets.
|
|
21
|
+
2. Do not use `BrowserRouter`, root-absolute URLs, Node APIs, or arbitrary `http:`/`https:`/`npm:`/`jsr:` imports. `Deno.env` is allowed only for the platform public three-key whitelist (`SUPABASE_URL` / `SUPABASE_ANON_KEY` / `SUPABASE_INTERNAL_URL`); any other variable or `Deno.env.toObject()` is a `deno-env-not-allowed` blocker, and custom Secrets are a `custom-secrets-not-supported` blocker. Fetch the data plane through `SUPABASE_INTERNAL_URL` only — `SUPABASE_URL` is render-only.
|
|
22
|
+
3. Do not publish, rollback, or unpublish without the locally recorded `expectedGeneration`; a conflict requires state reconciliation.
|
|
23
|
+
4. Do not label a URL Public unless capabilities report isolated origin, enforced host, and public exposure.
|
|
24
|
+
|
|
25
|
+
## Legacy modes MUST
|
|
26
|
+
|
|
27
|
+
1. **One function, one prefix.** Serve the page(s) and the JSON API under the same `/functions/v1/{slug}` prefix. In single-file mode one `Deno.serve` handles both (branch on `?action=`); in `mpa` mode the proxy serves static pages/assets by path and the worker handles `?action=`.
|
|
28
|
+
2. **Relative URLs for client calls.** Use `location.pathname + "?action=time"` (or `new URL("?action=time", location.href)`), never `fetch("/action")`. In `mpa`, call the API from the *directory base* (`./?action=time` or a computed base ending in `/`) so a sub-page like `about.html` does not turn the call into `about.html?action=time`.
|
|
29
|
+
3. **Assets: inline (single-file) or real files (mpa).** `spa`/`api` must inline all CSS/JS in the one file. `mpa` deploys real **text** files (`.html`/`.css`/`.js`/`.svg`/`.json`), flat or in **nested subdirs** (`assets/`, `pages/`) — reference them with **relative** links and list every page/asset in `app.json` `pages`/`assets` so spec-lint (`mpa-assets-exist`) and the proxy static branch can find them.
|
|
30
|
+
4. **Escape reflected input.** Anything from the request echoed into HTML must be escaped (`&`,`<`,`>`,`"`) — the URL is public, so this is an XSS guard.
|
|
31
|
+
5. **CORS + OPTIONS.** Return `Access-Control-Allow-*` headers and answer `OPTIONS` with 204 when the page calls the API.
|
|
32
|
+
6. **Stay small and fast.** ~150MB memory, CPU ~10s soft/20s hard, ~5min wall; one sandbox shared per tenant. In `mpa` each static file must be ≤ `STATIC_MAX_BYTES` (default 256 KB) or the proxy falls through to the worker.
|
|
33
|
+
|
|
34
|
+
## Legacy modes MUST NOT
|
|
35
|
+
|
|
36
|
+
1. **No root-absolute paths.** `href="/x"`, `src="/x"`, `fetch("/x")`, `url(/x)`, `<base href="/">` — these resolve against the site root, escape `/functions/v1/`, and land on Studio → broken page/assets. This is the #1 bug.
|
|
37
|
+
2. **No external CDN `<script>`/`<link>` for core assets.** Inline them. (Legacy edge functions may `fetch()` public APIs at runtime, though the page must not depend on a CDN to render. This does **not** apply to Webapp backends: they are `egressPolicy="deny"` — public egress is blocked and only the platform-internal base `SUPABASE_INTERNAL_URL` is reachable.)
|
|
38
|
+
3. **No Node.js APIs / build output / large binaries / base64 blobs.** It is Deno; single-file mode is memory-capped, and `mpa` static files are capped at `STATIC_MAX_BYTES` (256 KB). **Binary assets (png/jpg/gif/webp/ico/woff/woff2/ttf/…) are BLOCKED at deploy** (`no-binary-assets`): the utf-8 upload pipeline silently corrupts them. Put images/fonts/video on **Storage/OSS** (`pxbaas storage cp`) and reference them by URL, never as function files.
|
|
39
|
+
4. **No secrets in a `verify_jwt=false` function.** It is public and the slug is enumerable. Put secrets/private logic behind `--verify-jwt` or a separate function; keep DB keys server-side, never in client HTML.
|
|
40
|
+
5. **No DDL / long jobs in the request path** (CPU/wall limits; DDL implicitly commits and is often blocked for tenant users).
|
|
41
|
+
|
|
42
|
+
## Common pitfalls → diagnosis → fix
|
|
43
|
+
|
|
44
|
+
| Symptom | Likely cause | Fix |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| Webapp deploy is blocked before upload | schema v2, App endpoint, static, Deno, or Hono capability is unavailable | inspect `pxbaas app capabilities --json`; do not bypass the gate |
|
|
47
|
+
| React page loads but route refresh/navigation fails | `BrowserRouter` or Vite root base | use `HashRouter` and `base: "./"` |
|
|
48
|
+
| Frontend artifact is rejected | `.env`, source map, source tree, `node_modules`, Service Worker, or binary file was staged | build cleanly and upload only bounded static output; move binaries to Storage/OSS |
|
|
49
|
+
| Backend artifact is rejected | missing `Deno.serve`, Node API, remote import, Secrets, or non-deny egress | use the Deno/Hono template and first-release policy |
|
|
50
|
+
| Publish returns `APP_PUBLISH_CONFLICT` | local generation is stale | fetch/reconcile mount state; never retry with a guessed generation |
|
|
51
|
+
| Output says Preview instead of Public | origin isolation/host enforcement/public exposure is incomplete | treat it as Preview; do not present it as publicly isolated |
|
|
52
|
+
| Page loads but CSS/JS missing, or you land on Studio/404 | root-absolute asset path (`/x`) | use inline assets or relative paths; never `/`-rooted refs |
|
|
53
|
+
| Button click does nothing / API 404 | client used `fetch("/api")` (absolute) | use `fetch(location.pathname + "?action=...")` |
|
|
54
|
+
| First visit blank or 503, later visits fine | sandbox cold start (paused → resume) | retry with backoff (~60s); optionally warm-up ping; tell the user |
|
|
55
|
+
| CORS error in browser | missing CORS/OPTIONS handling | return `Access-Control-Allow-*` and 204 on OPTIONS |
|
|
56
|
+
| Reflected text breaks layout / XSS risk | unescaped input in HTML | escape with an `esc()` helper |
|
|
57
|
+
| Deploy 400 / parse error | multipart `metadata` sent as a file part | send `metadata` as a plain form field WITHOUT filename (see deploy-options.md) |
|
|
58
|
+
| Deploy 429 | function quota (5/tenant) reached | remove an unused function or request a higher quota; do NOT loop |
|
|
59
|
+
| Share URL works for you, not others | endpoint is localhost/private, or sandbox-internal URL | share only `{public endpoint}/functions/v1/{slug}`; check the instance is publicly reachable + IP whitelist |
|
|
60
|
+
| `Deno.serve` never invoked / worker error | used Node APIs or bound a port | use top-level `Deno.serve(handler)`, no `listen()` |
|
|
61
|
+
| Function runs but data calls 401 | used service_role in client, or wrong key | call REST with the anon key server-side; never expose service_role |
|
|
62
|
+
| mpa: `style.css`/`about.html` 404 or served by the worker | file not listed in `app.json` `pages`/`assets`, missing on disk, or `app.json` `mode` is not `mpa` | add the real file, list it under `pages`/`assets`, keep `"mode": "mpa"`; `pxbaas app verify` probes each |
|
|
63
|
+
| mpa: page loads but unstyled / assets land on Studio | asset linked root-absolute (`/style.css`) | link relatively (`style.css`, `./app.js`) |
|
|
64
|
+
| mpa: API button 404 / returns HTML | called `?action=` from a sub-page (resolved to `about.html?action=`) | call the directory base: `./?action=time` or computed base ending in `/` |
|
|
65
|
+
| mpa: large image/font not served | file > `STATIC_MAX_BYTES` (256 KB) falls through to worker | host large binaries on Storage/OSS and reference by URL |
|
|
66
|
+
| mpa: deploy blocked with `no-binary-assets` | a png/jpg/woff/… is in the app dir | binary files corrupt under utf-8 upload — move them to Storage/OSS and link by URL |
|
|
67
|
+
| mpa: image renders as garbage | a binary was uploaded as a function file (older build) | never upload binaries as function files; use Storage/OSS |
|
|
68
|
+
| webapp: publish returns `APP_MANIFEST_INVALID` | candidate `app.json` `name` ≠ the mount's `appName`, or the manifest is legacy v1 while the instance only accepts schemaVersion 2 | set `name` = `appName`; migrate v1→v2 (deploy-options.md) — the error text does not name the offending field |
|
|
69
|
+
| webapp: publish returns `APP_SSR_PROBE_FAILED` (timeouts, then `http_500`) | **platform-side**: a cold tenant sandbox cannot resolve/compile the worker's npm imports within the probe budget | warm that tenant's sandbox with a legacy function, then retry publish 3–5× — do NOT rewrite a worker that already returns 200 when called directly |
|
|
70
|
+
| webapp: publish returns `APP_ARTIFACT_INVALID: Candidate is already active` | idempotent re-run of the same candidate | treat as success |
|
|
71
|
+
| webapp: static pages 404 but the worker API works | multipart upload flattened nested paths into the bundle root | upload each file with an explicit `filename=frontend/dist/<name>` |
|
|
72
|
+
| webapp: worker sees no user identity under `/apps/{mount}/` | `TRUSTED_INTERNAL` strips client `Authorization`/`Cookie` before the worker | read the proxy-injected `X-App-User-*` headers; establish the session via `./_session/login` (ssr-session.md) |
|
|
73
|
+
| multi-tenant: RLS policies silently do not apply | policies written with `table_schema='public'`, or into the tenant DB's unread `_mysqlrest_rls_*` copies | write to the central `supabase._mysqlrest_rls_*` with `table_schema='<tenant db>'` — see pxbaas-cli `references/multi-tenant-apps.md` |
|
|
74
|
+
|
|
75
|
+
## Prefix-safety quick check (before deploy)
|
|
76
|
+
|
|
77
|
+
Scan the source for root-absolute references:
|
|
78
|
+
- `href="/…"`, `src="/…"`, `action="/…"`, `fetch("/…"`, `url(/…)` → replace with relative/inline.
|
|
79
|
+
- Protocol-relative `//cdn/…` for core assets → inline instead.
|
|
80
|
+
- Any hardcoded absolute instance URL in client code → use `location.origin + location.pathname`.
|
|
81
|
+
|
|
82
|
+
`pxbaas app deploy` runs this scan as part of spec-lint. In Manifest v2 it is a blocking artifact error; fix it before deploying.
|
|
83
|
+
|
|
84
|
+
## Public-safety checklist (verify_jwt=false)
|
|
85
|
+
|
|
86
|
+
- No API keys, DB passwords, or tokens anywhere in the returned HTML/JS.
|
|
87
|
+
- No private/admin actions reachable without auth (the function is public).
|
|
88
|
+
- Escape all reflected input.
|
|
89
|
+
- If the demo needs protected logic, split it: a public `verify_jwt=false` front function + a separate `verify_jwt=true` function for privileged actions.
|