@supacloud/cli 0.29.0 → 0.30.0
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 +14 -0
- package/dist/index.js +97 -10
- package/package.json +1 -1
- package/skills/supacloud-cli/references/command-map.md +2 -1
package/README.md
CHANGED
|
@@ -142,6 +142,9 @@ supacloud-cli release postgrest_status --ref abc123
|
|
|
142
142
|
supacloud-cli release postgrest_restart --ref abc123
|
|
143
143
|
supacloud-cli release release_canary_fixture_stage_replay --ref abc123 \
|
|
144
144
|
--subject <central-subject-uuid> --request_id <stage-request-uuid>
|
|
145
|
+
supacloud-cli release release_canary_fixture_disable_replay --ref abc123 \
|
|
146
|
+
--fixture_id <fixture-uuid> --disable_request_id <disable-request-uuid> \
|
|
147
|
+
--issuer <issuer-url> --subject <central-subject-uuid>
|
|
145
148
|
```
|
|
146
149
|
|
|
147
150
|
Backup creation reports success only after the CLI verifies exactly one new
|
|
@@ -171,6 +174,17 @@ reads the selected project's authoritative endpoint projection and requires the
|
|
|
171
174
|
configured application origin to match its API origin or alias. Production
|
|
172
175
|
confirmation is mandatory.
|
|
173
176
|
|
|
177
|
+
`release_canary_fixture_disable_replay` performs one non-retried call to the
|
|
178
|
+
fixed `fa_release_canary_fixture_disable` PostgREST RPC using the selected
|
|
179
|
+
project's application service-role origin. Its receipt must contain exactly
|
|
180
|
+
`fixtureId`, `state="disabled"`, and a boolean `idempotent`; both the first
|
|
181
|
+
disable (`idempotent=false`) and same-request replay (`idempotent=true`) are
|
|
182
|
+
valid. The CLI then calls the existing fixed
|
|
183
|
+
`fa_release_canary_fixture_pending` RPC with only the exact issuer and subject
|
|
184
|
+
query parameters and accepts only its authoritative JSON boolean `false`
|
|
185
|
+
read-back. The fixture binding comes from the validated disable receipt before
|
|
186
|
+
reporting success. Management endpoint projection is checked before and after.
|
|
187
|
+
|
|
174
188
|
The legacy `.env` fallback is unclassified and therefore does not enable the
|
|
175
189
|
production confirmation gate. Production automation must select a `prod` or
|
|
176
190
|
`production` profile, or set `SUPACLOUD_ENV=production` together with a complete
|
package/dist/index.js
CHANGED
|
@@ -6491,7 +6491,7 @@ var ACTION_POLICY = {
|
|
|
6491
6491
|
mutations: { read: ["status"] },
|
|
6492
6492
|
release: {
|
|
6493
6493
|
read: ["logical_backup_list", "postgrest_status"],
|
|
6494
|
-
write: ["logical_backup_create", "logical_backup_restore", "postgrest_restart", "release_canary_fixture_stage_replay"]
|
|
6494
|
+
write: ["logical_backup_create", "logical_backup_restore", "postgrest_restart", "release_canary_fixture_stage_replay", "release_canary_fixture_disable_replay"]
|
|
6495
6495
|
},
|
|
6496
6496
|
secrets: { read: ["list"], write: ["upsert", "delete"] },
|
|
6497
6497
|
frontend: {
|
|
@@ -12844,6 +12844,10 @@ var RELEASE_READ_RESPONSE_TIMEOUT_MS = 5000;
|
|
|
12844
12844
|
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
12845
12845
|
var RELEASE_CANARY_TENANT_KEY = /^release-canary-[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
12846
12846
|
var RELEASE_CANARY_STAGE_RECEIPT_KEYS = new Set(["fixtureId", "tenantKey", "state", "idempotent"]);
|
|
12847
|
+
var RELEASE_CANARY_DISABLE_RECEIPT_KEYS = new Set(["fixtureId", "state", "idempotent"]);
|
|
12848
|
+
var RELEASE_CANARY_DISABLE_RPC_PATH = "/rest/v1/rpc/fa_release_canary_fixture_disable";
|
|
12849
|
+
var RELEASE_CANARY_PENDING_RPC_PATH = "/rest/v1/rpc/fa_release_canary_fixture_pending";
|
|
12850
|
+
var RELEASE_CANARY_CLAIM_MAX_LENGTH = 2048;
|
|
12847
12851
|
function isRecord3(value) {
|
|
12848
12852
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
12849
12853
|
}
|
|
@@ -12993,27 +12997,79 @@ function releaseCanaryStageReceipt(value) {
|
|
|
12993
12997
|
idempotent: true
|
|
12994
12998
|
};
|
|
12995
12999
|
}
|
|
13000
|
+
function releaseCanaryIssuer(value) {
|
|
13001
|
+
if (typeof value !== "string" || value.length === 0 || value.length > RELEASE_CANARY_CLAIM_MAX_LENGTH || value !== value.trim() || /[\u0000-\u001f\u007f]/u.test(value)) {
|
|
13002
|
+
throw new Error("'issuer' must be a bounded absolute HTTP(S) issuer");
|
|
13003
|
+
}
|
|
13004
|
+
let parsed;
|
|
13005
|
+
try {
|
|
13006
|
+
parsed = new URL(value);
|
|
13007
|
+
} catch {
|
|
13008
|
+
throw new Error("'issuer' must be a bounded absolute HTTP(S) issuer");
|
|
13009
|
+
}
|
|
13010
|
+
const loopbackHttp = parsed.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(parsed.hostname) && parsed.port.length > 0;
|
|
13011
|
+
if (parsed.protocol !== "https:" && !loopbackHttp || parsed.username || parsed.password || parsed.search || parsed.hash) {
|
|
13012
|
+
throw new Error("'issuer' must be a bounded absolute HTTP(S) issuer");
|
|
13013
|
+
}
|
|
13014
|
+
return value;
|
|
13015
|
+
}
|
|
13016
|
+
function releaseCanaryDisableInput(fixtureId, disableRequestId, issuer, subject) {
|
|
13017
|
+
if (typeof fixtureId !== "string" || !UUID.test(fixtureId)) {
|
|
13018
|
+
throw new Error("'fixture_id' must be a canonical UUID");
|
|
13019
|
+
}
|
|
13020
|
+
if (typeof disableRequestId !== "string" || !UUID.test(disableRequestId)) {
|
|
13021
|
+
throw new Error("'disable_request_id' must be a canonical UUID");
|
|
13022
|
+
}
|
|
13023
|
+
if (typeof subject !== "string" || !UUID.test(subject)) {
|
|
13024
|
+
throw new Error("'subject' must be a canonical UUID");
|
|
13025
|
+
}
|
|
13026
|
+
return {
|
|
13027
|
+
p_fixture_id: fixtureId,
|
|
13028
|
+
p_disable_request_id: disableRequestId,
|
|
13029
|
+
p_issuer: releaseCanaryIssuer(issuer),
|
|
13030
|
+
p_subject: subject
|
|
13031
|
+
};
|
|
13032
|
+
}
|
|
13033
|
+
function releaseCanaryDisableReceipt(receiptCandidate, fixtureId) {
|
|
13034
|
+
if (!isRecord3(receiptCandidate) || Object.keys(receiptCandidate).some((key) => !RELEASE_CANARY_DISABLE_RECEIPT_KEYS.has(key)) || Object.keys(receiptCandidate).length !== RELEASE_CANARY_DISABLE_RECEIPT_KEYS.size || receiptCandidate.fixtureId !== fixtureId || receiptCandidate.state !== "disabled" || typeof receiptCandidate.idempotent !== "boolean")
|
|
13035
|
+
return null;
|
|
13036
|
+
return { fixtureId, state: "disabled", idempotent: receiptCandidate.idempotent };
|
|
13037
|
+
}
|
|
13038
|
+
function releaseCanaryPendingPath(request) {
|
|
13039
|
+
const params = new URLSearchParams({
|
|
13040
|
+
p_issuer: request.p_issuer,
|
|
13041
|
+
p_subject: request.p_subject
|
|
13042
|
+
});
|
|
13043
|
+
return `${RELEASE_CANARY_PENDING_RPC_PATH}?${params.toString()}`;
|
|
13044
|
+
}
|
|
13045
|
+
function releaseCanaryPendingReadback(readbackCandidate) {
|
|
13046
|
+
return readbackCandidate === false;
|
|
13047
|
+
}
|
|
12996
13048
|
async function applicationOriginMatches(http, projectRef2, applicationOrigin) {
|
|
12997
|
-
const endpointRead = await http.get(`${endpoint(projectRef2)}/endpoint/projection`, {
|
|
13049
|
+
const endpointRead = await http.get(`${endpoint(projectRef2)}/endpoint/projection`, { maxJsonBytes: PROJECT_ENDPOINT_RESPONSE_MAX_BYTES, responseTimeoutMs: RELEASE_READ_RESPONSE_TIMEOUT_MS });
|
|
12998
13050
|
return projectApiOrigins(endpointRead, projectRef2)?.includes(applicationOrigin) === true;
|
|
12999
13051
|
}
|
|
13000
13052
|
function registerReleaseTools(server, http, options = {}) {
|
|
13001
|
-
server.tool("release", "Verified release controls. Management actions use the Management API;
|
|
13053
|
+
server.tool("release", "Verified release controls. Management actions use the Management API; release canary stage/disable replay additionally require the selected project's SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY.", {
|
|
13002
13054
|
action: withDescription(stringEnum([
|
|
13003
13055
|
"logical_backup_list",
|
|
13004
13056
|
"logical_backup_create",
|
|
13005
13057
|
"logical_backup_restore",
|
|
13006
13058
|
"postgrest_status",
|
|
13007
13059
|
"postgrest_restart",
|
|
13008
|
-
"release_canary_fixture_stage_replay"
|
|
13060
|
+
"release_canary_fixture_stage_replay",
|
|
13061
|
+
"release_canary_fixture_disable_replay"
|
|
13009
13062
|
]), "Release control action"),
|
|
13010
13063
|
ref: optional(Type.String(), options.projectRef ? "Optional override when not auto-linked" : "Project ref"),
|
|
13011
13064
|
backup_id: optional(Type.String(), "[logical_backup_restore] Exact verified logical-full backup ID from the selected project inventory"),
|
|
13012
13065
|
expected_sha256: optional(Type.String(), "[logical_backup_restore] Exact lowercase SHA-256 from the selected project inventory"),
|
|
13013
13066
|
restore_confirmation: optional(Type.String(), "[logical_backup_restore] Exact RESTORE_PROJECT:<ref>:<backup_id>:<sha256> confirmation"),
|
|
13014
|
-
subject: optional(Type.String(), "[release_canary_fixture_stage_replay] Exact central subject UUID"),
|
|
13015
|
-
request_id: optional(Type.String(), "[release_canary_fixture_stage_replay] Exact idempotent stage request UUID")
|
|
13016
|
-
|
|
13067
|
+
subject: optional(Type.String(), "[release_canary_fixture_stage_replay/disable_replay] Exact central subject UUID"),
|
|
13068
|
+
request_id: optional(Type.String(), "[release_canary_fixture_stage_replay] Exact idempotent stage request UUID"),
|
|
13069
|
+
fixture_id: optional(Type.String(), "[release_canary_fixture_disable_replay] Exact staged fixture UUID"),
|
|
13070
|
+
disable_request_id: optional(Type.String(), "[release_canary_fixture_disable_replay] Exact idempotent disable request UUID"),
|
|
13071
|
+
issuer: optional(Type.String(), "[release_canary_fixture_disable_replay] Exact HTTP(S) issuer")
|
|
13072
|
+
}, async ({ action, ref, backup_id, expected_sha256, restore_confirmation, subject, request_id, fixture_id, disable_request_id, issuer }) => {
|
|
13017
13073
|
const projectRef2 = typeof ref === "string" && ref || options.projectRef;
|
|
13018
13074
|
if (!projectRef2)
|
|
13019
13075
|
throw new Error("'ref' is required for release controls");
|
|
@@ -13105,6 +13161,36 @@ function registerReleaseTools(server, http, options = {}) {
|
|
|
13105
13161
|
receipt
|
|
13106
13162
|
});
|
|
13107
13163
|
}
|
|
13164
|
+
if (action === "release_canary_fixture_disable_replay") {
|
|
13165
|
+
if (!options.applicationHttp || !options.applicationOrigin) {
|
|
13166
|
+
throw new Error("release_canary_fixture_disable_replay requires SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY");
|
|
13167
|
+
}
|
|
13168
|
+
const request = releaseCanaryDisableInput(fixture_id, disable_request_id, issuer, subject);
|
|
13169
|
+
if (!await applicationOriginMatches(http, projectRef2, options.applicationOrigin)) {
|
|
13170
|
+
return releaseControlFailure("release.release_canary.fixture_disable_replay", "INVALID_RESPONSE", null);
|
|
13171
|
+
}
|
|
13172
|
+
const response = await options.applicationHttp.postReleaseMutation(RELEASE_CANARY_DISABLE_RPC_PATH, request);
|
|
13173
|
+
if (!response.ok || response.status !== 200) {
|
|
13174
|
+
return mutationFailure("release.release_canary.fixture_disable_replay", response);
|
|
13175
|
+
}
|
|
13176
|
+
const receipt = releaseCanaryDisableReceipt(response.data, request.p_fixture_id);
|
|
13177
|
+
if (!receipt) {
|
|
13178
|
+
return releaseControlFailure("release.release_canary.fixture_disable_replay", "OUTCOME_UNKNOWN", response.status);
|
|
13179
|
+
}
|
|
13180
|
+
const pendingRequest = {
|
|
13181
|
+
p_issuer: request.p_issuer,
|
|
13182
|
+
p_subject: request.p_subject
|
|
13183
|
+
};
|
|
13184
|
+
const pendingResponse = await options.applicationHttp.get(releaseCanaryPendingPath(pendingRequest), { maxJsonBytes: MUTATION_MAX_BYTES, responseTimeoutMs: RELEASE_READ_RESPONSE_TIMEOUT_MS });
|
|
13185
|
+
if (!pendingResponse.ok || pendingResponse.status !== 200 || !releaseCanaryPendingReadback(pendingResponse.data) || !await applicationOriginMatches(http, projectRef2, options.applicationOrigin)) {
|
|
13186
|
+
return releaseControlFailure("release.release_canary.fixture_disable_replay", "OUTCOME_UNKNOWN", pendingResponse.transportError ? null : pendingResponse.status);
|
|
13187
|
+
}
|
|
13188
|
+
return releaseControlSuccess("release.release_canary.fixture_disable_replay", {
|
|
13189
|
+
project_ref: projectRef2,
|
|
13190
|
+
receipt,
|
|
13191
|
+
pending: false
|
|
13192
|
+
});
|
|
13193
|
+
}
|
|
13108
13194
|
if (action !== "postgrest_restart")
|
|
13109
13195
|
throw new Error("Unknown release control action");
|
|
13110
13196
|
const mutation = await http.postReleaseMutation(`${endpoint(projectRef2)}/services/postgrest/restart`);
|
|
@@ -13125,7 +13211,7 @@ function registerReleaseTools(server, http, options = {}) {
|
|
|
13125
13211
|
// package.json
|
|
13126
13212
|
var package_default = {
|
|
13127
13213
|
name: "@supacloud/cli",
|
|
13128
|
-
version: "0.
|
|
13214
|
+
version: "0.30.0",
|
|
13129
13215
|
description: "Project-scoped CLI for SupaCloud users",
|
|
13130
13216
|
type: "module",
|
|
13131
13217
|
main: "./dist/index.js",
|
|
@@ -13354,8 +13440,8 @@ DEFAULT CONTEXT
|
|
|
13354
13440
|
Without a selector or project variables, runs use the current project's legacy .env.
|
|
13355
13441
|
Application status accepts SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY.
|
|
13356
13442
|
Management-backed project commands require SUPACLOUD_API_URL +
|
|
13357
|
-
SUPACLOUD_API_TOKEN. Only release
|
|
13358
|
-
the selected project's SUPABASE_* pair for
|
|
13443
|
+
SUPACLOUD_API_TOKEN. Only release canary stage/disable replay actions may additionally use
|
|
13444
|
+
the selected project's SUPABASE_* pair for fixed application-origin RPCs.
|
|
13359
13445
|
SUPACLOUD_PROJECT_REF is required when it cannot be inferred from <ref>.api.*.
|
|
13360
13446
|
|
|
13361
13447
|
SUPACLOUD_READ_ONLY=true blocks remote writes. Production writes require an
|
|
@@ -13377,6 +13463,7 @@ EXAMPLES
|
|
|
13377
13463
|
${preferredCommand} release postgrest_status --ref abc123
|
|
13378
13464
|
${preferredCommand} release postgrest_restart --ref abc123
|
|
13379
13465
|
${preferredCommand} release release_canary_fixture_stage_replay --ref abc123 --subject <uuid> --request_id <uuid>
|
|
13466
|
+
${preferredCommand} release release_canary_fixture_disable_replay --ref abc123 --fixture_id <uuid> --disable_request_id <uuid> --issuer <issuer-url> --subject <uuid>
|
|
13380
13467
|
${preferredCommand} queue stats --queue emails
|
|
13381
13468
|
${preferredCommand} queue dlq --queue emails --limit 20
|
|
13382
13469
|
${preferredCommand} frontend list --ref abc123
|
package/package.json
CHANGED
|
@@ -16,6 +16,7 @@ Load this reference when selecting a command surface or when a user asks an AI t
|
|
|
16
16
|
| Rebuild local database | `supabase db_reset` | Local only; preserve required seed behavior |
|
|
17
17
|
| Inspect or back up a remote database | `supabase db_pull`, `migration_list`, `db_dump`, `gen_types` | Requires explicit PostgreSQL DSN; redact it |
|
|
18
18
|
| Replay the release-canary fixture stage receipt | `release release_canary_fixture_stage_replay` | Dual-bind Management project context and that project's service-role application origin; accepts only the exact subject/request UUID pair and a strict idempotent receipt |
|
|
19
|
+
| Disable a staged release-canary fixture | `release release_canary_fixture_disable_replay` | Uses the fixed disable RPC once, accepts idempotent false/true receipts, then queries the existing pending RPC with issuer/subject and requires authoritative JSON `false`; fixture binding comes from the strict disable receipt |
|
|
19
20
|
| Preview/apply migrations remotely | `supabase push` | Always dry-run first; production needs explicit approval |
|
|
20
21
|
| Mark proven-equivalent historical migrations as applied | `database baseline_migrations` | Dry-run, schema-equivalence proof, backup, explicit approval |
|
|
21
22
|
| Inspect auth users or generate a controlled login link | `auth list_users`, `auth get_user`, `auth generate_link` | User reads are bounded; generation supports only `magiclink`, `recovery`, and `invite`, requires production confirmation, and returns only a validated action URL |
|
|
@@ -40,7 +41,7 @@ until a project-scoped context is resolved.
|
|
|
40
41
|
- `secrets`: project secret management; never print values after write.
|
|
41
42
|
- `queue`, `task_events`, `diagnostics`: asynchronous workload operations and bounded diagnostics.
|
|
42
43
|
- `gateway`: project route/config/rebuild operations.
|
|
43
|
-
- `release`: verified backup/PostgREST lifecycle controls plus
|
|
44
|
+
- `release`: verified backup/PostgREST lifecycle controls plus production-confirmed, non-retried, strict release-canary fixture stage and disable replay actions. These actions require both the selected Management project binding and that project's `SUPABASE_URL`/`SUPABASE_SERVICE_ROLE_KEY`.
|
|
44
45
|
- `ai`: inspect or install this packaged Skill.
|
|
45
46
|
|
|
46
47
|
## Safe inspection pattern
|