@polardbx/pxbaas 0.1.0-beta.2 → 0.1.0-beta.4
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 +116 -13
- package/dist/app/local-index.d.ts +70 -0
- package/dist/app/local-index.d.ts.map +1 -0
- package/dist/app/local-index.js +140 -0
- package/dist/app/local-index.js.map +1 -0
- package/dist/app/preflight.d.ts +24 -1
- package/dist/app/preflight.d.ts.map +1 -1
- package/dist/app/preflight.js +47 -11
- package/dist/app/preflight.js.map +1 -1
- package/dist/app/spec-lint.d.ts.map +1 -1
- package/dist/app/spec-lint.js +16 -0
- package/dist/app/spec-lint.js.map +1 -1
- package/dist/cli/root.d.ts.map +1 -1
- package/dist/cli/root.js +3 -0
- package/dist/cli/root.js.map +1 -1
- package/dist/commands/app/index.d.ts.map +1 -1
- package/dist/commands/app/index.js +428 -44
- package/dist/commands/app/index.js.map +1 -1
- package/dist/commands/completion/index.d.ts.map +1 -1
- package/dist/commands/completion/index.js +5 -1
- package/dist/commands/completion/index.js.map +1 -1
- package/dist/commands/data/index.d.ts +52 -0
- package/dist/commands/data/index.d.ts.map +1 -1
- package/dist/commands/data/index.js +201 -39
- package/dist/commands/data/index.js.map +1 -1
- package/dist/commands/db/index.d.ts +14 -0
- package/dist/commands/db/index.d.ts.map +1 -1
- package/dist/commands/db/index.js +151 -40
- package/dist/commands/db/index.js.map +1 -1
- package/dist/commands/functions/new.d.ts +4 -0
- package/dist/commands/functions/new.d.ts.map +1 -1
- package/dist/commands/functions/new.js +24 -9
- package/dist/commands/functions/new.js.map +1 -1
- package/dist/commands/gen/index.d.ts.map +1 -1
- package/dist/commands/gen/index.js +13 -10
- package/dist/commands/gen/index.js.map +1 -1
- package/dist/commands/login/index.d.ts.map +1 -1
- package/dist/commands/login/index.js +15 -30
- package/dist/commands/login/index.js.map +1 -1
- package/dist/commands/profile/import-env.d.ts +7 -2
- package/dist/commands/profile/import-env.d.ts.map +1 -1
- package/dist/commands/profile/import-env.js +141 -16
- package/dist/commands/profile/import-env.js.map +1 -1
- package/dist/commands/profile/index.d.ts.map +1 -1
- package/dist/commands/profile/index.js +13 -1
- package/dist/commands/profile/index.js.map +1 -1
- package/dist/commands/storage/index.d.ts.map +1 -1
- package/dist/commands/storage/index.js +209 -7
- package/dist/commands/storage/index.js.map +1 -1
- package/dist/commands/tenant/index.d.ts +19 -0
- package/dist/commands/tenant/index.d.ts.map +1 -0
- package/dist/commands/tenant/index.js +662 -0
- package/dist/commands/tenant/index.js.map +1 -0
- package/dist/commands/whoami/index.js +19 -0
- package/dist/commands/whoami/index.js.map +1 -1
- package/dist/credentials/credential-store.d.ts +30 -1
- package/dist/credentials/credential-store.d.ts.map +1 -1
- package/dist/credentials/credential-store.js +114 -12
- package/dist/credentials/credential-store.js.map +1 -1
- package/dist/credentials/env-file.d.ts +48 -7
- package/dist/credentials/env-file.d.ts.map +1 -1
- package/dist/credentials/env-file.js +91 -16
- package/dist/credentials/env-file.js.map +1 -1
- package/dist/credentials/jwt-introspect.d.ts +7 -0
- package/dist/credentials/jwt-introspect.d.ts.map +1 -1
- package/dist/credentials/jwt-introspect.js.map +1 -1
- package/dist/credentials/masker.d.ts +3 -0
- package/dist/credentials/masker.d.ts.map +1 -1
- package/dist/credentials/masker.js +3 -0
- package/dist/credentials/masker.js.map +1 -1
- package/dist/credentials/profile.d.ts +12 -0
- package/dist/credentials/profile.d.ts.map +1 -1
- package/dist/credentials/profile.js +23 -0
- package/dist/credentials/profile.js.map +1 -1
- package/dist/credentials/tty-guard.d.ts +10 -0
- package/dist/credentials/tty-guard.d.ts.map +1 -1
- package/dist/credentials/tty-guard.js +32 -0
- package/dist/credentials/tty-guard.js.map +1 -1
- package/dist/db/introspect.d.ts +54 -0
- package/dist/db/introspect.d.ts.map +1 -0
- package/dist/db/introspect.js +140 -0
- package/dist/db/introspect.js.map +1 -0
- package/dist/main.js +21 -2
- package/dist/main.js.map +1 -1
- package/dist/tenant/bundle.d.ts +66 -0
- package/dist/tenant/bundle.d.ts.map +1 -0
- package/dist/tenant/bundle.js +132 -0
- package/dist/tenant/bundle.js.map +1 -0
- package/dist/tenant/guide.d.ts +50 -0
- package/dist/tenant/guide.d.ts.map +1 -0
- package/dist/tenant/guide.js +152 -0
- package/dist/tenant/guide.js.map +1 -0
- package/dist/tenant/probe.d.ts +65 -0
- package/dist/tenant/probe.d.ts.map +1 -0
- package/dist/tenant/probe.js +159 -0
- package/dist/tenant/probe.js.map +1 -0
- package/dist/tenant/verify.d.ts +37 -0
- package/dist/tenant/verify.d.ts.map +1 -0
- package/dist/tenant/verify.js +132 -0
- package/dist/tenant/verify.js.map +1 -0
- package/dist/version.d.ts +29 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +65 -0
- package/dist/version.js.map +1 -0
- package/package.json +2 -2
- package/skills/pxbaas-app-spec/references/runtime-contract.md +2 -2
- package/skills/pxbaas-cli/SKILL.md +14 -5
- package/skills/pxbaas-cli/references/auth-and-credentials.md +19 -3
- package/skills/pxbaas-cli/references/data-postgrest.md +19 -3
- package/skills/pxbaas-cli/references/db-and-meta.md +5 -0
- package/skills/pxbaas-cli/references/safety-and-two-phase.md +42 -13
- package/skills/pxbaas-cli/references/tenant-admin.md +122 -0
- package/skills/pxbaas-cli/references/troubleshooting.md +4 -1
- package/templates/webapp/react-vite-hono/backend/index.ts +21 -4
- package/templates/webapp/ssr-hono/backend/index.ts +50 -31
package/README.md
CHANGED
|
@@ -39,7 +39,81 @@ pxbaas doctor # connectivity / configuration diagnostics
|
|
|
39
39
|
pxbaas status # instance reachability summary
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
Every command accepts `--json` for machine-readable output, and `--profile <name>` to select a profile per invocation (`pxbaas profile
|
|
42
|
+
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).
|
|
43
|
+
|
|
44
|
+
### Non-interactive credential setup
|
|
45
|
+
|
|
46
|
+
Agents and CI have two TTY-free paths; keys never pass through argv or shell history:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# 1) stdin mode: pipe exactly two lines - anon key, then service_role key.
|
|
50
|
+
# --anon-key-stdin and --service-key-stdin must always be given together.
|
|
51
|
+
pxbaas login --endpoint https://baas.example.com \
|
|
52
|
+
[--tenant-id <tenant>] \
|
|
53
|
+
--anon-key-stdin --service-key-stdin <<'EOF'
|
|
54
|
+
<anon-key>
|
|
55
|
+
<service-role-key>
|
|
56
|
+
EOF
|
|
57
|
+
|
|
58
|
+
# 2) import from a Supabase-convention .env file (single- or multi-tenant)
|
|
59
|
+
pxbaas profile import ./instance.env --name prod --json
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`profile import` resolves variables in first-match order and validates JWT expiry + tenant consistency before storing the profile:
|
|
63
|
+
|
|
64
|
+
| Field | Accepted env variable names (first match wins) |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| Endpoint | `SUPABASE_URL`, `SUPABASE_PUBLIC_URL`, `SUPABASE_INTRANET_URL` (`--intranet` prefers the intranet URL) |
|
|
67
|
+
| anon key | `SUPABASE_ANON_KEY`, `ANON_KEY`, `GLOBAL_ANON_KEY` |
|
|
68
|
+
| service_role key | `SUPABASE_SERVICE_ROLE_KEY`, `SERVICE_ROLE_KEY`, `GLOBAL_SERVICE_ROLE_KEY` |
|
|
69
|
+
|
|
70
|
+
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.
|
|
71
|
+
|
|
72
|
+
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.
|
|
73
|
+
|
|
74
|
+
`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.
|
|
75
|
+
|
|
76
|
+
## Multi-tenant
|
|
77
|
+
|
|
78
|
+
On a `MULTI_TENANT_MODE=true` instance, authenticate with that tenant's own keys:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pxbaas login --tenant-id <tenant> # paste the tenant's per-tenant anon + service_role keys
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
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.
|
|
85
|
+
|
|
86
|
+
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:
|
|
87
|
+
|
|
88
|
+
- `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.
|
|
89
|
+
- `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`.
|
|
90
|
+
- `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.
|
|
91
|
+
- 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.
|
|
92
|
+
|
|
93
|
+
## Tenant administration (instance operators)
|
|
94
|
+
|
|
95
|
+
Ordinary tenants never use this. It requires the instance dashboard credentials (HTTP Basic), which live in a
|
|
96
|
+
separate keyring account from your tenant API keys and are refused in argv.
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
pxbaas tenant login --endpoint https://<gateway> # masked prompt, or --user-stdin --password-stdin
|
|
100
|
+
pxbaas tenant probe --json # admin / not-admin / single-tenant / metadata-unreadable / unreachable
|
|
101
|
+
pxbaas tenant ls --json # redacted: ids, status, plan, db, key-presence booleans
|
|
102
|
+
|
|
103
|
+
# hand an existing tenant's keys to its user
|
|
104
|
+
pxbaas tenant keys acme --as-profile # into this machine's keyring
|
|
105
|
+
pxbaas tenant keys acme --bundle ./acme.pxbaas.env # 0600 file to hand over
|
|
106
|
+
|
|
107
|
+
# only when a tenant really must be created
|
|
108
|
+
pxbaas tenant create acme --dry-run --json
|
|
109
|
+
pxbaas tenant create acme --as-profile --bundle ./acme.pxbaas.env
|
|
110
|
+
pxbaas tenant repair-keys # preview; --confirm to run
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
- `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`.
|
|
114
|
+
- 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.
|
|
115
|
+
- 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.
|
|
116
|
+
- 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.
|
|
43
117
|
|
|
44
118
|
## Command Reference
|
|
45
119
|
|
|
@@ -47,14 +121,15 @@ Every command accepts `--json` for machine-readable output, and `--profile <name
|
|
|
47
121
|
| --- | --- |
|
|
48
122
|
| Onboarding & diagnostics | `login`, `logout`, `whoami`, `status`, `doctor`, `profile` |
|
|
49
123
|
| Disposable instances (Zero) | `provision` (alias `instance claim`), `instance list / current / credentials / release` |
|
|
50
|
-
| Database | `db query <sql>`, `db tables`, `db schema <table>`, `db push <file
|
|
124
|
+
| Database | `db query <sql>`, `db tables`, `db schema <table>`, `db push <file> [--rls <rls.json>]`, `db create-table`, `db add-column` |
|
|
51
125
|
| Data (REST) | `data select / insert / update / delete <table>`, `rest <method> <path>` |
|
|
52
126
|
| Edge Functions | `functions new / deploy / list / delete` |
|
|
53
|
-
| Webapp demos | `app new / deploy / verify / url / list / remove / destroy / templates / db-init / capabilities`, `app share ...` |
|
|
127
|
+
| Webapp demos | `app new / deploy / verify / url / list / remove / destroy / templates / db-init / capabilities / publish / rollback / unpublish`, `app share ...` |
|
|
54
128
|
| Storage | `storage ls / cp / mv / rm` |
|
|
55
|
-
| Auth admin | `auth users list / create / invite` |
|
|
129
|
+
| Auth admin | `auth users list / get / create / delete / invite` |
|
|
56
130
|
| Codegen | `gen types` (TypeScript types from the live schema) |
|
|
57
|
-
|
|
|
131
|
+
| Tenant administration (operator) | `tenant login / logout / probe / ls / create / keys / repair-keys / handoffs` |
|
|
132
|
+
| Tooling | `skill install / list / remove`, `completion <shell>` (not yet implemented) |
|
|
58
133
|
|
|
59
134
|
### Provisioning (PolarDB-X Zero)
|
|
60
135
|
|
|
@@ -69,15 +144,43 @@ Keys come from Zero over TLS and are stored in the keyring; output shows only fi
|
|
|
69
144
|
### Webapp demos (edge-function sandbox)
|
|
70
145
|
|
|
71
146
|
```bash
|
|
72
|
-
pxbaas app new shop --template
|
|
73
|
-
pxbaas app
|
|
74
|
-
pxbaas app
|
|
75
|
-
pxbaas app
|
|
76
|
-
pxbaas app
|
|
77
|
-
pxbaas app
|
|
147
|
+
pxbaas app new shop --template ssr-shop --json # scaffold a schemaVersion-2 webapp (ships db/)
|
|
148
|
+
pxbaas app db-init shop --dry-run --json # preview the db/schema.sql + db/rls.json plan
|
|
149
|
+
pxbaas app db-init shop --yes --json # create tables + register RLS + seed (authorized)
|
|
150
|
+
pxbaas app deploy shop --dry-run --json # preflight (quota / root-abs refs / public reachability)
|
|
151
|
+
pxbaas app deploy shop --json # upload + publish -> mount URL
|
|
152
|
+
pxbaas app verify shop --json # cold-start retry + content-type + API ping
|
|
153
|
+
pxbaas app url shop --json # webapp: {appEndpoint}/apps/{mount}/; legacy spa/api/mpa: {endpoint}/functions/v1/{slug}
|
|
154
|
+
pxbaas app publish shop --json # publish a new candidate (generation CAS)
|
|
155
|
+
pxbaas app rollback shop --json # roll back one generation
|
|
156
|
+
pxbaas app unpublish shop --json # take the mount offline (reversible)
|
|
157
|
+
pxbaas app destroy shop --confirm --json # delete edge funcs + mount record (DB tables kept)
|
|
158
|
+
pxbaas app destroy shop --confirm --purge-db --json # also drop tables + RLS registry rows (destructive)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`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`).
|
|
162
|
+
|
|
163
|
+
### Secure sharing (`app share`)
|
|
164
|
+
|
|
165
|
+
A default deploy (`verify_jwt=false`) is fully public. For time-boxed, revocable, audience-limited sharing, use secure share links:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
pxbaas app share create shop --json # default 86400s lifetime; prints the one-time entry URL
|
|
169
|
+
pxbaas app share create shop --expires-in 3600 --json # relative expiry (1 hour)
|
|
170
|
+
pxbaas app share create shop --expires-at 2026-12-31T00:00:00Z --json # absolute RFC3339 expiry (must be in the future)
|
|
171
|
+
pxbaas app share create shop --max-uses 5 --json # cap ticket redemptions (positive int, max 4294967295)
|
|
172
|
+
pxbaas app share list shop --json # never exposes tickets or sessions
|
|
173
|
+
pxbaas app share verify shop <share-id> --json # status check without revealing the link
|
|
174
|
+
pxbaas app share revoke shop <share-id> --confirm --json # disable a link (two-phase)
|
|
78
175
|
```
|
|
79
176
|
|
|
80
|
-
|
|
177
|
+
How it works:
|
|
178
|
+
|
|
179
|
+
- `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.
|
|
180
|
+
- `--expires-in` and `--expires-at` are mutually exclusive; `--max-uses` is a positive integer.
|
|
181
|
+
- With platform credentials on a multi-tenant instance, pass `--tenant-id <tenant>` explicitly.
|
|
182
|
+
- ⚠️ Redeploying the same slug (`app deploy`) immediately revokes **all** of its active share links — re-create them afterwards.
|
|
183
|
+
- `list` / `verify` never return the ticket or cookie; only `create`'s one-time output contains the entry URL — hand it over right away.
|
|
81
184
|
|
|
82
185
|
### Agent Skill
|
|
83
186
|
|
|
@@ -95,7 +198,7 @@ Installs into supported Agent hosts (`~/.qoder`, `~/.claude`, `~/.cursor`, `~/.q
|
|
|
95
198
|
1. **Human-only login**: `pxbaas login` requires an interactive TTY; agents cannot execute it.
|
|
96
199
|
2. **Agent commands never accept key parameters**: post-login commands read credentials from the keyring.
|
|
97
200
|
3. **Output masking**: commands show only key fingerprints (sha256 prefix 8 + suffix 4), never plaintext.
|
|
98
|
-
4. **Two-phase operations**:
|
|
201
|
+
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.
|
|
99
202
|
5. **Tenant isolation**: commands validate that the key's tenant matches the profile's tenant before executing.
|
|
100
203
|
|
|
101
204
|
## Documentation
|
|
@@ -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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"}
|
package/dist/app/preflight.d.ts
CHANGED
|
@@ -6,17 +6,28 @@
|
|
|
6
6
|
* (they escape the /functions/v1/ prefix onto Studio), and cross-machine sharing
|
|
7
7
|
* requires a public endpoint (sandbox URLs are internal).
|
|
8
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;
|
|
9
16
|
export interface QuotaCheck {
|
|
10
17
|
used: number;
|
|
11
18
|
limit: number;
|
|
12
19
|
ok: boolean;
|
|
13
20
|
near: boolean;
|
|
21
|
+
source: QuotaLimitSource;
|
|
22
|
+
/** True when the limit is authoritative (platform/flag) so a violation blocks. */
|
|
23
|
+
authoritative: boolean;
|
|
14
24
|
}
|
|
15
25
|
export interface PreflightInput {
|
|
16
26
|
source: string;
|
|
17
27
|
endpoint: string;
|
|
18
28
|
functionsCount: number;
|
|
19
29
|
quotaLimit?: number;
|
|
30
|
+
quotaLimitSource?: QuotaLimitSource;
|
|
20
31
|
verifyJwt: boolean;
|
|
21
32
|
entrypointExists: boolean;
|
|
22
33
|
sourceBytes?: number;
|
|
@@ -32,7 +43,19 @@ export interface PreflightResult {
|
|
|
32
43
|
export declare function scanRootAbsoluteRefs(source: string): string[];
|
|
33
44
|
/** True when the endpoint host is publicly reachable (not loopback / RFC1918). */
|
|
34
45
|
export declare function isPublicEndpoint(endpoint: string): boolean;
|
|
35
|
-
export declare function checkQuota(used: number, limit?: number): QuotaCheck;
|
|
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
|
+
};
|
|
36
59
|
/** Run all preflight checks and collect warnings/blockers. */
|
|
37
60
|
export declare function runPreflight(input: PreflightInput): PreflightResult;
|
|
38
61
|
//# sourceMappingURL=preflight.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../../src/app/preflight.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,EAAE,OAAO,CAAC;
|
|
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"}
|
package/dist/app/preflight.js
CHANGED
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
* (they escape the /functions/v1/ prefix onto Studio), and cross-machine sharing
|
|
7
7
|
* requires a public endpoint (sandbox URLs are internal).
|
|
8
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;
|
|
9
15
|
/** Detect root-absolute references that would escape the /functions/v1/ prefix. */
|
|
10
16
|
export function scanRootAbsoluteRefs(source) {
|
|
11
17
|
const patterns = [
|
|
@@ -40,25 +46,55 @@ export function isPublicEndpoint(endpoint) {
|
|
|
40
46
|
return false;
|
|
41
47
|
return true;
|
|
42
48
|
}
|
|
43
|
-
export function checkQuota(used, limit =
|
|
44
|
-
return {
|
|
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 };
|
|
45
87
|
}
|
|
46
88
|
/** Run all preflight checks and collect warnings/blockers. */
|
|
47
89
|
export function runPreflight(input) {
|
|
48
|
-
const
|
|
90
|
+
const quotaSource = input.quotaLimitSource ?? (input.quotaLimit != null ? "flag" : "fallback");
|
|
91
|
+
const quota = checkQuota(input.functionsCount, input.quotaLimit ?? FALLBACK_QUOTA_LIMIT, quotaSource);
|
|
49
92
|
const rootAbsoluteRefs = scanRootAbsoluteRefs(input.source);
|
|
50
93
|
const publicReachable = isPublicEndpoint(input.endpoint);
|
|
51
|
-
const warnings =
|
|
52
|
-
const blockers = [];
|
|
94
|
+
const { warnings, blockers } = quotaFindings(quota);
|
|
53
95
|
if (!input.entrypointExists) {
|
|
54
96
|
blockers.push("Entrypoint file not found. Run `pxbaas app new <name>` first.");
|
|
55
97
|
}
|
|
56
|
-
if (!quota.ok) {
|
|
57
|
-
blockers.push(`Edge Function quota reached (${quota.used}/${quota.limit}). Remove an existing function (\`pxbaas app remove\` / \`pxbaas functions delete\`) or request a higher quota.`);
|
|
58
|
-
}
|
|
59
|
-
else if (quota.near) {
|
|
60
|
-
warnings.push(`Approaching the Edge Function quota (${quota.used}/${quota.limit}).`);
|
|
61
|
-
}
|
|
62
98
|
if (rootAbsoluteRefs.length > 0) {
|
|
63
99
|
// Root-absolute refs are a hard spec violation and are reported as a BLOCKER
|
|
64
100
|
// by spec-lint (pxbaas-app-spec). We still surface the raw list here for data,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preflight.js","sourceRoot":"","sources":["../../src/app/preflight.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"preflight.js","sourceRoot":"","sources":["../../src/app/preflight.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spec-lint.d.ts","sourceRoot":"","sources":["../../src/app/spec-lint.ts"],"names":[],"mappings":"AAAA,sFAAsF;AAGtF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;AAE7C,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;IACnC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,sBAAsB,CAAC,EAAE,MAAM,EAAE,CAAC;IAClC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;
|
|
1
|
+
{"version":3,"file":"spec-lint.d.ts","sourceRoot":"","sources":["../../src/app/spec-lint.ts"],"names":[],"mappings":"AAAA,sFAAsF;AAGtF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;AAE7C,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;IACnC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,sBAAsB,CAAC,EAAE,MAAM,EAAE,CAAC;IAClC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAubD,sFAAsF;AACtF,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,eAAoB,GAC5B,cAAc,CA6GhB"}
|
package/dist/app/spec-lint.js
CHANGED
|
@@ -216,6 +216,22 @@ function lintBackend(source, options, blockers, warnings) {
|
|
|
216
216
|
specRef: `${SPEC}/references/guardrails-pitfalls.md`,
|
|
217
217
|
});
|
|
218
218
|
}
|
|
219
|
+
// C2: with APP_WORKER_PATH_REWRITE off (the default) the proxy forwards every
|
|
220
|
+
// request as /<candidateSlug>/<logical>, so a Hono backend that registers fixed
|
|
221
|
+
// literal routes (app.get("/data")) without a wildcard dispatcher or slug
|
|
222
|
+
// normalization will 404 at runtime. Warn (never block) so authors normalize.
|
|
223
|
+
const honoBackend = /new\s+Hono\s*\(/.test(code);
|
|
224
|
+
const literalRoute = /app\.(get|post|put|delete|patch)\s*\(\s*["']\/[^"']*["']/.test(code);
|
|
225
|
+
const wildcardDispatch = /app\.(all|get|post)\s*\(\s*["']\*["']/.test(code);
|
|
226
|
+
const slugNormalized = /\bnorm\s*\(/.test(code) || /\/app_/.test(code);
|
|
227
|
+
if (honoBackend && literalRoute && !wildcardDispatch && !slugNormalized) {
|
|
228
|
+
warnings.push({
|
|
229
|
+
rule: "worker-path-normalize",
|
|
230
|
+
severity: "warning",
|
|
231
|
+
message: 'Backend registers fixed literal routes (e.g. app.get("/data")), but with APP_WORKER_PATH_REWRITE off (the default) the worker receives a /<candidateSlug>-prefixed path, so these routes 404. Normalize the leading slug (path.replace(/^\\/app_[^/]+/, "")) and dispatch on the logical path, or use a wildcard app.get("*").',
|
|
232
|
+
specRef: `${SPEC}/references/runtime-contract.md`,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
219
235
|
}
|
|
220
236
|
function lintWebappFrontend(options, blockers) {
|
|
221
237
|
const files = options.frontendFiles ?? [];
|