@supacloud/cli 0.75.1 → 0.76.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 -3
- package/dist/index.js +131 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -231,6 +231,7 @@ actions and also exposes the immutable prebuilt release workflow:
|
|
|
231
231
|
|
|
232
232
|
```bash
|
|
233
233
|
supacloud-cli frontend list_releases --ref abc123 --id web
|
|
234
|
+
supacloud-cli frontend get_active_release --ref abc123 --id web
|
|
234
235
|
supacloud-cli frontend get_release --ref abc123 --id web --release_id <sha256>
|
|
235
236
|
supacloud-cli frontend upload_release --ref abc123 --id web --zip_path ./dist.zip
|
|
236
237
|
supacloud-cli frontend activate_release --ref abc123 --id web \
|
|
@@ -238,6 +239,7 @@ supacloud-cli frontend activate_release --ref abc123 --id web \
|
|
|
238
239
|
--expected_active_release_id absent \
|
|
239
240
|
--expected_activation_id absent \
|
|
240
241
|
--mutation_id <retry-stable-uuid-v4>
|
|
242
|
+
supacloud-cli frontend rollback --ref abc123 --id web --release_id <retained-sha256>
|
|
241
243
|
```
|
|
242
244
|
|
|
243
245
|
`upload_release` hashes and streams an existing regular ZIP file without
|
|
@@ -245,10 +247,19 @@ buffering the full archive. The Management API binds the upload to that SHA-256,
|
|
|
245
247
|
and the CLI reads the immutable release back before reporting success.
|
|
246
248
|
`activate_release` uses both the observed active release and activation IDs as
|
|
247
249
|
optimistic concurrency tokens, then verifies the authoritative active release.
|
|
248
|
-
Use the values returned by `
|
|
249
|
-
release has been activated. Production uploads and
|
|
250
|
+
Use the values returned by `get_active_release`; `absent` is valid only when no
|
|
251
|
+
release has been activated. Production uploads, activations and rollback require the
|
|
250
252
|
normal exact `--confirm-production <ref>` value, and
|
|
251
|
-
`SUPACLOUD_READ_ONLY=true` blocks
|
|
253
|
+
`SUPACLOUD_READ_ONLY=true` blocks all mutations.
|
|
254
|
+
|
|
255
|
+
`get_active_release` automatically handles older servers returning HTTP 404
|
|
256
|
+
with a one-record history page and exact active artifact readback. It never
|
|
257
|
+
falls back for authentication, integrity or server failures.
|
|
258
|
+
`rollback` reads the current CAS identity and generates the mutation ID itself;
|
|
259
|
+
the retained target release must be explicit. It does not infer the previous
|
|
260
|
+
version from content-hash-sorted history, reupload assets or restore a database.
|
|
261
|
+
An uncertain result includes the mutation ID for read-only reconciliation and
|
|
262
|
+
must not be blindly retried.
|
|
252
263
|
|
|
253
264
|
### Immutable application releases
|
|
254
265
|
|
package/dist/index.js
CHANGED
|
@@ -210793,8 +210793,8 @@ var ACTION_POLICY = {
|
|
|
210793
210793
|
},
|
|
210794
210794
|
secrets: { read: ["list"], write: ["upsert", "delete"] },
|
|
210795
210795
|
frontend: {
|
|
210796
|
-
read: ["list", "get", "build_logs", "list_frameworks", "list_records", "list_releases", "get_release"],
|
|
210797
|
-
write: ["create", "update", "delete", "deploy_git", "deploy_upload", "redeploy", "add_domain", "remove_domain", "set_env", "upload_release", "activate_release"]
|
|
210796
|
+
read: ["list", "get", "build_logs", "list_frameworks", "list_records", "list_releases", "get_active_release", "get_release"],
|
|
210797
|
+
write: ["create", "update", "delete", "deploy_git", "deploy_upload", "redeploy", "add_domain", "remove_domain", "set_env", "upload_release", "activate_release", "rollback"]
|
|
210798
210798
|
},
|
|
210799
210799
|
task_events: { read: ["inspect_webhook"], write: ["register_webhook", "unregister_webhook"] },
|
|
210800
210800
|
diagnostics: { read: ["list_checks", "get_run"], write: ["run_checks", "repair"] },
|
|
@@ -218159,12 +218159,15 @@ function releaseInventory(candidate, expected) {
|
|
|
218159
218159
|
next_cursor: nextCursor
|
|
218160
218160
|
};
|
|
218161
218161
|
}
|
|
218162
|
-
function
|
|
218162
|
+
function deploymentEndpoint(projectRef, deploymentId) {
|
|
218163
218163
|
if (!PROJECT_REF_PATTERN3.test(projectRef))
|
|
218164
218164
|
throw new Error("'ref' is invalid for frontend releases");
|
|
218165
218165
|
if (!DEPLOYMENT_ID_PATTERN.test(deploymentId))
|
|
218166
218166
|
throw new Error("'id' is invalid for frontend releases");
|
|
218167
|
-
return `/v1/projects/${encodeURIComponent(projectRef)}/frontend/deployments/${encodeURIComponent(deploymentId)}
|
|
218167
|
+
return `/v1/projects/${encodeURIComponent(projectRef)}/frontend/deployments/${encodeURIComponent(deploymentId)}`;
|
|
218168
|
+
}
|
|
218169
|
+
function releaseEndpoint(projectRef, deploymentId) {
|
|
218170
|
+
return `${deploymentEndpoint(projectRef, deploymentId)}/releases`;
|
|
218168
218171
|
}
|
|
218169
218172
|
function releasePath(projectRef, deploymentId, releaseId) {
|
|
218170
218173
|
if (!RELEASE_ID_PATTERN.test(releaseId))
|
|
@@ -218369,16 +218372,15 @@ function activationReceipt(candidate, input) {
|
|
|
218369
218372
|
return release;
|
|
218370
218373
|
}
|
|
218371
218374
|
async function activeReleaseReadback(http, identity) {
|
|
218372
|
-
const
|
|
218373
|
-
|
|
218374
|
-
|
|
218375
|
-
|
|
218376
|
-
|
|
218377
|
-
|
|
218378
|
-
|
|
218379
|
-
|
|
218380
|
-
|
|
218381
|
-
return { release: null, status: inventoryRead.status };
|
|
218375
|
+
const snapshot = await readActiveFrontendRelease(http, identity.projectRef, identity.deploymentId);
|
|
218376
|
+
if ("failure" in snapshot)
|
|
218377
|
+
return { release: null, status: snapshot.status };
|
|
218378
|
+
const inventory = snapshot.inventory;
|
|
218379
|
+
if (inventory.active_release_id !== identity.releaseId || inventory.active_activation_id !== identity.mutationId) {
|
|
218380
|
+
return { release: null, status: 200 };
|
|
218381
|
+
}
|
|
218382
|
+
if (snapshot.exactReadback) {
|
|
218383
|
+
return { release: inventory.releases[0] ?? null, status: 200 };
|
|
218382
218384
|
}
|
|
218383
218385
|
const releaseRead = await http.get(releasePath(identity.projectRef, identity.deploymentId, identity.releaseId), { maxJsonBytes: RESPONSE_MAX_BYTES });
|
|
218384
218386
|
const release = releaseRead.ok ? releaseEnvelope(releaseRead.data, {
|
|
@@ -218388,6 +218390,103 @@ async function activeReleaseReadback(http, identity) {
|
|
|
218388
218390
|
}) : null;
|
|
218389
218391
|
return { release, status: releaseRead.status };
|
|
218390
218392
|
}
|
|
218393
|
+
async function readActiveFrontendRelease(http, projectRef, deploymentId) {
|
|
218394
|
+
const response = await http.get(`${deploymentEndpoint(projectRef, deploymentId)}/active-release`, {
|
|
218395
|
+
maxJsonBytes: RESPONSE_MAX_BYTES
|
|
218396
|
+
});
|
|
218397
|
+
const inventory = response.ok && response.status === 200 ? releaseInventory(response.data, { projectRef, deploymentId }) : null;
|
|
218398
|
+
if (inventory) {
|
|
218399
|
+
if (inventory.next_cursor !== null || inventory.releases.length !== (inventory.active_release_id === null ? 0 : 1) || inventory.releases[0] && inventory.releases[0].release_id !== inventory.active_release_id) {
|
|
218400
|
+
return { failure: releaseReadFailure("frontend.get_active_release", response), status: response.status };
|
|
218401
|
+
}
|
|
218402
|
+
return { inventory, exactReadback: false };
|
|
218403
|
+
}
|
|
218404
|
+
if (response.ok || response.status !== 404) {
|
|
218405
|
+
return { failure: releaseReadFailure("frontend.get_active_release", response), status: response.status };
|
|
218406
|
+
}
|
|
218407
|
+
const historyResponse = await http.get(`${releaseEndpoint(projectRef, deploymentId)}?limit=1`, {
|
|
218408
|
+
maxJsonBytes: RESPONSE_MAX_BYTES
|
|
218409
|
+
});
|
|
218410
|
+
const history = historyResponse.ok && historyResponse.status === 200 ? releaseInventory(historyResponse.data, { projectRef, deploymentId }) : null;
|
|
218411
|
+
if (!history || history.releases.length > 1) {
|
|
218412
|
+
return {
|
|
218413
|
+
failure: releaseReadFailure("frontend.get_active_release", historyResponse),
|
|
218414
|
+
status: historyResponse.status
|
|
218415
|
+
};
|
|
218416
|
+
}
|
|
218417
|
+
if (history.active_release_id === null) {
|
|
218418
|
+
return { inventory: { ...history, releases: [], next_cursor: null }, exactReadback: false };
|
|
218419
|
+
}
|
|
218420
|
+
const exactResponse = await http.get(releasePath(projectRef, deploymentId, history.active_release_id), {
|
|
218421
|
+
maxJsonBytes: RESPONSE_MAX_BYTES
|
|
218422
|
+
});
|
|
218423
|
+
const release = exactResponse.ok && exactResponse.status === 200 ? releaseEnvelope(exactResponse.data, { projectRef, deploymentId, releaseId: history.active_release_id }) : null;
|
|
218424
|
+
if (!release) {
|
|
218425
|
+
return {
|
|
218426
|
+
failure: releaseReadFailure("frontend.get_active_release", exactResponse),
|
|
218427
|
+
status: exactResponse.status
|
|
218428
|
+
};
|
|
218429
|
+
}
|
|
218430
|
+
return { inventory: { ...history, releases: [release], next_cursor: null }, exactReadback: true };
|
|
218431
|
+
}
|
|
218432
|
+
async function getActiveFrontendRelease(http, projectRef, deploymentId) {
|
|
218433
|
+
const snapshot = await readActiveFrontendRelease(http, projectRef, deploymentId);
|
|
218434
|
+
return "failure" in snapshot ? snapshot.failure : toolResponse(snapshot.inventory);
|
|
218435
|
+
}
|
|
218436
|
+
async function readFrontendReleaseAuthority(http, projectRef, deploymentId) {
|
|
218437
|
+
return getActiveFrontendRelease(http, projectRef, deploymentId);
|
|
218438
|
+
}
|
|
218439
|
+
async function rollbackFrontendRelease(http, projectRef, deploymentId, releaseId) {
|
|
218440
|
+
if (!RELEASE_ID_PATTERN.test(releaseId))
|
|
218441
|
+
throw new Error("'release_id' must be a SHA-256 digest");
|
|
218442
|
+
const authority = await readActiveFrontendRelease(http, projectRef, deploymentId);
|
|
218443
|
+
if ("failure" in authority)
|
|
218444
|
+
return authority.failure;
|
|
218445
|
+
const inventory = authority.inventory;
|
|
218446
|
+
if (inventory.active_release_id === releaseId) {
|
|
218447
|
+
return toolResponse({
|
|
218448
|
+
project_ref: projectRef,
|
|
218449
|
+
deployment_id: deploymentId,
|
|
218450
|
+
active_release_id: releaseId,
|
|
218451
|
+
activation_id: inventory.active_activation_id,
|
|
218452
|
+
unchanged: true
|
|
218453
|
+
});
|
|
218454
|
+
}
|
|
218455
|
+
if (inventory.active_release_id === null || inventory.active_activation_id === null) {
|
|
218456
|
+
return releaseFailure("frontend.rollback", "NO_ACTIVE_RELEASE", 409);
|
|
218457
|
+
}
|
|
218458
|
+
const target = await getFrontendRelease(http, projectRef, deploymentId, releaseId);
|
|
218459
|
+
if (target.isError)
|
|
218460
|
+
return target;
|
|
218461
|
+
const mutationId = crypto.randomUUID();
|
|
218462
|
+
const activation = await activateFrontendRelease(http, {
|
|
218463
|
+
projectRef,
|
|
218464
|
+
deploymentId,
|
|
218465
|
+
releaseId,
|
|
218466
|
+
expectedActiveReleaseId: inventory.active_release_id,
|
|
218467
|
+
expectedActivationId: inventory.active_activation_id,
|
|
218468
|
+
mutationId
|
|
218469
|
+
});
|
|
218470
|
+
if (activation.isError) {
|
|
218471
|
+
return {
|
|
218472
|
+
...toolResponse({
|
|
218473
|
+
...JSON.parse(activation.content[0].text),
|
|
218474
|
+
operation: "frontend.rollback",
|
|
218475
|
+
mutation_id: mutationId,
|
|
218476
|
+
target_release_id: releaseId,
|
|
218477
|
+
expected_active_release_id: inventory.active_release_id,
|
|
218478
|
+
expected_activation_id: inventory.active_activation_id
|
|
218479
|
+
}),
|
|
218480
|
+
isError: true
|
|
218481
|
+
};
|
|
218482
|
+
}
|
|
218483
|
+
return toolResponse({
|
|
218484
|
+
...JSON.parse(activation.content[0].text),
|
|
218485
|
+
rollback: true,
|
|
218486
|
+
previous_release_id: inventory.active_release_id,
|
|
218487
|
+
previous_activation_id: inventory.active_activation_id
|
|
218488
|
+
});
|
|
218489
|
+
}
|
|
218391
218490
|
async function activateFrontendRelease(http, input) {
|
|
218392
218491
|
const mutationId = input.mutationId;
|
|
218393
218492
|
if (!MUTATION_ID_PATTERN.test(mutationId))
|
|
@@ -218454,7 +218553,7 @@ async function activationReadback(http, input, mutationId, activationStatus) {
|
|
|
218454
218553
|
// src/shared/tools/frontend-tools.ts
|
|
218455
218554
|
function registerFrontendTools(server2, http) {
|
|
218456
218555
|
registerTool(server2, "frontend", `Frontend hosting and immutable prebuilt releases. Supports: static, react, vue, svelte, sveltekit, sveltekit-static, nextjs, nuxt, astro.
|
|
218457
|
-
Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy, build_logs, add_domain, remove_domain, set_env, list_frameworks, list_records, list_releases, get_release, upload_release, activate_release`, {
|
|
218556
|
+
Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy, build_logs, add_domain, remove_domain, set_env, list_frameworks, list_records, list_releases, get_active_release, get_release, upload_release, activate_release, rollback`, {
|
|
218458
218557
|
action: withDescription(stringEnum([
|
|
218459
218558
|
"list",
|
|
218460
218559
|
"get",
|
|
@@ -218471,9 +218570,11 @@ Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy,
|
|
|
218471
218570
|
"list_frameworks",
|
|
218472
218571
|
"list_records",
|
|
218473
218572
|
"list_releases",
|
|
218573
|
+
"get_active_release",
|
|
218474
218574
|
"get_release",
|
|
218475
218575
|
"upload_release",
|
|
218476
|
-
"activate_release"
|
|
218576
|
+
"activate_release",
|
|
218577
|
+
"rollback"
|
|
218477
218578
|
]), "Action"),
|
|
218478
218579
|
ref: optional(String2(), "Project ref"),
|
|
218479
218580
|
id: optional(String2(), "Deployment ID"),
|
|
@@ -218489,7 +218590,7 @@ Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy,
|
|
|
218489
218590
|
git_url: optional(String2(), "[deploy_git] Git repository URL"),
|
|
218490
218591
|
branch: optional(String2(), "[deploy_git] Branch (default: main)"),
|
|
218491
218592
|
zip_path: optional(String2(), "[deploy_upload/upload_release] Local ZIP file path"),
|
|
218492
|
-
release_id: optional(String2(), "[get_release/activate_release] SHA-256 release ID"),
|
|
218593
|
+
release_id: optional(String2(), "[get_release/activate_release/rollback] SHA-256 release ID"),
|
|
218493
218594
|
expected_active_release_id: optional(String2(), "[activate_release] Current release SHA-256 or absent"),
|
|
218494
218595
|
expected_activation_id: optional(String2(), "[activate_release] Current activation UUIDv4 or absent"),
|
|
218495
218596
|
mutation_id: optional(String2(), "[activate_release] Required retry-stable UUIDv4"),
|
|
@@ -218634,6 +218735,10 @@ Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy,
|
|
|
218634
218735
|
need("id", id);
|
|
218635
218736
|
need("release_id", release_id);
|
|
218636
218737
|
return getFrontendRelease(http, ref, id, release_id);
|
|
218738
|
+
case "get_active_release":
|
|
218739
|
+
need("ref", ref);
|
|
218740
|
+
need("id", id);
|
|
218741
|
+
return getActiveFrontendRelease(http, ref, id);
|
|
218637
218742
|
case "upload_release":
|
|
218638
218743
|
need("ref", ref);
|
|
218639
218744
|
need("id", id);
|
|
@@ -218654,6 +218759,11 @@ Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy,
|
|
|
218654
218759
|
expectedActivationId: expected_activation_id,
|
|
218655
218760
|
mutationId: mutation_id
|
|
218656
218761
|
});
|
|
218762
|
+
case "rollback":
|
|
218763
|
+
need("ref", ref);
|
|
218764
|
+
need("id", id);
|
|
218765
|
+
need("release_id", release_id);
|
|
218766
|
+
return rollbackFrontendRelease(http, ref, id, release_id);
|
|
218657
218767
|
default:
|
|
218658
218768
|
text = `❌ Unknown action`;
|
|
218659
218769
|
}
|
|
@@ -252167,7 +252277,7 @@ function registerDeployTools(server2, http, options = {}) {
|
|
|
252167
252277
|
report("package", relative17(projectDirectory, outputDirectory) || ".");
|
|
252168
252278
|
const archive = await createFrontendArchive(outputDirectory);
|
|
252169
252279
|
try {
|
|
252170
|
-
const inventory = payload(await
|
|
252280
|
+
const inventory = payload(await readFrontendReleaseAuthority(http, projectRef, selected.id));
|
|
252171
252281
|
const activeReleaseId = typeof inventory.active_release_id === "string" ? inventory.active_release_id : null;
|
|
252172
252282
|
const activeActivationId = typeof inventory.active_activation_id === "string" ? inventory.active_activation_id : null;
|
|
252173
252283
|
if (archive.sha256 === activeReleaseId) {
|
|
@@ -252210,6 +252320,8 @@ function registerDeployTools(server2, http, options = {}) {
|
|
|
252210
252320
|
deployment_id: selected.id,
|
|
252211
252321
|
release_id: activated.active_release_id,
|
|
252212
252322
|
activation_id: activated.activation_id,
|
|
252323
|
+
previous_release_id: activeReleaseId,
|
|
252324
|
+
previous_activation_id: activeActivationId,
|
|
252213
252325
|
url: finalDeployment.deploymentUrl,
|
|
252214
252326
|
file_count: archive.fileCount,
|
|
252215
252327
|
source_bytes: archive.sourceBytes,
|
|
@@ -252492,7 +252604,7 @@ function registerRemoteDevTools(server2, options = {}) {
|
|
|
252492
252604
|
// package.json
|
|
252493
252605
|
var package_default = {
|
|
252494
252606
|
name: "@supacloud/cli",
|
|
252495
|
-
version: "0.
|
|
252607
|
+
version: "0.76.0",
|
|
252496
252608
|
description: "Project-scoped CLI for SupaCloud users",
|
|
252497
252609
|
type: "module",
|
|
252498
252610
|
main: "./dist/index.js",
|