@polardbx/pxbaas 0.0.0-stage → 0.1.0-beta.10

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.
Files changed (312) hide show
  1. package/README.md +223 -2
  2. package/bin/pxbaas.js +13 -0
  3. package/dist/app/local-index.d.ts +70 -0
  4. package/dist/app/local-index.d.ts.map +1 -0
  5. package/dist/app/local-index.js +140 -0
  6. package/dist/app/local-index.js.map +1 -0
  7. package/dist/app/preflight.d.ts +61 -0
  8. package/dist/app/preflight.d.ts.map +1 -0
  9. package/dist/app/preflight.js +115 -0
  10. package/dist/app/preflight.js.map +1 -0
  11. package/dist/app/spec-lint.d.ts +45 -0
  12. package/dist/app/spec-lint.d.ts.map +1 -0
  13. package/dist/app/spec-lint.js +423 -0
  14. package/dist/app/spec-lint.js.map +1 -0
  15. package/dist/app/templates.d.ts +167 -0
  16. package/dist/app/templates.d.ts.map +1 -0
  17. package/dist/app/templates.js +151 -0
  18. package/dist/app/templates.js.map +1 -0
  19. package/dist/cli/help-guides.d.ts +17 -0
  20. package/dist/cli/help-guides.d.ts.map +1 -0
  21. package/dist/cli/help-guides.js +132 -0
  22. package/dist/cli/help-guides.js.map +1 -0
  23. package/dist/cli/root.d.ts +3 -0
  24. package/dist/cli/root.d.ts.map +1 -0
  25. package/dist/cli/root.js +75 -0
  26. package/dist/cli/root.js.map +1 -0
  27. package/dist/commands/app/index.d.ts +8 -0
  28. package/dist/commands/app/index.d.ts.map +1 -0
  29. package/dist/commands/app/index.js +1907 -0
  30. package/dist/commands/app/index.js.map +1 -0
  31. package/dist/commands/auth/index.d.ts +3 -0
  32. package/dist/commands/auth/index.d.ts.map +1 -0
  33. package/dist/commands/auth/index.js +198 -0
  34. package/dist/commands/auth/index.js.map +1 -0
  35. package/dist/commands/completion/index.d.ts +3 -0
  36. package/dist/commands/completion/index.d.ts.map +1 -0
  37. package/dist/commands/completion/index.js +14 -0
  38. package/dist/commands/completion/index.js.map +1 -0
  39. package/dist/commands/data/index.d.ts +55 -0
  40. package/dist/commands/data/index.d.ts.map +1 -0
  41. package/dist/commands/data/index.js +407 -0
  42. package/dist/commands/data/index.js.map +1 -0
  43. package/dist/commands/db/index.d.ts +29 -0
  44. package/dist/commands/db/index.d.ts.map +1 -0
  45. package/dist/commands/db/index.js +500 -0
  46. package/dist/commands/db/index.js.map +1 -0
  47. package/dist/commands/doctor/index.d.ts +3 -0
  48. package/dist/commands/doctor/index.d.ts.map +1 -0
  49. package/dist/commands/doctor/index.js +305 -0
  50. package/dist/commands/doctor/index.js.map +1 -0
  51. package/dist/commands/functions/delete.d.ts +3 -0
  52. package/dist/commands/functions/delete.d.ts.map +1 -0
  53. package/dist/commands/functions/delete.js +55 -0
  54. package/dist/commands/functions/delete.js.map +1 -0
  55. package/dist/commands/functions/deploy.d.ts +3 -0
  56. package/dist/commands/functions/deploy.d.ts.map +1 -0
  57. package/dist/commands/functions/deploy.js +118 -0
  58. package/dist/commands/functions/deploy.js.map +1 -0
  59. package/dist/commands/functions/index.d.ts +3 -0
  60. package/dist/commands/functions/index.d.ts.map +1 -0
  61. package/dist/commands/functions/index.js +15 -0
  62. package/dist/commands/functions/index.js.map +1 -0
  63. package/dist/commands/functions/list.d.ts +6 -0
  64. package/dist/commands/functions/list.d.ts.map +1 -0
  65. package/dist/commands/functions/list.js +63 -0
  66. package/dist/commands/functions/list.js.map +1 -0
  67. package/dist/commands/functions/new.d.ts +7 -0
  68. package/dist/commands/functions/new.d.ts.map +1 -0
  69. package/dist/commands/functions/new.js +72 -0
  70. package/dist/commands/functions/new.js.map +1 -0
  71. package/dist/commands/gen/index.d.ts +3 -0
  72. package/dist/commands/gen/index.d.ts.map +1 -0
  73. package/dist/commands/gen/index.js +323 -0
  74. package/dist/commands/gen/index.js.map +1 -0
  75. package/dist/commands/guide/index.d.ts +8 -0
  76. package/dist/commands/guide/index.d.ts.map +1 -0
  77. package/dist/commands/guide/index.js +20 -0
  78. package/dist/commands/guide/index.js.map +1 -0
  79. package/dist/commands/instance/index.d.ts +8 -0
  80. package/dist/commands/instance/index.d.ts.map +1 -0
  81. package/dist/commands/instance/index.js +481 -0
  82. package/dist/commands/instance/index.js.map +1 -0
  83. package/dist/commands/login/index.d.ts +3 -0
  84. package/dist/commands/login/index.d.ts.map +1 -0
  85. package/dist/commands/login/index.js +145 -0
  86. package/dist/commands/login/index.js.map +1 -0
  87. package/dist/commands/logout/index.d.ts +3 -0
  88. package/dist/commands/logout/index.d.ts.map +1 -0
  89. package/dist/commands/logout/index.js +41 -0
  90. package/dist/commands/logout/index.js.map +1 -0
  91. package/dist/commands/profile/import-env.d.ts +18 -0
  92. package/dist/commands/profile/import-env.d.ts.map +1 -0
  93. package/dist/commands/profile/import-env.js +206 -0
  94. package/dist/commands/profile/import-env.js.map +1 -0
  95. package/dist/commands/profile/index.d.ts +3 -0
  96. package/dist/commands/profile/index.d.ts.map +1 -0
  97. package/dist/commands/profile/index.js +124 -0
  98. package/dist/commands/profile/index.js.map +1 -0
  99. package/dist/commands/rest/index.d.ts +3 -0
  100. package/dist/commands/rest/index.d.ts.map +1 -0
  101. package/dist/commands/rest/index.js +102 -0
  102. package/dist/commands/rest/index.js.map +1 -0
  103. package/dist/commands/skill/index.d.ts +11 -0
  104. package/dist/commands/skill/index.d.ts.map +1 -0
  105. package/dist/commands/skill/index.js +228 -0
  106. package/dist/commands/skill/index.js.map +1 -0
  107. package/dist/commands/status/index.d.ts +3 -0
  108. package/dist/commands/status/index.d.ts.map +1 -0
  109. package/dist/commands/status/index.js +59 -0
  110. package/dist/commands/status/index.js.map +1 -0
  111. package/dist/commands/storage/index.d.ts +3 -0
  112. package/dist/commands/storage/index.d.ts.map +1 -0
  113. package/dist/commands/storage/index.js +400 -0
  114. package/dist/commands/storage/index.js.map +1 -0
  115. package/dist/commands/tenant/index.d.ts +19 -0
  116. package/dist/commands/tenant/index.d.ts.map +1 -0
  117. package/dist/commands/tenant/index.js +667 -0
  118. package/dist/commands/tenant/index.js.map +1 -0
  119. package/dist/commands/whoami/index.d.ts +3 -0
  120. package/dist/commands/whoami/index.d.ts.map +1 -0
  121. package/dist/commands/whoami/index.js +101 -0
  122. package/dist/commands/whoami/index.js.map +1 -0
  123. package/dist/credentials/commit.d.ts +38 -0
  124. package/dist/credentials/commit.d.ts.map +1 -0
  125. package/dist/credentials/commit.js +57 -0
  126. package/dist/credentials/commit.js.map +1 -0
  127. package/dist/credentials/credential-store.d.ts +86 -0
  128. package/dist/credentials/credential-store.d.ts.map +1 -0
  129. package/dist/credentials/credential-store.js +282 -0
  130. package/dist/credentials/credential-store.js.map +1 -0
  131. package/dist/credentials/env-file.d.ts +85 -0
  132. package/dist/credentials/env-file.d.ts.map +1 -0
  133. package/dist/credentials/env-file.js +193 -0
  134. package/dist/credentials/env-file.js.map +1 -0
  135. package/dist/credentials/formatter.d.ts +39 -0
  136. package/dist/credentials/formatter.d.ts.map +1 -0
  137. package/dist/credentials/formatter.js +79 -0
  138. package/dist/credentials/formatter.js.map +1 -0
  139. package/dist/credentials/index.d.ts +13 -0
  140. package/dist/credentials/index.d.ts.map +1 -0
  141. package/dist/credentials/index.js +13 -0
  142. package/dist/credentials/index.js.map +1 -0
  143. package/dist/credentials/jwt-introspect.d.ts +39 -0
  144. package/dist/credentials/jwt-introspect.d.ts.map +1 -0
  145. package/dist/credentials/jwt-introspect.js +64 -0
  146. package/dist/credentials/jwt-introspect.js.map +1 -0
  147. package/dist/credentials/masker.d.ts +23 -0
  148. package/dist/credentials/masker.d.ts.map +1 -0
  149. package/dist/credentials/masker.js +40 -0
  150. package/dist/credentials/masker.js.map +1 -0
  151. package/dist/credentials/profile.d.ts +95 -0
  152. package/dist/credentials/profile.d.ts.map +1 -0
  153. package/dist/credentials/profile.js +207 -0
  154. package/dist/credentials/profile.js.map +1 -0
  155. package/dist/credentials/tenant-guard.d.ts +16 -0
  156. package/dist/credentials/tenant-guard.d.ts.map +1 -0
  157. package/dist/credentials/tenant-guard.js +53 -0
  158. package/dist/credentials/tenant-guard.js.map +1 -0
  159. package/dist/credentials/tty-guard.d.ts +29 -0
  160. package/dist/credentials/tty-guard.d.ts.map +1 -0
  161. package/dist/credentials/tty-guard.js +79 -0
  162. package/dist/credentials/tty-guard.js.map +1 -0
  163. package/dist/db/apply-schema.d.ts +144 -0
  164. package/dist/db/apply-schema.d.ts.map +1 -0
  165. package/dist/db/apply-schema.js +359 -0
  166. package/dist/db/apply-schema.js.map +1 -0
  167. package/dist/db/introspect.d.ts +54 -0
  168. package/dist/db/introspect.d.ts.map +1 -0
  169. package/dist/db/introspect.js +140 -0
  170. package/dist/db/introspect.js.map +1 -0
  171. package/dist/db/sql-file.d.ts +104 -0
  172. package/dist/db/sql-file.d.ts.map +1 -0
  173. package/dist/db/sql-file.js +584 -0
  174. package/dist/db/sql-file.js.map +1 -0
  175. package/dist/instance/provision.d.ts +89 -0
  176. package/dist/instance/provision.d.ts.map +1 -0
  177. package/dist/instance/provision.js +139 -0
  178. package/dist/instance/provision.js.map +1 -0
  179. package/dist/instance/zero-store.d.ts +47 -0
  180. package/dist/instance/zero-store.d.ts.map +1 -0
  181. package/dist/instance/zero-store.js +91 -0
  182. package/dist/instance/zero-store.js.map +1 -0
  183. package/dist/instance/zero-whitelist.d.ts +39 -0
  184. package/dist/instance/zero-whitelist.d.ts.map +1 -0
  185. package/dist/instance/zero-whitelist.js +67 -0
  186. package/dist/instance/zero-whitelist.js.map +1 -0
  187. package/dist/main.d.ts +3 -0
  188. package/dist/main.d.ts.map +1 -0
  189. package/dist/main.js +69 -0
  190. package/dist/main.js.map +1 -0
  191. package/dist/skill/agents-md.d.ts +25 -0
  192. package/dist/skill/agents-md.d.ts.map +1 -0
  193. package/dist/skill/agents-md.js +62 -0
  194. package/dist/skill/agents-md.js.map +1 -0
  195. package/dist/skill/assets.d.ts +38 -0
  196. package/dist/skill/assets.d.ts.map +1 -0
  197. package/dist/skill/assets.js +111 -0
  198. package/dist/skill/assets.js.map +1 -0
  199. package/dist/skill/hint.d.ts +39 -0
  200. package/dist/skill/hint.d.ts.map +1 -0
  201. package/dist/skill/hint.js +109 -0
  202. package/dist/skill/hint.js.map +1 -0
  203. package/dist/skill/hosts.d.ts +45 -0
  204. package/dist/skill/hosts.d.ts.map +1 -0
  205. package/dist/skill/hosts.js +96 -0
  206. package/dist/skill/hosts.js.map +1 -0
  207. package/dist/skill/installer.d.ts +100 -0
  208. package/dist/skill/installer.d.ts.map +1 -0
  209. package/dist/skill/installer.js +241 -0
  210. package/dist/skill/installer.js.map +1 -0
  211. package/dist/skill/ownership.d.ts +35 -0
  212. package/dist/skill/ownership.d.ts.map +1 -0
  213. package/dist/skill/ownership.js +52 -0
  214. package/dist/skill/ownership.js.map +1 -0
  215. package/dist/tenant/bundle.d.ts +66 -0
  216. package/dist/tenant/bundle.d.ts.map +1 -0
  217. package/dist/tenant/bundle.js +132 -0
  218. package/dist/tenant/bundle.js.map +1 -0
  219. package/dist/tenant/guide.d.ts +50 -0
  220. package/dist/tenant/guide.d.ts.map +1 -0
  221. package/dist/tenant/guide.js +152 -0
  222. package/dist/tenant/guide.js.map +1 -0
  223. package/dist/tenant/probe.d.ts +65 -0
  224. package/dist/tenant/probe.d.ts.map +1 -0
  225. package/dist/tenant/probe.js +159 -0
  226. package/dist/tenant/probe.js.map +1 -0
  227. package/dist/tenant/verify.d.ts +37 -0
  228. package/dist/tenant/verify.d.ts.map +1 -0
  229. package/dist/tenant/verify.js +132 -0
  230. package/dist/tenant/verify.js.map +1 -0
  231. package/dist/version.d.ts +29 -0
  232. package/dist/version.d.ts.map +1 -0
  233. package/dist/version.js +65 -0
  234. package/dist/version.js.map +1 -0
  235. package/package.json +63 -3
  236. package/skills/pxbaas-app-spec/SKILL.md +165 -0
  237. package/skills/pxbaas-app-spec/references/deploy-options.md +158 -0
  238. package/skills/pxbaas-app-spec/references/guardrails-pitfalls.md +89 -0
  239. package/skills/pxbaas-app-spec/references/runtime-contract.md +151 -0
  240. package/skills/pxbaas-app-spec/references/ssr-session.md +121 -0
  241. package/skills/pxbaas-app-spec/references/templates-examples.md +311 -0
  242. package/skills/pxbaas-cli/SKILL.md +186 -0
  243. package/skills/pxbaas-cli/references/api-reference.md +68 -0
  244. package/skills/pxbaas-cli/references/app-deploy.md +186 -0
  245. package/skills/pxbaas-cli/references/auth-and-credentials.md +116 -0
  246. package/skills/pxbaas-cli/references/data-postgrest.md +101 -0
  247. package/skills/pxbaas-cli/references/db-and-meta.md +102 -0
  248. package/skills/pxbaas-cli/references/edge-functions.md +71 -0
  249. package/skills/pxbaas-cli/references/multi-tenant-apps.md +138 -0
  250. package/skills/pxbaas-cli/references/provision.md +101 -0
  251. package/skills/pxbaas-cli/references/safety-and-two-phase.md +78 -0
  252. package/skills/pxbaas-cli/references/storage.md +61 -0
  253. package/skills/pxbaas-cli/references/tenant-admin.md +122 -0
  254. package/skills/pxbaas-cli/references/troubleshooting.md +76 -0
  255. package/templates/webapp/api/index.ts +55 -0
  256. package/templates/webapp/csr-bff/README.md +81 -0
  257. package/templates/webapp/csr-bff/backend/index.ts +168 -0
  258. package/templates/webapp/csr-bff/db/rls.json +13 -0
  259. package/templates/webapp/csr-bff/db/schema.sql +49 -0
  260. package/templates/webapp/csr-bff/frontend/dist/admin.html +101 -0
  261. package/templates/webapp/csr-bff/frontend/dist/article.html +44 -0
  262. package/templates/webapp/csr-bff/frontend/dist/blog.js +206 -0
  263. package/templates/webapp/csr-bff/frontend/dist/blogger.html +53 -0
  264. package/templates/webapp/csr-bff/frontend/dist/index.html +70 -0
  265. package/templates/webapp/csr-bff/frontend/dist/login.html +56 -0
  266. package/templates/webapp/csr-bff/frontend/dist/profile.html +59 -0
  267. package/templates/webapp/csr-bff/frontend/dist/signup.html +78 -0
  268. package/templates/webapp/csr-bff/frontend/dist/style.css +27 -0
  269. package/templates/webapp/manifest.json +419 -0
  270. package/templates/webapp/mpa/about.html +29 -0
  271. package/templates/webapp/mpa/app.js +42 -0
  272. package/templates/webapp/mpa/index.html +42 -0
  273. package/templates/webapp/mpa/index.ts +60 -0
  274. package/templates/webapp/mpa/style.css +42 -0
  275. package/templates/webapp/react-vite/frontend/index.html +12 -0
  276. package/templates/webapp/react-vite/frontend/package-lock.json +1826 -0
  277. package/templates/webapp/react-vite/frontend/package.json +23 -0
  278. package/templates/webapp/react-vite/frontend/src/App.tsx +34 -0
  279. package/templates/webapp/react-vite/frontend/src/main.tsx +13 -0
  280. package/templates/webapp/react-vite/frontend/src/style.css +46 -0
  281. package/templates/webapp/react-vite/frontend/tsconfig.json +20 -0
  282. package/templates/webapp/react-vite/frontend/vite.config.ts +10 -0
  283. package/templates/webapp/react-vite-hono/backend/index.ts +38 -0
  284. package/templates/webapp/react-vite-hono/frontend/index.html +12 -0
  285. package/templates/webapp/react-vite-hono/frontend/package-lock.json +1826 -0
  286. package/templates/webapp/react-vite-hono/frontend/package.json +23 -0
  287. package/templates/webapp/react-vite-hono/frontend/src/App.tsx +47 -0
  288. package/templates/webapp/react-vite-hono/frontend/src/main.tsx +13 -0
  289. package/templates/webapp/react-vite-hono/frontend/src/style.css +66 -0
  290. package/templates/webapp/react-vite-hono/frontend/tsconfig.json +20 -0
  291. package/templates/webapp/react-vite-hono/frontend/vite.config.ts +10 -0
  292. package/templates/webapp/spa/index.ts +119 -0
  293. package/templates/webapp/ssr-auth/backend/index.ts +133 -0
  294. package/templates/webapp/ssr-auth/frontend/dist/index.html +15 -0
  295. package/templates/webapp/ssr-blog/README.md +111 -0
  296. package/templates/webapp/ssr-blog/backend/index.ts +764 -0
  297. package/templates/webapp/ssr-blog/db/rls.json +13 -0
  298. package/templates/webapp/ssr-blog/db/schema.sql +51 -0
  299. package/templates/webapp/ssr-blog/frontend/dist/index.html +39 -0
  300. package/templates/webapp/ssr-frameworks/README.md +64 -0
  301. package/templates/webapp/ssr-frameworks/backend/index.ts +241 -0
  302. package/templates/webapp/ssr-frameworks/frontend/dist/index.html +31 -0
  303. package/templates/webapp/ssr-hono/backend/index.ts +111 -0
  304. package/templates/webapp/ssr-hono/frontend/dist/index.html +12 -0
  305. package/templates/webapp/ssr-shop/README.md +80 -0
  306. package/templates/webapp/ssr-shop/backend/index.ts +491 -0
  307. package/templates/webapp/ssr-shop/db/rls.json +16 -0
  308. package/templates/webapp/ssr-shop/db/schema.sql +90 -0
  309. package/templates/webapp/ssr-shop/frontend/dist/index.html +12 -0
  310. package/templates/webapp/vanilla-static/frontend/dist/app.js +6 -0
  311. package/templates/webapp/vanilla-static/frontend/dist/index.html +19 -0
  312. package/templates/webapp/vanilla-static/frontend/dist/style.css +43 -0
package/README.md CHANGED
@@ -1,3 +1,224 @@
1
- # Temporary Holding Version
1
+ # @polardbx/pxbaas
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Command-line interface for [PolarDB-X BaaS](https://github.com/polardbx/mysql-supabase) (MySQL-Supabase, Backend as a Service). Run SQL, inspect tables and schemas, query and edit table data over the PostgREST-style REST API, deploy Edge Functions and webapp demos, manage storage objects and auth users, generate TypeScript types — and claim a free disposable BaaS instance — all from your terminal.
4
+
5
+ ## Requirements
6
+
7
+ - Node.js >= 20
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ # Public npm (prerelease line publishes under the `beta` dist-tag)
13
+ npm install -g @polardbx/pxbaas@beta
14
+
15
+ # From a repository clone (installer checks Node >= 20, never uses sudo,
16
+ # and verifies the binary with --version + --help after install)
17
+ git clone https://github.com/polardbx/mysql-supabase.git
18
+ cd mysql-supabase/cli
19
+ bash install.sh --yes
20
+
21
+ # Air-gapped environments: build an offline bundle (tarballs + optional Node runtime)
22
+ bash scripts/pack-offline.sh
23
+ ```
24
+
25
+ `@polardbx/pxbaas` depends on `@polardbx/pxbaas-client`; both are published to npm in lockstep, so a plain global install resolves everything. If the OS keychain is unavailable, credentials fall back to an encrypted `~/.baas/credentials.json` (mode 0600).
26
+
27
+ ## Quick Start
28
+
29
+ ```bash
30
+ # No instance yet? Claim a free disposable PolarDB-X Zero BaaS instance.
31
+ # Agent-safe: keys arrive over TLS and only fingerprints are printed.
32
+ pxbaas provision --json
33
+
34
+ # Already have an endpoint + keys? Interactive login (human-only, requires a TTY).
35
+ pxbaas login
36
+
37
+ pxbaas whoami # current profile + key fingerprints (never plaintext keys)
38
+ pxbaas doctor # connectivity / configuration diagnostics
39
+ pxbaas status # instance reachability summary
40
+ ```
41
+
42
+ ## For coding agents (start here)
43
+
44
+ Run `pxbaas guide` first: it prints the recommended end-to-end workflow for
45
+ developing, deploying, verifying and sharing an app — instance binding,
46
+ capability checks, scoped approvals, stage-by-stage failure recovery, and
47
+ network/whitelist handling — without needing credentials or network access.
48
+ Then install the bundled skills (`pxbaas skill install --host
49
+ <qoder|claude|cursor|qwen|hermes|codex|gemini|copilot|qwenworkcn|agents>`, or
50
+ `--dir <path>` / `--scope project` for other agent hosts and repo-local
51
+ conventions) and follow `pxbaas-cli` / `pxbaas-app-spec`. `pxbaas login` needs
52
+ a human TTY; agents should bind instances with `pxbaas profile import
53
+ <env-file>` or, with user approval, `pxbaas provision`.
54
+
55
+ Every command accepts `--json` for machine-readable output, and `--profile <name>` to select a profile per invocation (`pxbaas profile ls / use / rm / import` manage them).
56
+
57
+ ### Non-interactive credential setup
58
+
59
+ Agents and CI have two TTY-free paths; keys never pass through argv or shell history:
60
+
61
+ ```bash
62
+ # 1) stdin mode: pipe exactly two lines - anon key, then service_role key.
63
+ # --anon-key-stdin and --service-key-stdin must always be given together.
64
+ pxbaas login --endpoint https://baas.example.com \
65
+ [--tenant-id <tenant>] \
66
+ --anon-key-stdin --service-key-stdin <<'EOF'
67
+ <anon-key>
68
+ <service-role-key>
69
+ EOF
70
+
71
+ # 2) import from a Supabase-convention .env file (single- or multi-tenant)
72
+ pxbaas profile import ./instance.env --name prod --json
73
+ ```
74
+
75
+ `profile import` resolves variables in first-match order and validates JWT expiry + tenant consistency before storing the profile:
76
+
77
+ | Field | Accepted env variable names (first match wins) |
78
+ | --- | --- |
79
+ | Endpoint | `SUPABASE_URL`, `SUPABASE_PUBLIC_URL`, `SUPABASE_INTRANET_URL` (`--intranet` prefers the intranet URL) |
80
+ | anon key | `SUPABASE_ANON_KEY`, `ANON_KEY`, `GLOBAL_ANON_KEY` |
81
+ | service_role key | `SUPABASE_SERVICE_ROLE_KEY`, `SERVICE_ROLE_KEY`, `GLOBAL_SERVICE_ROLE_KEY` |
82
+
83
+ For a **multi-tenant** instance the env file must carry tenant-level credentials — `MULTI_TENANT_MODE=true`, `DEFAULT_TENANT_ID`, `TENANT_ANON_KEY`, `TENANT_SERVICE_ROLE_KEY`. Platform-global keys (`GLOBAL_*`, whose JWTs have no `tenant_id` claim) are reported as present-but-not-imported: they cannot reach tenant resources.
84
+
85
+ An env file that is multi-tenant but lacks the tenant keys is refused **with a diagnosis and a route**: if the machine can open the tenant admin channel it points you at `pxbaas tenant …`; otherwise it prints a checklist to send to the instance operator. Nothing is created and nothing is imported in that branch.
86
+
87
+ `DASHBOARD_USERNAME` / `DASHBOARD_PASSWORD` in an env file are never absorbed silently — pass `--with-admin` to bind them, and only after they actually authenticate against the admin channel.
88
+
89
+ ## Multi-tenant
90
+
91
+ On a `MULTI_TENANT_MODE=true` instance, authenticate with that tenant's own keys:
92
+
93
+ ```bash
94
+ pxbaas login --tenant-id <tenant> # paste the tenant's per-tenant anon + service_role keys
95
+ ```
96
+
97
+ A tenant-level key is one whose JWT payload contains `tenant_id`; a platform-global key does not and will be rejected on tenant resources.
98
+
99
+ With a multi-tenant profile, `db query`, `db tables`, `db schema`, `db create-table`, `db add-column`, `db push`, and `gen types` all run through `/api/tenant-ddl` against the tenant's **own** database using its per-tenant `service_role` key (these previously returned 401 in multi-tenant mode). Notes:
100
+
101
+ - `db query` always executes as the tenant `service_role` in multi-tenant mode; `--role anon` is not applicable (warned and ignored). `/pg/query` remains platform-admin only.
102
+ - `db push` / `app db-init` register RLS via the structured `/api/tenant-ddl/rls/*` endpoints (the tenant account has no grant on the central `supabase` registry); `table_schema` is bound server-side and the `__SCHEMA__` placeholder is de-qualified automatically. RLS source is hybrid: declarative `db/rls.json` preferred, else RLS INSERTs parsed from `schema.sql`.
103
+ - `db create-table` / `db add-column` work for a tenant too: the DDL lands in the tenant database, so `--schema` is ignored (the effective database is printed) and `--role anon` is ignored.
104
+ - A normal tenant can run the full webapp lifecycle (`db-init` → `deploy` → `publish/rollback/unpublish` → `destroy`) with only its per-tenant key; cross-tenant access is rejected.
105
+
106
+ ## Tenant administration (instance operators)
107
+
108
+ Ordinary tenants never use this. It requires the instance dashboard credentials (HTTP Basic), which live in a
109
+ separate keyring account from your tenant API keys and are refused in argv.
110
+
111
+ ```bash
112
+ pxbaas tenant login --endpoint https://<gateway> # masked prompt, or --user-stdin --password-stdin
113
+ pxbaas tenant probe --json # admin / not-admin / single-tenant / metadata-unreadable / unreachable
114
+ pxbaas tenant ls --json # redacted: ids, status, plan, db, key-presence booleans
115
+
116
+ # hand an existing tenant's keys to its user
117
+ pxbaas tenant keys acme --as-profile # into this machine's keyring
118
+ pxbaas tenant keys acme --bundle ./acme.pxbaas.env # 0600 file to hand over
119
+
120
+ # only when a tenant really must be created
121
+ pxbaas tenant create acme --dry-run --json
122
+ pxbaas tenant create acme --as-profile --bundle ./acme.pxbaas.env
123
+ pxbaas tenant repair-keys # preview; --confirm to run
124
+ ```
125
+
126
+ - `tenant create` shows a plan and requires authorization, and **refuses a colliding tenant id** — reusing a soft-deleted id resurrects that tenant and regenerates its keys, so it needs an explicit `--reactivate`.
127
+ - After creating, the CLI verifies the tenant is actually usable (`SELECT DATABASE()`, table count, RLS registry, Realtime registration), because the platform treats those provisioning steps as non-fatal. A failing check exits non-zero.
128
+ - An **access bundle** contains exactly `MULTI_TENANT_MODE`, `SUPABASE_URL`, `DEFAULT_TENANT_ID`, `TENANT_ANON_KEY`, `TENANT_SERVICE_ROLE_KEY` — never `jwt_secret`, `DASHBOARD_*` or `GLOBAL_*` — and is consumed directly by `pxbaas profile import`. The command prints a handoff note with both key fingerprints so the receiver can prove nothing was swapped in transit; `pxbaas tenant handoffs` keeps a local ledger of fingerprints and file hashes.
129
+ - Bundles hold **plaintext, ~10-year, currently irrevocable** credentials. Transmit them over a secure channel and delete the file after import. Rotation is not available: the platform does not accept `jwt_secret` updates, and re-signing keys does not invalidate old ones.
130
+
131
+ ## Command Reference
132
+
133
+ | Group | Commands |
134
+ | --- | --- |
135
+ | Onboarding & diagnostics | `login`, `logout`, `whoami`, `status`, `doctor`, `profile` |
136
+ | Disposable instances (Zero) | `provision` (alias `instance claim`), `instance list / current / credentials / release` |
137
+ | Database | `db query <sql>`, `db tables`, `db schema <table>`, `db push <file> [--rls <rls.json>]`, `db create-table`, `db add-column` |
138
+ | Data (REST) | `data select / insert / update / delete <table>`, `rest <method> <path>` |
139
+ | Edge Functions | `functions new / deploy / list / delete` |
140
+ | Webapp demos | `app new / deploy / verify / url / list / remove / destroy / templates / db-init / capabilities / publish / rollback / unpublish`, `app share ...` |
141
+ | Storage | `storage ls / cp / mv / rm` |
142
+ | Auth admin | `auth users list / get / create / delete / invite` |
143
+ | Codegen | `gen types` (TypeScript types from the live schema) |
144
+ | Tenant administration (operator) | `tenant login / logout / probe / ls / create / keys / repair-keys / handoffs` |
145
+ | Tooling | `skill install / list / remove`, `completion <shell>` (not yet implemented) |
146
+
147
+ ### Provisioning (PolarDB-X Zero)
148
+
149
+ ```bash
150
+ pxbaas provision --json # claim a free 15-day Zero BaaS instance -> binds a profile
151
+ pxbaas instance list --json # Zero instances bound locally
152
+ pxbaas instance release <id> --confirm # release (two-phase; destroys cloud resources)
153
+ ```
154
+
155
+ Keys come from Zero over TLS and are stored in the keyring; output shows only fingerprints. Unlike `pxbaas login`, `pxbaas provision` is safe for agents to run. One instance = one profile (`source: zero`); select with `pxbaas profile use` or `--profile`.
156
+
157
+ ### Webapp demos (edge-function sandbox)
158
+
159
+ ```bash
160
+ pxbaas app new shop --template ssr-shop --json # scaffold a schemaVersion-2 webapp (ships db/)
161
+ pxbaas app db-init shop --dry-run --json # preview the db/schema.sql + db/rls.json plan
162
+ pxbaas app db-init shop --yes --json # create tables + register RLS + seed (authorized)
163
+ pxbaas app deploy shop --dry-run --json # preflight (quota / root-abs refs / public reachability)
164
+ pxbaas app deploy shop --json # upload + publish -> mount URL
165
+ pxbaas app verify shop --json # cold-start retry + content-type + API ping
166
+ pxbaas app url shop --json # webapp: {appEndpoint}/apps/{mount}/; legacy spa/api/mpa: {endpoint}/functions/v1/{slug}
167
+ pxbaas app publish shop --json # publish a new candidate (generation CAS)
168
+ pxbaas app rollback shop --json # roll back one generation
169
+ pxbaas app unpublish shop --json # take the mount offline (reversible)
170
+ pxbaas app destroy shop --confirm --json # delete edge funcs + mount record (DB tables kept)
171
+ pxbaas app destroy shop --confirm --purge-db --json # also drop tables + RLS registry rows (destructive)
172
+ ```
173
+
174
+ `pxbaas app` is a higher-level convenience over `pxbaas functions` for lightweight demos. Templates ship under `templates/` (npm/source install only); `pxbaas app templates` is the authoritative list. The CLI scaffolds but does not generate demo code — fill the scaffold yourself or let your coding agent do it. `app db-init` applies to templates that ship `db/schema.sql` (+ optional `db/rls.json`); `app publish / rollback / unpublish / destroy` operate on schemaVersion-2 webapps (legacy `spa/api/mpa` use `app remove`).
175
+
176
+ ### Secure sharing (`app share`)
177
+
178
+ A default deploy (`verify_jwt=false`) is fully public. For time-boxed, revocable, audience-limited sharing, use secure share links:
179
+
180
+ ```bash
181
+ pxbaas app share create shop --json # default 86400s lifetime; prints the one-time entry URL
182
+ pxbaas app share create shop --expires-in 3600 --json # relative expiry (1 hour)
183
+ pxbaas app share create shop --expires-at 2026-12-31T00:00:00Z --json # absolute RFC3339 expiry (must be in the future)
184
+ pxbaas app share create shop --max-uses 5 --json # cap ticket redemptions (positive int, max 4294967295)
185
+ pxbaas app share list shop --json # never exposes tickets or sessions
186
+ pxbaas app share verify shop <share-id> --json # status check without revealing the link
187
+ pxbaas app share revoke shop <share-id> --confirm --json # disable a link (two-phase)
188
+ ```
189
+
190
+ How it works:
191
+
192
+ - `share create` prints a one-time entry URL of the form `{endpoint}/share/v1/{mountId}/{ticket}`; the first visit exchanges the ticket for a share session cookie, and later page/resource requests ride that session.
193
+ - `--expires-in` and `--expires-at` are mutually exclusive; `--max-uses` is a positive integer.
194
+ - With platform credentials on a multi-tenant instance, pass `--tenant-id <tenant>` explicitly.
195
+ - ⚠️ Redeploying the same slug (`app deploy`) immediately revokes **all** of its active share links — re-create them afterwards.
196
+ - `list` / `verify` never return the ticket or cookie; only `create`'s one-time output contains the entry URL — hand it over right away.
197
+
198
+ ### Agent Skill
199
+
200
+ ```bash
201
+ pxbaas skill install --host qoder --json # install the bundled pxbaas-cli Agent Skill
202
+ pxbaas skill install --all --dry-run # preview for every detected host
203
+ pxbaas skill list # show install state per host
204
+ pxbaas skill remove --host qoder --confirm # remove (only baas-managed dirs)
205
+ ```
206
+
207
+ Installs into supported Agent hosts (`~/.qoder`, `~/.claude`, `~/.cursor`, `~/.qwen`, `~/.hermes`); fail-closed on foreign directories. When an Agent host is detected but the skill is missing, any `pxbaas` command prints a one-time hint on stderr (skipped under `--json`, silenced with `BAAS_NO_SKILL_HINT=1`). The CLI never writes into agent directories on npm install.
208
+
209
+ ## Security Principles
210
+
211
+ 1. **Human-only login**: `pxbaas login` requires an interactive TTY; agents cannot execute it.
212
+ 2. **Agent commands never accept key parameters**: post-login commands read credentials from the keyring.
213
+ 3. **Output masking**: commands show only key fingerprints (sha256 prefix 8 + suffix 4), never plaintext.
214
+ 4. **Two-phase operations**: `db push` / `app db-init` / `data delete` / `tenant create` preview first and require `--dry-run` + `--yes`, and `app remove` / `app destroy` / `app share revoke` / `instance release` / `skill remove` / `tenant repair-keys` require `--confirm`. Commands with **no** gate — `db create-table` / `db add-column`, `functions deploy` / `delete`, `storage rm` / `mv`, `auth users delete`, `rest` writes — execute immediately, so verify the target yourself first.
215
+ 5. **Tenant isolation**: commands validate that the key's tenant matches the profile's tenant before executing.
216
+
217
+ ## Documentation
218
+
219
+ - Repository & monorepo docs: <https://github.com/polardbx/mysql-supabase> (`cli/` directory)
220
+ - Design documents: `mysql_supbase_design/supabase_cli/spcli/` in the repository (capability scope, engineering organization, test design, delivery plan)
221
+
222
+ ## License
223
+
224
+ MIT
package/bin/pxbaas.js ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { fileURLToPath } from 'node:url';
4
+ import { dirname, join } from 'node:path';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
8
+
9
+ // Import and run the CLI
10
+ import(join(__dirname, '..', 'dist', 'main.js')).catch((err) => {
11
+ console.error('Failed to start CLI:', err);
12
+ process.exit(1);
13
+ });
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Workspace-level side index of deployed App mounts (`.pxbaas/apps.json`).
3
+ *
4
+ * The lifecycle commands address a mount solely through `app.json`'s
5
+ * `deployment` block, so losing that block (instance reinstall, hand edit,
6
+ * re-scaffold, working from another machine) turns a live mount into an orphan:
7
+ * deploy then dies on APP_MOUNT_CONFLICT while destroy dies on
8
+ * APP_DEPLOYMENT_NOT_FOUND, and nothing in the CLI can reach the mount again.
9
+ *
10
+ * This index mirrors `deployment` one level up, keyed by app name, so the CLI can
11
+ * recover the mountId on its own. It is a CACHE, never a source of truth:
12
+ * `app.json` always wins, and an entry is ignored unless it was written against
13
+ * the same endpoint + tenant as the active profile (one workspace can be pointed
14
+ * at several instances, and a mountId from instance A must never be replayed
15
+ * against instance B).
16
+ */
17
+ export declare const LOCAL_INDEX_DIRNAME = ".pxbaas";
18
+ export declare const LOCAL_INDEX_FILENAME = "apps.json";
19
+ export interface LocalAppIndexEntry {
20
+ mountId: string;
21
+ generation: number;
22
+ /** Endpoint the entry was recorded against; guards cross-instance reuse. */
23
+ endpoint: string;
24
+ /** Tenant the entry was recorded against; null for single-tenant profiles. */
25
+ tenantId: string | null;
26
+ updatedAt: string;
27
+ }
28
+ export type LocalAppIndex = Record<string, LocalAppIndexEntry>;
29
+ /** The instance the active profile points at. */
30
+ export interface ProfileScope {
31
+ endpoint: string;
32
+ tenantId?: string | null;
33
+ }
34
+ export interface LocalMountLookup {
35
+ /** The usable entry, or null when there is none / it was ignored. */
36
+ entry: LocalAppIndexEntry | null;
37
+ /** Human-readable reason when an entry existed but was NOT used. */
38
+ ignored: string | null;
39
+ path: string;
40
+ }
41
+ /** Absolute-ish path of the index for an apps directory (`--dir`). */
42
+ export declare function localIndexPath(appsDir: string): string;
43
+ /**
44
+ * Read the raw index. A missing, unreadable or corrupt file degrades to an empty
45
+ * index: the cache must never be able to break a command that would otherwise
46
+ * work off `app.json`.
47
+ */
48
+ export declare function readLocalIndex(appsDir: string): Promise<LocalAppIndex>;
49
+ /**
50
+ * Look up an app's recorded mount, rejecting entries that belong to a different
51
+ * instance or tenant. `ignored` carries the reason so callers can tell the user
52
+ * why a stale entry was skipped instead of silently dropping it.
53
+ */
54
+ export declare function lookupLocalMount(appsDir: string, name: string, scope: ProfileScope): Promise<LocalMountLookup>;
55
+ /**
56
+ * Record a mount after a successful deploy/publish/rollback/adopt. Best effort:
57
+ * returns an error message instead of throwing so a cache write can never fail
58
+ * the operation that already succeeded server-side.
59
+ */
60
+ export declare function recordLocalMount(appsDir: string, name: string, mount: {
61
+ mountId: string;
62
+ generation: number;
63
+ }, scope: ProfileScope): Promise<string | null>;
64
+ /**
65
+ * Drop an app's entry after a successful destroy. `expectedMountId` makes the
66
+ * delete conditional so destroying one mount never erases the record of a
67
+ * different one (e.g. `destroy --mount-id` overriding stale local state).
68
+ */
69
+ export declare function forgetLocalMount(appsDir: string, name: string, expectedMountId?: string): Promise<string | null>;
70
+ //# sourceMappingURL=local-index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-index.d.ts","sourceRoot":"","sources":["../../src/app/local-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAC7C,eAAO,MAAM,oBAAoB,cAAc,CAAC;AAEhD,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;AAE/D,iDAAiD;AACjD,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,qEAAqE;IACrE,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACjC,oEAAoE;IACpE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,sEAAsE;AACtE,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEtD;AAUD;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA2B5E;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,gBAAgB,CAAC,CA2B3B;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,EAC9C,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiBxB;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,eAAe,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAcxB"}
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Workspace-level side index of deployed App mounts (`.pxbaas/apps.json`).
3
+ *
4
+ * The lifecycle commands address a mount solely through `app.json`'s
5
+ * `deployment` block, so losing that block (instance reinstall, hand edit,
6
+ * re-scaffold, working from another machine) turns a live mount into an orphan:
7
+ * deploy then dies on APP_MOUNT_CONFLICT while destroy dies on
8
+ * APP_DEPLOYMENT_NOT_FOUND, and nothing in the CLI can reach the mount again.
9
+ *
10
+ * This index mirrors `deployment` one level up, keyed by app name, so the CLI can
11
+ * recover the mountId on its own. It is a CACHE, never a source of truth:
12
+ * `app.json` always wins, and an entry is ignored unless it was written against
13
+ * the same endpoint + tenant as the active profile (one workspace can be pointed
14
+ * at several instances, and a mountId from instance A must never be replayed
15
+ * against instance B).
16
+ */
17
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
18
+ import { dirname, join } from "node:path";
19
+ export const LOCAL_INDEX_DIRNAME = ".pxbaas";
20
+ export const LOCAL_INDEX_FILENAME = "apps.json";
21
+ /** Absolute-ish path of the index for an apps directory (`--dir`). */
22
+ export function localIndexPath(appsDir) {
23
+ return join(appsDir, LOCAL_INDEX_DIRNAME, LOCAL_INDEX_FILENAME);
24
+ }
25
+ function normalizeEndpoint(endpoint) {
26
+ return endpoint.replace(/\/+$/, "");
27
+ }
28
+ function normalizeTenant(tenantId) {
29
+ return tenantId == null || tenantId === "" ? null : tenantId;
30
+ }
31
+ /**
32
+ * Read the raw index. A missing, unreadable or corrupt file degrades to an empty
33
+ * index: the cache must never be able to break a command that would otherwise
34
+ * work off `app.json`.
35
+ */
36
+ export async function readLocalIndex(appsDir) {
37
+ try {
38
+ const parsed = JSON.parse(await readFile(localIndexPath(appsDir), "utf8"));
39
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
40
+ return {};
41
+ const index = {};
42
+ for (const [name, value] of Object.entries(parsed)) {
43
+ const entry = value;
44
+ if (typeof entry?.mountId !== "string" || !entry.mountId)
45
+ continue;
46
+ index[name] = {
47
+ mountId: entry.mountId,
48
+ generation: typeof entry.generation === "number" && Number.isFinite(entry.generation)
49
+ ? entry.generation
50
+ : 0,
51
+ endpoint: typeof entry.endpoint === "string" ? entry.endpoint : "",
52
+ tenantId: normalizeTenant(entry.tenantId),
53
+ updatedAt: typeof entry.updatedAt === "string" ? entry.updatedAt : "",
54
+ };
55
+ }
56
+ return index;
57
+ }
58
+ catch {
59
+ return {};
60
+ }
61
+ }
62
+ /**
63
+ * Look up an app's recorded mount, rejecting entries that belong to a different
64
+ * instance or tenant. `ignored` carries the reason so callers can tell the user
65
+ * why a stale entry was skipped instead of silently dropping it.
66
+ */
67
+ export async function lookupLocalMount(appsDir, name, scope) {
68
+ const path = localIndexPath(appsDir);
69
+ const entry = (await readLocalIndex(appsDir))[name];
70
+ if (!entry)
71
+ return { entry: null, ignored: null, path };
72
+ const wantEndpoint = normalizeEndpoint(scope.endpoint);
73
+ if (normalizeEndpoint(entry.endpoint) !== wantEndpoint) {
74
+ return {
75
+ entry: null,
76
+ ignored: `Ignoring the ${path} entry for '${name}': it was recorded against endpoint ` +
77
+ `${entry.endpoint || "(unknown)"} but the active profile points at ${wantEndpoint}. ` +
78
+ `A mountId is only valid on the instance that issued it.`,
79
+ path,
80
+ };
81
+ }
82
+ const wantTenant = normalizeTenant(scope.tenantId);
83
+ if (entry.tenantId !== wantTenant) {
84
+ return {
85
+ entry: null,
86
+ ignored: `Ignoring the ${path} entry for '${name}': it was recorded for tenant ` +
87
+ `${entry.tenantId ?? "(none)"} but the active profile is ${wantTenant ?? "(single-tenant)"}.`,
88
+ path,
89
+ };
90
+ }
91
+ return { entry, ignored: null, path };
92
+ }
93
+ /**
94
+ * Record a mount after a successful deploy/publish/rollback/adopt. Best effort:
95
+ * returns an error message instead of throwing so a cache write can never fail
96
+ * the operation that already succeeded server-side.
97
+ */
98
+ export async function recordLocalMount(appsDir, name, mount, scope) {
99
+ const path = localIndexPath(appsDir);
100
+ try {
101
+ const index = await readLocalIndex(appsDir);
102
+ index[name] = {
103
+ mountId: mount.mountId,
104
+ generation: mount.generation,
105
+ endpoint: normalizeEndpoint(scope.endpoint),
106
+ tenantId: normalizeTenant(scope.tenantId),
107
+ updatedAt: new Date().toISOString(),
108
+ };
109
+ await mkdir(dirname(path), { recursive: true });
110
+ await writeFile(path, JSON.stringify(index, null, 2) + "\n", "utf8");
111
+ return null;
112
+ }
113
+ catch (e) {
114
+ return `Could not update the local mount index ${path}: ${e instanceof Error ? e.message : String(e)}`;
115
+ }
116
+ }
117
+ /**
118
+ * Drop an app's entry after a successful destroy. `expectedMountId` makes the
119
+ * delete conditional so destroying one mount never erases the record of a
120
+ * different one (e.g. `destroy --mount-id` overriding stale local state).
121
+ */
122
+ export async function forgetLocalMount(appsDir, name, expectedMountId) {
123
+ const path = localIndexPath(appsDir);
124
+ try {
125
+ const index = await readLocalIndex(appsDir);
126
+ const entry = index[name];
127
+ if (!entry)
128
+ return null;
129
+ if (expectedMountId && entry.mountId !== expectedMountId)
130
+ return null;
131
+ delete index[name];
132
+ await mkdir(dirname(path), { recursive: true });
133
+ await writeFile(path, JSON.stringify(index, null, 2) + "\n", "utf8");
134
+ return null;
135
+ }
136
+ catch (e) {
137
+ return `Could not update the local mount index ${path}: ${e instanceof Error ? e.message : String(e)}`;
138
+ }
139
+ }
140
+ //# sourceMappingURL=local-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-index.js","sourceRoot":"","sources":["../../src/app/local-index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Preflight guardrails for `pxbaas app deploy`.
3
+ *
4
+ * These checks encode the edge-function sandbox constraints (see doc 02 and the
5
+ * sandbox webapp evaluation): 5-function quota, no root-absolute asset refs
6
+ * (they escape the /functions/v1/ prefix onto Studio), and cross-machine sharing
7
+ * requires a public endpoint (sandbox URLs are internal).
8
+ */
9
+ export type QuotaLimitSource = "platform" | "flag" | "fallback";
10
+ /**
11
+ * Client-side fallback limit, used ONLY when the instance did not report its
12
+ * real quota (capabilities.maxFunctions) and no --quota-limit was given. It never
13
+ * hard-blocks on its own; the server-side quota trigger is authoritative (弯路5).
14
+ */
15
+ export declare const FALLBACK_QUOTA_LIMIT = 5;
16
+ export interface QuotaCheck {
17
+ used: number;
18
+ limit: number;
19
+ ok: boolean;
20
+ near: boolean;
21
+ source: QuotaLimitSource;
22
+ /** True when the limit is authoritative (platform/flag) so a violation blocks. */
23
+ authoritative: boolean;
24
+ }
25
+ export interface PreflightInput {
26
+ source: string;
27
+ endpoint: string;
28
+ functionsCount: number;
29
+ quotaLimit?: number;
30
+ quotaLimitSource?: QuotaLimitSource;
31
+ verifyJwt: boolean;
32
+ entrypointExists: boolean;
33
+ sourceBytes?: number;
34
+ }
35
+ export interface PreflightResult {
36
+ quota: QuotaCheck;
37
+ rootAbsoluteRefs: string[];
38
+ publicReachable: boolean;
39
+ warnings: string[];
40
+ blockers: string[];
41
+ }
42
+ /** Detect root-absolute references that would escape the /functions/v1/ prefix. */
43
+ export declare function scanRootAbsoluteRefs(source: string): string[];
44
+ /** True when the endpoint host is publicly reachable (not loopback / RFC1918). */
45
+ export declare function isPublicEndpoint(endpoint: string): boolean;
46
+ export declare function checkQuota(used: number, limit?: number, source?: QuotaLimitSource): QuotaCheck;
47
+ /**
48
+ * Split a quota check into the blocker/warning text the deploy gates report.
49
+ *
50
+ * Exported separately from runPreflight because the schemaVersion 2 webapp path
51
+ * has no single-file `source`/entrypoint to feed runPreflight — it still needs
52
+ * the identical quota wording, so both paths share this one implementation
53
+ * instead of the webapp branch re-deriving (or skipping) the check.
54
+ */
55
+ export declare function quotaFindings(quota: QuotaCheck): {
56
+ warnings: string[];
57
+ blockers: string[];
58
+ };
59
+ /** Run all preflight checks and collect warnings/blockers. */
60
+ export declare function runPreflight(input: PreflightInput): PreflightResult;
61
+ //# sourceMappingURL=preflight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../../src/app/preflight.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,MAAM,GAAG,UAAU,CAAC;AAEhE;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,gBAAgB,CAAC;IACzB,kFAAkF;IAClF,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,SAAS,EAAE,OAAO,CAAC;IACnB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,UAAU,CAAC;IAClB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,eAAe,EAAE,OAAO,CAAC;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,mFAAmF;AACnF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAY7D;AAED,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAY1D;AAED,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,KAAK,SAAuB,EAC5B,MAAM,GAAE,gBAA6B,GACpC,UAAU,CASZ;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG;IAChD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CA0BA;AAED,8DAA8D;AAC9D,wBAAgB,YAAY,CAAC,KAAK,EAAE,cAAc,GAAG,eAAe,CA4CnE"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Preflight guardrails for `pxbaas app deploy`.
3
+ *
4
+ * These checks encode the edge-function sandbox constraints (see doc 02 and the
5
+ * sandbox webapp evaluation): 5-function quota, no root-absolute asset refs
6
+ * (they escape the /functions/v1/ prefix onto Studio), and cross-machine sharing
7
+ * requires a public endpoint (sandbox URLs are internal).
8
+ */
9
+ /**
10
+ * Client-side fallback limit, used ONLY when the instance did not report its
11
+ * real quota (capabilities.maxFunctions) and no --quota-limit was given. It never
12
+ * hard-blocks on its own; the server-side quota trigger is authoritative (弯路5).
13
+ */
14
+ export const FALLBACK_QUOTA_LIMIT = 5;
15
+ /** Detect root-absolute references that would escape the /functions/v1/ prefix. */
16
+ export function scanRootAbsoluteRefs(source) {
17
+ const patterns = [
18
+ /(href|src|action)\s*=\s*["']\/(?!\/)[^"']*["']/gi,
19
+ /fetch\(\s*["'`]\/(?!\/)[^"'`]*["'`]/gi,
20
+ /url\(\s*\/(?!\/)[^)]*\)/gi,
21
+ ];
22
+ const hits = [];
23
+ for (const re of patterns) {
24
+ const m = source.match(re);
25
+ if (m)
26
+ hits.push(...m);
27
+ }
28
+ return hits;
29
+ }
30
+ /** True when the endpoint host is publicly reachable (not loopback / RFC1918). */
31
+ export function isPublicEndpoint(endpoint) {
32
+ const host = endpoint
33
+ .replace(/^https?:\/\//, "")
34
+ .split("/")[0]
35
+ .split(":")[0]
36
+ .toLowerCase();
37
+ if (!host)
38
+ return false;
39
+ if (["localhost", "127.0.0.1", "::1", "0.0.0.0"].includes(host))
40
+ return false;
41
+ if (/^10\./.test(host))
42
+ return false;
43
+ if (/^192\.168\./.test(host))
44
+ return false;
45
+ if (/^172\.(1[6-9]|2\d|3[01])\./.test(host))
46
+ return false;
47
+ return true;
48
+ }
49
+ export function checkQuota(used, limit = FALLBACK_QUOTA_LIMIT, source = "fallback") {
50
+ return {
51
+ used,
52
+ limit,
53
+ ok: used < limit,
54
+ near: used === limit - 1,
55
+ source,
56
+ authoritative: source !== "fallback",
57
+ };
58
+ }
59
+ /**
60
+ * Split a quota check into the blocker/warning text the deploy gates report.
61
+ *
62
+ * Exported separately from runPreflight because the schemaVersion 2 webapp path
63
+ * has no single-file `source`/entrypoint to feed runPreflight — it still needs
64
+ * the identical quota wording, so both paths share this one implementation
65
+ * instead of the webapp branch re-deriving (or skipping) the check.
66
+ */
67
+ export function quotaFindings(quota) {
68
+ const warnings = [];
69
+ const blockers = [];
70
+ if (quota.ok) {
71
+ if (quota.near) {
72
+ warnings.push(`Approaching the Edge Function quota (${quota.used}/${quota.limit}, source: ${quota.source}).`);
73
+ }
74
+ return { warnings, blockers };
75
+ }
76
+ const detail = `${quota.used}/${quota.limit}, source: ${quota.source}`;
77
+ if (quota.authoritative) {
78
+ blockers.push(`Edge Function quota reached (${detail}). Remove an existing function (\`pxbaas app remove\` / \`pxbaas functions delete\`) or request a higher quota.`);
79
+ }
80
+ else {
81
+ // No authoritative limit could be read (the instance did not report its
82
+ // quota and no --quota-limit was given). Do NOT hard-block on a client-side
83
+ // guess — the server-side quota trigger is authoritative. Warn instead. (弯路5)
84
+ warnings.push(`Edge Function count is at the client fallback limit (${detail}); the platform limit could not be read. Proceeding — the server enforces the real quota. Pass --quota-limit <n> to make this check authoritative.`);
85
+ }
86
+ return { warnings, blockers };
87
+ }
88
+ /** Run all preflight checks and collect warnings/blockers. */
89
+ export function runPreflight(input) {
90
+ const quotaSource = input.quotaLimitSource ?? (input.quotaLimit != null ? "flag" : "fallback");
91
+ const quota = checkQuota(input.functionsCount, input.quotaLimit ?? FALLBACK_QUOTA_LIMIT, quotaSource);
92
+ const rootAbsoluteRefs = scanRootAbsoluteRefs(input.source);
93
+ const publicReachable = isPublicEndpoint(input.endpoint);
94
+ const { warnings, blockers } = quotaFindings(quota);
95
+ if (!input.entrypointExists) {
96
+ blockers.push("Entrypoint file not found. Run `pxbaas app new <name>` first.");
97
+ }
98
+ if (rootAbsoluteRefs.length > 0) {
99
+ // Root-absolute refs are a hard spec violation and are reported as a BLOCKER
100
+ // by spec-lint (pxbaas-app-spec). We still surface the raw list here for data,
101
+ // but do not double-report it as a preflight warning.
102
+ }
103
+ if (!publicReachable) {
104
+ warnings.push(`Endpoint ${input.endpoint} is not publicly reachable (loopback/private). The share URL will NOT work cross-machine.`);
105
+ }
106
+ if (!input.verifyJwt) {
107
+ warnings.push("verify_jwt=false: the share URL is public and the slug is enumerable. Do not co-locate secrets or sensitive logic in this function.");
108
+ }
109
+ const bytes = input.sourceBytes ?? Buffer.byteLength(input.source, "utf8");
110
+ if (bytes > 1_000_000) {
111
+ warnings.push(`Single-file bundle is large (${Math.round(bytes / 1024)}KB). Worker memory is limited (~150MB); consider hosting static assets via Storage/CDN instead.`);
112
+ }
113
+ return { quota, rootAbsoluteRefs, publicReachable, warnings, blockers };
114
+ }
115
+ //# sourceMappingURL=preflight.js.map