@supacloud/cli 0.93.0 → 0.94.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 +23 -32
- package/dist/index.js +177 -543
- package/dist/shared/deployment-upgrade.d.ts +2 -0
- package/dist/shared/tools/application-tools.d.ts +1 -1
- package/dist/shared/tools/deploy-tools.d.ts +0 -8
- package/dist/shared/tools/developer-experience-tools.d.ts +1 -1
- package/dist/shared/tools/project-deployment.d.ts +1 -0
- package/dist/shared/tools/remote-dev-tools.d.ts +1 -1
- package/dist/tool-runtime.js +25 -0
- package/package.json +2 -2
- package/skills/supacloud/SKILL.md +9 -0
- package/skills/supacloud/references/command-map.md +3 -5
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ are not supported.
|
|
|
13
13
|
|
|
14
14
|
## Project Setup and Environment Profiles
|
|
15
15
|
|
|
16
|
-
For an existing frontend, the shortest path is:
|
|
16
|
+
For an existing SupaCloud application with a frontend, the shortest path is:
|
|
17
17
|
|
|
18
18
|
```sh
|
|
19
19
|
supacloud init
|
|
@@ -26,6 +26,11 @@ supacloud --env test deploy
|
|
|
26
26
|
overwriting an existing project configuration. `env list` and `env check` are
|
|
27
27
|
local-only, redact credentials, and provide stable JSON with `--json` for CI or
|
|
28
28
|
AI tooling. The real `.env.supacloud.*` files are added to `.gitignore`.
|
|
29
|
+
Fill in `SUPACLOUD_APPLICATION_ID` and the Management credentials in the
|
|
30
|
+
selected profile, and configure the application's current delivery build.
|
|
31
|
+
Standalone frontend deployment is no longer supported. Upgrade the CLI with
|
|
32
|
+
`npm install -g @supacloud/cli@latest` and upgrade the SupaCloud server together.
|
|
33
|
+
`deploy` checks the server protocol before building or uploading.
|
|
29
34
|
|
|
30
35
|
## Application Starter
|
|
31
36
|
|
|
@@ -356,6 +361,7 @@ target configuration:
|
|
|
356
361
|
|
|
357
362
|
```json
|
|
358
363
|
{
|
|
364
|
+
"application": { "id": "orders", "environmentId": "test" },
|
|
359
365
|
"targets": {
|
|
360
366
|
"web": {
|
|
361
367
|
"type": "frontend",
|
|
@@ -383,36 +389,24 @@ loopback development origins, with the default `:80` likewise omitted. Use
|
|
|
383
389
|
|
|
384
390
|
### Immutable frontend releases
|
|
385
391
|
|
|
386
|
-
The `frontend` command
|
|
387
|
-
|
|
392
|
+
The `frontend` command retains release inspection and explicit operator rollback.
|
|
393
|
+
Publish through `supacloud deploy`, which delegates activation to the platform:
|
|
388
394
|
|
|
389
395
|
```bash
|
|
390
396
|
supacloud frontend list_releases --ref abc123 --id web
|
|
391
397
|
supacloud frontend get_active_release --ref abc123 --id web
|
|
392
398
|
supacloud frontend get_release --ref abc123 --id web --release_id <sha256>
|
|
393
|
-
supacloud frontend upload_release --ref abc123 --id web --archive_path ./dist.tar.zst
|
|
394
|
-
supacloud frontend activate_release --ref abc123 --id web \
|
|
395
|
-
--release_id <sha256> \
|
|
396
|
-
--expected_active_release_id absent \
|
|
397
|
-
--expected_activation_id absent \
|
|
398
|
-
--mutation_id <retry-stable-uuid-v4>
|
|
399
399
|
supacloud frontend rollback --ref abc123 --id web --release_id <retained-sha256>
|
|
400
400
|
supacloud frontend rollback --ref abc123 --id web
|
|
401
401
|
```
|
|
402
402
|
|
|
403
|
-
`upload_release`
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
normal exact `--confirm-production <ref>` value, and
|
|
411
|
-
`SUPACLOUD_READ_ONLY=true` blocks all mutations.
|
|
412
|
-
|
|
413
|
-
`get_active_release` automatically handles older servers returning HTTP 404
|
|
414
|
-
with a one-record history page and exact active artifact readback. It never
|
|
415
|
-
falls back for authentication, integrity or server failures.
|
|
403
|
+
`deploy_git`, `deploy_upload`, `redeploy`, `upload_release` and `activate_release`
|
|
404
|
+
have been removed from `frontend`. Old invocations fail with an upgrade message,
|
|
405
|
+
without reading an archive or sending a request. Production rollback retains
|
|
406
|
+
the exact `--confirm-production <ref>` gate; read-only mode blocks mutations.
|
|
407
|
+
|
|
408
|
+
`get_active_release` requires the current server endpoint. An HTTP 404 returns
|
|
409
|
+
an upgrade/deployment-existence hint and never falls back to historical inventory.
|
|
416
410
|
`rollback` without `--release_id` asks SupaCloud for a journal-verified previous
|
|
417
411
|
immutable release, reads the current CAS identity and generates the mutation ID
|
|
418
412
|
itself. An explicit `--release_id` remains available for a deliberate rollback
|
|
@@ -635,26 +629,23 @@ reporting success. Management endpoint projection is checked before and after.
|
|
|
635
629
|
|
|
636
630
|
### Internal release scope controls
|
|
637
631
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
contract and is not implemented yet. For current deployments, use the
|
|
641
|
-
target-level one-command flow documented in
|
|
632
|
+
Do not generate, rebind or hash scope files as part of a deployment.
|
|
633
|
+
Use an application binding and the unified flow documented in
|
|
642
634
|
[`docs/cli-guide.md`](../../docs/cli-guide.md), for example:
|
|
643
635
|
|
|
644
636
|
```bash
|
|
645
637
|
supacloud deploy
|
|
646
|
-
supacloud deploy --target web
|
|
647
|
-
supacloud deploy --target api
|
|
648
638
|
```
|
|
649
639
|
|
|
650
640
|
FA deployment policy and the boundary between platform-owned operations and
|
|
651
641
|
FA-owned business features are documented in
|
|
652
642
|
[`docs/fa-deployment-capability-boundary.md`](../../docs/fa-deployment-capability-boundary.md).
|
|
653
643
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
644
|
+
`deploy --target`, `--id`, `--build_command`, `--output_dir`, `--plan`, `--diff`
|
|
645
|
+
and `--dry_run` are removed. Put build settings in `supacloud.json`; use
|
|
646
|
+
`app deploy-plan` for read-only review. Historical project-specific manifests
|
|
647
|
+
and standalone Function targets must move to application delivery configuration.
|
|
648
|
+
The CLI rejects them rather than falling back to a partial Web deployment.
|
|
658
649
|
|
|
659
650
|
The legacy `.env` fallback is unclassified and therefore does not enable the
|
|
660
651
|
production confirmation gate. Production automation must select a `prod` or
|
package/dist/index.js
CHANGED
|
@@ -8766,6 +8766,28 @@ function outcomeUnknownGuidance(text) {
|
|
|
8766
8766
|
保持原 --env/--env-file 选择,先查询:${command}` : "");
|
|
8767
8767
|
}
|
|
8768
8768
|
|
|
8769
|
+
// src/shared/deployment-upgrade.ts
|
|
8770
|
+
var DEPLOYMENT_UPGRADE = "DEPLOYMENT_UPGRADE_REQUIRED: Legacy deployment is no longer supported. Upgrade with `npm install -g @supacloud/cli@latest`, bind application.id and application.environmentId in supacloud.json (or SUPACLOUD_APPLICATION_ID and SUPACLOUD_ENV), then run `supacloud deploy`. SupaCloud owns deployment planning and activation; do not retry with legacy commands.";
|
|
8771
|
+
var removedFrontendActions = new Set([
|
|
8772
|
+
"deploy_git",
|
|
8773
|
+
"deploy_upload",
|
|
8774
|
+
"redeploy",
|
|
8775
|
+
"upload_release",
|
|
8776
|
+
"activate_release"
|
|
8777
|
+
]);
|
|
8778
|
+
var removedDeployFlags = ["target", "id", "build_command", "output_dir", "plan", "diff", "dry_run"];
|
|
8779
|
+
function assertCurrentDeploymentInput(tool, input) {
|
|
8780
|
+
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
8781
|
+
return;
|
|
8782
|
+
const args = input;
|
|
8783
|
+
if (tool === "frontend" && typeof args["action"] === "string" && removedFrontendActions.has(args["action"])) {
|
|
8784
|
+
throw new Error(DEPLOYMENT_UPGRADE);
|
|
8785
|
+
}
|
|
8786
|
+
if (tool === "deploy" && removedDeployFlags.some((flag) => Object.hasOwn(args, flag))) {
|
|
8787
|
+
throw new Error(`${DEPLOYMENT_UPGRADE} Put build settings in the frontend configuration. For read-only review use \`supacloud app deploy-plan\`.`);
|
|
8788
|
+
}
|
|
8789
|
+
}
|
|
8790
|
+
|
|
8769
8791
|
// src/shared/cli.ts
|
|
8770
8792
|
function cliToolResultIsError(toolResult) {
|
|
8771
8793
|
if (toolResult.isError === true)
|
|
@@ -8889,6 +8911,7 @@ async function runCli(cliTools, args, options = {}) {
|
|
|
8889
8911
|
const parsedArgs = parseCliFlags(args, startIdx, tool.schema);
|
|
8890
8912
|
if (startIdx === 2)
|
|
8891
8913
|
parsedArgs.action = args[1];
|
|
8914
|
+
assertCurrentDeploymentInput(args[0], parsedArgs);
|
|
8892
8915
|
validateToolArguments(tool.schema, parsedArgs);
|
|
8893
8916
|
const result = await tool.callback(parsedArgs);
|
|
8894
8917
|
if (result && result.content && Array.isArray(result.content)) {
|
|
@@ -11188,6 +11211,7 @@ var resultSchema = _Object_({
|
|
|
11188
11211
|
});
|
|
11189
11212
|
function registerTool(server, name, description, schema, callback) {
|
|
11190
11213
|
server.tool(name, description, schema, async (input) => {
|
|
11214
|
+
assertCurrentDeploymentInput(name, input);
|
|
11191
11215
|
const args = parseToolArguments(schema, input);
|
|
11192
11216
|
const result = await callback(args);
|
|
11193
11217
|
if (!Check2(resultSchema, result))
|
|
@@ -16319,39 +16343,6 @@ async function getFrontendRelease(http, projectRef, deploymentId, releaseId) {
|
|
|
16319
16343
|
}
|
|
16320
16344
|
return toolResponse({ project_ref: projectRef, deployment_id: deploymentId, release });
|
|
16321
16345
|
}
|
|
16322
|
-
async function uploadFrontendRelease(http, projectRef, deploymentId, archivePath) {
|
|
16323
|
-
const endpoint = releaseEndpoint(projectRef, deploymentId);
|
|
16324
|
-
const archive = await verifiedArchive(archivePath);
|
|
16325
|
-
let response;
|
|
16326
|
-
try {
|
|
16327
|
-
response = await http.postBinary(endpoint, {
|
|
16328
|
-
stream: archiveStream(archive),
|
|
16329
|
-
byteLength: archive.sizeBytes
|
|
16330
|
-
}, {
|
|
16331
|
-
contentType: FRONTEND_ARCHIVE_CONTENT_TYPE,
|
|
16332
|
-
contentLength: archive.sizeBytes,
|
|
16333
|
-
contentSha256: archive.sha256,
|
|
16334
|
-
maxJsonBytes: RESPONSE_MAX_BYTES,
|
|
16335
|
-
timeoutMs: UPLOAD_REQUEST_TIMEOUT_MS,
|
|
16336
|
-
responseTimeoutMs: MUTATION_RESPONSE_TIMEOUT_MS
|
|
16337
|
-
});
|
|
16338
|
-
} finally {
|
|
16339
|
-
await archive.handle.close();
|
|
16340
|
-
}
|
|
16341
|
-
const release = response.ok ? releaseEnvelope(response.data, { projectRef, deploymentId, releaseId: archive.sha256 }) : null;
|
|
16342
|
-
if (!release) {
|
|
16343
|
-
if (response.status >= 400 && response.status < 500 && response.status !== 408 && !response.transportError) {
|
|
16344
|
-
return releaseFailure("frontend.upload_release", "HTTP_ERROR", response.status);
|
|
16345
|
-
}
|
|
16346
|
-
return uploadReadback(http, projectRef, deploymentId, archive.sha256, response.status);
|
|
16347
|
-
}
|
|
16348
|
-
const readback = await http.get(`${endpoint}/${release.release_id}`, { maxJsonBytes: RESPONSE_MAX_BYTES });
|
|
16349
|
-
const verified = readback.ok ? releaseEnvelope(readback.data, { projectRef, deploymentId, releaseId: release.release_id }) : null;
|
|
16350
|
-
if (!verified || verified.tree_sha256 !== release.tree_sha256) {
|
|
16351
|
-
return releaseFailure("frontend.upload_release", "OUTCOME_UNKNOWN", readback.status);
|
|
16352
|
-
}
|
|
16353
|
-
return toolResponse({ project_ref: projectRef, deployment_id: deploymentId, release: verified });
|
|
16354
|
-
}
|
|
16355
16346
|
async function uploadProjectWebRelease(http, projectRef, environmentId, deploymentId, archivePath) {
|
|
16356
16347
|
const endpoint = `/v1/projects/${encodeURIComponent(projectRef)}/environments/${encodeURIComponent(environmentId)}/deployments/web-release?deployment_id=${encodeURIComponent(deploymentId)}`;
|
|
16357
16348
|
const archive = await verifiedArchive(archivePath);
|
|
@@ -16406,41 +16397,6 @@ async function uploadProjectWebRelease(http, projectRef, environmentId, deployme
|
|
|
16406
16397
|
await archive.handle.close();
|
|
16407
16398
|
}
|
|
16408
16399
|
}
|
|
16409
|
-
async function uploadFrontendSource(http, projectRef, deploymentId, archivePath) {
|
|
16410
|
-
const endpoint = `${deploymentEndpoint(projectRef, deploymentId)}/deploy/upload`;
|
|
16411
|
-
const archive = await verifiedArchive(archivePath);
|
|
16412
|
-
try {
|
|
16413
|
-
const response = await http.postBinary(endpoint, {
|
|
16414
|
-
stream: archiveStream(archive),
|
|
16415
|
-
byteLength: archive.sizeBytes
|
|
16416
|
-
}, {
|
|
16417
|
-
contentType: FRONTEND_ARCHIVE_CONTENT_TYPE,
|
|
16418
|
-
contentLength: archive.sizeBytes,
|
|
16419
|
-
contentSha256: archive.sha256,
|
|
16420
|
-
maxJsonBytes: RESPONSE_MAX_BYTES,
|
|
16421
|
-
timeoutMs: UPLOAD_REQUEST_TIMEOUT_MS
|
|
16422
|
-
});
|
|
16423
|
-
if (!response.ok) {
|
|
16424
|
-
return releaseFailure("frontend.deploy_upload", response.status >= 400 && response.status < 500 ? "HTTP_ERROR" : "OUTCOME_UNKNOWN", response.status);
|
|
16425
|
-
}
|
|
16426
|
-
if (!response.data || typeof response.data !== "object" || !("success" in response.data) || response.data.success !== true) {
|
|
16427
|
-
return releaseFailure("frontend.deploy_upload", "INVALID_RESPONSE", response.status);
|
|
16428
|
-
}
|
|
16429
|
-
return toolResponse(response.data);
|
|
16430
|
-
} finally {
|
|
16431
|
-
await archive.handle.close();
|
|
16432
|
-
}
|
|
16433
|
-
}
|
|
16434
|
-
async function uploadReadback(http, projectRef, deploymentId, releaseId, uploadStatus) {
|
|
16435
|
-
const response = await http.get(releasePath(projectRef, deploymentId, releaseId), {
|
|
16436
|
-
maxJsonBytes: RESPONSE_MAX_BYTES
|
|
16437
|
-
});
|
|
16438
|
-
const release = response.ok ? releaseEnvelope(response.data, { projectRef, deploymentId, releaseId }) : null;
|
|
16439
|
-
if (!release) {
|
|
16440
|
-
return releaseFailure("frontend.upload_release", "OUTCOME_UNKNOWN", uploadStatus);
|
|
16441
|
-
}
|
|
16442
|
-
return toolResponse({ project_ref: projectRef, deployment_id: deploymentId, release });
|
|
16443
|
-
}
|
|
16444
16400
|
function stableJson(candidate) {
|
|
16445
16401
|
if (candidate === null || typeof candidate !== "object")
|
|
16446
16402
|
return JSON.stringify(candidate);
|
|
@@ -16518,9 +16474,6 @@ async function activeReleaseReadback(http, identity) {
|
|
|
16518
16474
|
if (inventory.active_release_id !== identity.releaseId || inventory.active_activation_id !== identity.mutationId) {
|
|
16519
16475
|
return { release: null, status: 200 };
|
|
16520
16476
|
}
|
|
16521
|
-
if (snapshot.exactReadback) {
|
|
16522
|
-
return { release: inventory.releases[0] ?? null, status: 200 };
|
|
16523
|
-
}
|
|
16524
16477
|
const releaseRead = await http.get(releasePath(identity.projectRef, identity.deploymentId, identity.releaseId), { maxJsonBytes: RESPONSE_MAX_BYTES });
|
|
16525
16478
|
const release = releaseRead.ok ? releaseEnvelope(releaseRead.data, {
|
|
16526
16479
|
projectRef: identity.projectRef,
|
|
@@ -16538,43 +16491,31 @@ async function readActiveFrontendRelease(http, projectRef, deploymentId) {
|
|
|
16538
16491
|
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) {
|
|
16539
16492
|
return { failure: releaseReadFailure("frontend.get_active_release", response), status: response.status };
|
|
16540
16493
|
}
|
|
16541
|
-
return { inventory
|
|
16542
|
-
}
|
|
16543
|
-
if (response.ok || response.status !== 404) {
|
|
16544
|
-
return { failure: releaseReadFailure("frontend.get_active_release", response), status: response.status };
|
|
16494
|
+
return { inventory };
|
|
16545
16495
|
}
|
|
16546
|
-
|
|
16547
|
-
maxJsonBytes: RESPONSE_MAX_BYTES
|
|
16548
|
-
});
|
|
16549
|
-
const history = historyResponse.ok && historyResponse.status === 200 ? releaseInventory(historyResponse.data, { projectRef, deploymentId }) : null;
|
|
16550
|
-
if (!history || history.releases.length > 1) {
|
|
16496
|
+
if (response.status === 404) {
|
|
16551
16497
|
return {
|
|
16552
|
-
failure:
|
|
16553
|
-
|
|
16554
|
-
|
|
16555
|
-
|
|
16556
|
-
|
|
16557
|
-
|
|
16558
|
-
|
|
16559
|
-
|
|
16560
|
-
|
|
16561
|
-
|
|
16562
|
-
|
|
16563
|
-
|
|
16564
|
-
|
|
16565
|
-
failure: releaseReadFailure("frontend.get_active_release", exactResponse),
|
|
16566
|
-
status: exactResponse.status
|
|
16498
|
+
failure: {
|
|
16499
|
+
isError: true,
|
|
16500
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
16501
|
+
ok: false,
|
|
16502
|
+
operation: "frontend.get_active_release",
|
|
16503
|
+
error: {
|
|
16504
|
+
code: "DEPLOYMENT_UPGRADE_REQUIRED",
|
|
16505
|
+
http_status: 404,
|
|
16506
|
+
message: `${DEPLOYMENT_UPGRADE} Check that the deployment exists and upgrade the server if this endpoint is unavailable.`
|
|
16507
|
+
}
|
|
16508
|
+
}) }]
|
|
16509
|
+
},
|
|
16510
|
+
status: response.status
|
|
16567
16511
|
};
|
|
16568
16512
|
}
|
|
16569
|
-
return {
|
|
16513
|
+
return { failure: releaseReadFailure("frontend.get_active_release", response), status: response.status };
|
|
16570
16514
|
}
|
|
16571
16515
|
async function getActiveFrontendRelease(http, projectRef, deploymentId) {
|
|
16572
16516
|
const snapshot = await readActiveFrontendRelease(http, projectRef, deploymentId);
|
|
16573
16517
|
return "failure" in snapshot ? snapshot.failure : toolResponse(snapshot.inventory);
|
|
16574
16518
|
}
|
|
16575
|
-
async function readFrontendReleaseAuthority(http, projectRef, deploymentId) {
|
|
16576
|
-
return getActiveFrontendRelease(http, projectRef, deploymentId);
|
|
16577
|
-
}
|
|
16578
16519
|
async function rollbackFrontendRelease(http, projectRef, deploymentId, releaseId) {
|
|
16579
16520
|
if (releaseId !== undefined && !RELEASE_ID_PATTERN.test(releaseId)) {
|
|
16580
16521
|
throw new Error("'release_id' must be a SHA-256 digest");
|
|
@@ -16670,7 +16611,6 @@ async function readPreviousFrontendRelease(http, projectRef, deploymentId) {
|
|
|
16670
16611
|
releases: [],
|
|
16671
16612
|
next_cursor: null
|
|
16672
16613
|
},
|
|
16673
|
-
exactReadback: false,
|
|
16674
16614
|
...previous ? { previousRelease: previous } : {}
|
|
16675
16615
|
};
|
|
16676
16616
|
}
|
|
@@ -16740,16 +16680,13 @@ async function activationReadback(http, input, mutationId, activationStatus) {
|
|
|
16740
16680
|
// src/shared/tools/frontend-tools.ts
|
|
16741
16681
|
function registerFrontendTools(server, http) {
|
|
16742
16682
|
registerTool(server, "frontend", `Frontend hosting and immutable prebuilt releases. Supports: static, react, vue, svelte, sveltekit, sveltekit-static, nextjs, nuxt, astro.
|
|
16743
|
-
Actions: list, get, create, update, delete,
|
|
16683
|
+
Actions: list, get, create, update, delete, build_logs, add_domain, remove_domain, set_env, list_frameworks, list_records, list_releases, get_active_release, get_release, rollback. Publish with supacloud deploy.`, {
|
|
16744
16684
|
action: withDescription(stringEnum([
|
|
16745
16685
|
"list",
|
|
16746
16686
|
"get",
|
|
16747
16687
|
"create",
|
|
16748
16688
|
"update",
|
|
16749
16689
|
"delete",
|
|
16750
|
-
"deploy_git",
|
|
16751
|
-
"deploy_upload",
|
|
16752
|
-
"redeploy",
|
|
16753
16690
|
"build_logs",
|
|
16754
16691
|
"add_domain",
|
|
16755
16692
|
"remove_domain",
|
|
@@ -16759,8 +16696,6 @@ Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy,
|
|
|
16759
16696
|
"list_releases",
|
|
16760
16697
|
"get_active_release",
|
|
16761
16698
|
"get_release",
|
|
16762
|
-
"upload_release",
|
|
16763
|
-
"activate_release",
|
|
16764
16699
|
"rollback"
|
|
16765
16700
|
]), "Action"),
|
|
16766
16701
|
ref: optional(String2(), "Project ref"),
|
|
@@ -16774,13 +16709,7 @@ Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy,
|
|
|
16774
16709
|
node_version: optional(String2(), "[create/update] Node.js version"),
|
|
16775
16710
|
health_check_path: optional(String2(), "[create/update] SSR readiness path (default: /)"),
|
|
16776
16711
|
env_vars: optional(Record(String2(), String2()), "[create/update/set_env] Environment variables"),
|
|
16777
|
-
|
|
16778
|
-
branch: optional(String2(), "[deploy_git] Branch (default: main)"),
|
|
16779
|
-
archive_path: optional(String2(), "[deploy_upload/upload_release] Local tar.zst file path"),
|
|
16780
|
-
release_id: optional(String2(), "[get_release/activate_release] SHA-256 release ID; [rollback] optional, defaults to journal-verified previous release"),
|
|
16781
|
-
expected_active_release_id: optional(String2(), "[activate_release] Current release SHA-256 or absent"),
|
|
16782
|
-
expected_activation_id: optional(String2(), "[activate_release] Current activation UUIDv4 or absent"),
|
|
16783
|
-
mutation_id: optional(String2(), "[activate_release] Required retry-stable UUIDv4"),
|
|
16712
|
+
release_id: optional(String2(), "[get_release] SHA-256 release ID; [rollback] optional, defaults to journal-verified previous release"),
|
|
16784
16713
|
cursor: optional(String2(), "[list_releases] Last release SHA-256 cursor"),
|
|
16785
16714
|
limit: optional(Number2(), "[list_releases] Page size, 1-100 (default 50)")
|
|
16786
16715
|
}, async (args) => {
|
|
@@ -16797,13 +16726,7 @@ Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy,
|
|
|
16797
16726
|
node_version,
|
|
16798
16727
|
health_check_path,
|
|
16799
16728
|
env_vars,
|
|
16800
|
-
git_url,
|
|
16801
|
-
branch,
|
|
16802
|
-
archive_path,
|
|
16803
16729
|
release_id,
|
|
16804
|
-
expected_active_release_id,
|
|
16805
|
-
expected_activation_id,
|
|
16806
|
-
mutation_id,
|
|
16807
16730
|
cursor,
|
|
16808
16731
|
limit
|
|
16809
16732
|
} = args;
|
|
@@ -16858,22 +16781,6 @@ Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy,
|
|
|
16858
16781
|
need("id", id);
|
|
16859
16782
|
text = (await http.delete(`/v1/projects/${ref}/frontend/deployments/${id}`)).ok ? `✅ Deleted` : `❌ Failed`;
|
|
16860
16783
|
break;
|
|
16861
|
-
case "deploy_git":
|
|
16862
|
-
need("ref", ref);
|
|
16863
|
-
need("id", id);
|
|
16864
|
-
need("git_url", git_url);
|
|
16865
|
-
text = ok(await http.post(`/v1/projects/${ref}/frontend/deployments/${id}/deploy/git`, { git_url, branch }));
|
|
16866
|
-
break;
|
|
16867
|
-
case "deploy_upload":
|
|
16868
|
-
need("ref", ref);
|
|
16869
|
-
need("id", id);
|
|
16870
|
-
need("archive_path", archive_path);
|
|
16871
|
-
return uploadFrontendSource(http, ref, id, archive_path);
|
|
16872
|
-
case "redeploy":
|
|
16873
|
-
need("ref", ref);
|
|
16874
|
-
need("id", id);
|
|
16875
|
-
text = ok(await http.post(`/v1/projects/${ref}/frontend/deployments/${id}/redeploy`));
|
|
16876
|
-
break;
|
|
16877
16784
|
case "build_logs":
|
|
16878
16785
|
need("ref", ref);
|
|
16879
16786
|
need("id", id);
|
|
@@ -16919,26 +16826,6 @@ Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy,
|
|
|
16919
16826
|
need("ref", ref);
|
|
16920
16827
|
need("id", id);
|
|
16921
16828
|
return getActiveFrontendRelease(http, ref, id);
|
|
16922
|
-
case "upload_release":
|
|
16923
|
-
need("ref", ref);
|
|
16924
|
-
need("id", id);
|
|
16925
|
-
need("archive_path", archive_path);
|
|
16926
|
-
return uploadFrontendRelease(http, ref, id, archive_path);
|
|
16927
|
-
case "activate_release":
|
|
16928
|
-
need("ref", ref);
|
|
16929
|
-
need("id", id);
|
|
16930
|
-
need("release_id", release_id);
|
|
16931
|
-
need("expected_active_release_id", expected_active_release_id);
|
|
16932
|
-
need("expected_activation_id", expected_activation_id);
|
|
16933
|
-
need("mutation_id", mutation_id);
|
|
16934
|
-
return activateFrontendRelease(http, {
|
|
16935
|
-
projectRef: ref,
|
|
16936
|
-
deploymentId: id,
|
|
16937
|
-
releaseId: release_id,
|
|
16938
|
-
expectedActiveReleaseId: expected_active_release_id,
|
|
16939
|
-
expectedActivationId: expected_activation_id,
|
|
16940
|
-
mutationId: mutation_id
|
|
16941
|
-
});
|
|
16942
16829
|
case "rollback":
|
|
16943
16830
|
need("ref", ref);
|
|
16944
16831
|
need("id", id);
|
|
@@ -21230,7 +21117,7 @@ var starter_metadata_default = {
|
|
|
21230
21117
|
version: "0.8.1"
|
|
21231
21118
|
},
|
|
21232
21119
|
db: {
|
|
21233
|
-
version: "0.16.
|
|
21120
|
+
version: "0.16.2",
|
|
21234
21121
|
peerDependencies: {
|
|
21235
21122
|
"drizzle-orm": "1.0.0-rc.5-169397b"
|
|
21236
21123
|
},
|
|
@@ -28108,6 +27995,117 @@ import { spawn as spawn2 } from "node:child_process";
|
|
|
28108
27995
|
import { createHash as createHash9 } from "node:crypto";
|
|
28109
27996
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
28110
27997
|
import { basename as basename8, dirname as dirname11, join as join17, relative as relative10, resolve as resolve22, sep as sep5 } from "node:path";
|
|
27998
|
+
|
|
27999
|
+
// src/shared/tools/project-deployment.ts
|
|
28000
|
+
async function requireProjectDeploymentProtocol(http, projectRef, environmentId) {
|
|
28001
|
+
const response = await http.get(`/v1/projects/${encodeURIComponent(projectRef)}/environments/${encodeURIComponent(environmentId)}/deployments/protocol`, { retry: false, totalTimeoutMs: 5000, maxJsonBytes: 4096 });
|
|
28002
|
+
if (response.transportError || response.responseReadError || response.status >= 500 || response.status === 401 || response.status === 403 || response.status === 429) {
|
|
28003
|
+
throw new Error(`DEPLOYMENT_PROTOCOL_UNAVAILABLE: Cannot verify the selected server (HTTP ${response.status}). Check connectivity, credentials and server health; no upload or deployment was attempted.`);
|
|
28004
|
+
}
|
|
28005
|
+
const data = response.data;
|
|
28006
|
+
if (response.status !== 200 || !response.ok || !data || typeof data !== "object" || !("protocol" in data) || data.protocol !== "supacloud.project-deployment.v1" || !("project_ref" in data) || data.project_ref !== projectRef || !("environment_id" in data) || data.environment_id !== environmentId) {
|
|
28007
|
+
throw new Error("DEPLOYMENT_PROTOCOL_MISMATCH: Upgrade the SupaCloud server and CLI together (`npm install -g @supacloud/cli@latest`), then retry `supacloud deploy`. No upload or deployment was attempted; legacy endpoints will not be used.");
|
|
28008
|
+
}
|
|
28009
|
+
}
|
|
28010
|
+
var operation = "applications.project_deploy";
|
|
28011
|
+
var scopeSchema = _Object_({
|
|
28012
|
+
project_ref: String2({ pattern: "^[a-z0-9-]{1,20}$" }),
|
|
28013
|
+
application_id: ApplicationIdSchema,
|
|
28014
|
+
environment_id: ApplicationIdSchema
|
|
28015
|
+
});
|
|
28016
|
+
var statusSchema = _Object_({
|
|
28017
|
+
schema: Literal("supacloud.project-deployment-status.v1"),
|
|
28018
|
+
deployment_id: ApplicationDeployRequestSchema.properties.mutation_id,
|
|
28019
|
+
status: Union([
|
|
28020
|
+
Literal("pending"),
|
|
28021
|
+
Literal("running"),
|
|
28022
|
+
Literal("succeeded"),
|
|
28023
|
+
Literal("failed_retryable"),
|
|
28024
|
+
Literal("failed_terminal"),
|
|
28025
|
+
Literal("outcome_unknown")
|
|
28026
|
+
]),
|
|
28027
|
+
phase: Union([
|
|
28028
|
+
Literal("queued"),
|
|
28029
|
+
Literal("prepared"),
|
|
28030
|
+
Literal("migrating"),
|
|
28031
|
+
Literal("activating"),
|
|
28032
|
+
Literal("binding-web"),
|
|
28033
|
+
Literal("verifying")
|
|
28034
|
+
]),
|
|
28035
|
+
operation: Literal("application.release.deploy"),
|
|
28036
|
+
...scopeSchema.properties,
|
|
28037
|
+
receipt: Optional(Unknown()),
|
|
28038
|
+
failure_code: Optional(String2({ pattern: "^[A-Z][A-Z0-9_]{0,63}$" }))
|
|
28039
|
+
}, { additionalProperties: false });
|
|
28040
|
+
function responseReceipt(data, expected) {
|
|
28041
|
+
if (!data || typeof data !== "object" || Array.isArray(data) || !("ok" in data) || data.ok !== true) {
|
|
28042
|
+
throw new Error("Invalid project deployment response");
|
|
28043
|
+
}
|
|
28044
|
+
const { ok: _ok, ...receipt } = data;
|
|
28045
|
+
return parseApplicationDeployResult(receipt, expected);
|
|
28046
|
+
}
|
|
28047
|
+
async function runProjectDeployment(http, args) {
|
|
28048
|
+
const scope = { project_ref: args.ref, application_id: args.id, environment_id: args.environment_id };
|
|
28049
|
+
const request = {
|
|
28050
|
+
mutation_id: args.mutation_id,
|
|
28051
|
+
release_id: args.release_id,
|
|
28052
|
+
...args.configuration_id === undefined ? {} : { configuration_id: args.configuration_id },
|
|
28053
|
+
...args.configuration_write === undefined ? {} : { configuration_write: args.configuration_write },
|
|
28054
|
+
...args.expected_plan_sha256 === undefined ? {} : { expected_plan_sha256: args.expected_plan_sha256 },
|
|
28055
|
+
...args.approved_migration_digest === undefined ? {} : { approved_migration_digest: args.approved_migration_digest },
|
|
28056
|
+
...args.web_release === undefined ? {} : { web_release: args.web_release }
|
|
28057
|
+
};
|
|
28058
|
+
if (!Check2(scopeSchema, scope) || !Check2(ApplicationDeployRequestSchema, request) || request.configuration_id !== undefined && request.configuration_write !== undefined && request.configuration_id !== request.configuration_write.configuration_id) {
|
|
28059
|
+
return releaseControlFailure(operation, "INVALID_RESPONSE", null);
|
|
28060
|
+
}
|
|
28061
|
+
const expected = { ...scope, request };
|
|
28062
|
+
const identity = {
|
|
28063
|
+
...scope,
|
|
28064
|
+
deployment_id: request.mutation_id,
|
|
28065
|
+
mutation_id: request.mutation_id,
|
|
28066
|
+
release_id: request.release_id
|
|
28067
|
+
};
|
|
28068
|
+
const endpoint = `/v1/projects/${encodeURIComponent(scope.project_ref)}/environments/${encodeURIComponent(scope.environment_id)}/deployments`;
|
|
28069
|
+
const { mutation_id, ...candidate } = request;
|
|
28070
|
+
const result = await http.post(endpoint, {
|
|
28071
|
+
...candidate,
|
|
28072
|
+
deployment_id: mutation_id,
|
|
28073
|
+
application_id: scope.application_id
|
|
28074
|
+
}, { timeoutMs: 120000, maxJsonBytes: 131072, responseTimeoutMs: 30000 });
|
|
28075
|
+
if (result.ok && result.status === 200) {
|
|
28076
|
+
try {
|
|
28077
|
+
return releaseControlSuccess(operation, {
|
|
28078
|
+
...responseReceipt(result.data, expected),
|
|
28079
|
+
deployment_id: mutation_id
|
|
28080
|
+
});
|
|
28081
|
+
} catch {}
|
|
28082
|
+
} else if (!result.ok && !result.transportError && !result.responseReadError && result.status < 500 && result.status !== 408) {
|
|
28083
|
+
return releaseControlMutationFailure(operation, result, identity);
|
|
28084
|
+
}
|
|
28085
|
+
const observed = await http.get(`${endpoint}/${encodeURIComponent(mutation_id)}`, {
|
|
28086
|
+
retry: false,
|
|
28087
|
+
totalTimeoutMs: 30000,
|
|
28088
|
+
maxJsonBytes: 131072
|
|
28089
|
+
});
|
|
28090
|
+
try {
|
|
28091
|
+
if (!observed.ok || observed.status !== 200 || !Check2(statusSchema, observed.data))
|
|
28092
|
+
throw new Error;
|
|
28093
|
+
const state = observed.data;
|
|
28094
|
+
if (state.deployment_id !== mutation_id || state.project_ref !== scope.project_ref || state.application_id !== scope.application_id || state.environment_id !== scope.environment_id || state.status !== "succeeded" || state.phase !== "verifying")
|
|
28095
|
+
throw new Error;
|
|
28096
|
+
const receipt = parseApplicationDeployResult(state.receipt, expected);
|
|
28097
|
+
return releaseControlSuccess(operation, {
|
|
28098
|
+
...receipt,
|
|
28099
|
+
deployment_id: mutation_id,
|
|
28100
|
+
replayed: true,
|
|
28101
|
+
execution_performed: false
|
|
28102
|
+
});
|
|
28103
|
+
} catch {
|
|
28104
|
+
return releaseControlFailure(operation, "OUTCOME_UNKNOWN", result.transportError ? null : result.status, identity);
|
|
28105
|
+
}
|
|
28106
|
+
}
|
|
28107
|
+
|
|
28108
|
+
// src/shared/tools/deploy-tools.ts
|
|
28111
28109
|
var MAX_SOURCE_BYTES = 256 * 1024 * 1024;
|
|
28112
28110
|
var PUBLIC_BUILD_ENV_PREFIXES = ["PUBLIC_", "VITE_", "NEXT_PUBLIC_", "NUXT_PUBLIC_", "ASTRO_PUBLIC_"];
|
|
28113
28111
|
var SENSITIVE_BUILD_ENV = /(?:^|_)(?:PASSWORD|PASS|SECRET|TOKEN|KEY|CREDENTIAL|AUTHORIZATION|COOKIE|SESSION)(?:_|$)|(?:DATABASE_URL|DATABASE_URI|CONNECTION_STRING|DSN|PRIVATE_KEY)/i;
|
|
@@ -28119,15 +28117,8 @@ function isProtectedBuildEnv(name) {
|
|
|
28119
28117
|
}
|
|
28120
28118
|
var deployToolSchema = {
|
|
28121
28119
|
ref: optional(String2(), "Project ref (defaults to the linked project)"),
|
|
28122
|
-
target: optional(String2(), "Named deploy target from supacloud.json"),
|
|
28123
|
-
id: optional(String2(), "Frontend deployment ID (auto-selected when unambiguous)"),
|
|
28124
28120
|
cwd: optional(String2(), "Project directory (default: current directory)"),
|
|
28125
|
-
build_command: optional(String2(), "Build command override"),
|
|
28126
|
-
output_dir: optional(String2(), "Build output directory override"),
|
|
28127
28121
|
skip_build: optional(Boolean2(), "Use an existing output directory without building"),
|
|
28128
|
-
dry_run: optional(Boolean2(), "Resolve and validate the deployment without building or publishing"),
|
|
28129
|
-
plan: optional(Boolean2(), "Build and compare the release with current authority without publishing"),
|
|
28130
|
-
diff: optional(Boolean2(), "Build and report the immutable release-level diff without publishing"),
|
|
28131
28122
|
json: optional(Boolean2(), "Print only the final JSON result")
|
|
28132
28123
|
};
|
|
28133
28124
|
function record4(candidate) {
|
|
@@ -28386,40 +28377,6 @@ function targetRoot(configRoot, target, name) {
|
|
|
28386
28377
|
}
|
|
28387
28378
|
return root;
|
|
28388
28379
|
}
|
|
28389
|
-
function selectTarget(configRoot, invocationDirectory, config, requestedName) {
|
|
28390
|
-
const targets = config.targets ? Object.entries(config.targets) : [["frontend", {
|
|
28391
|
-
type: "frontend",
|
|
28392
|
-
root: config.frontend?.root,
|
|
28393
|
-
id: config.frontend?.id,
|
|
28394
|
-
buildCommand: config.frontend?.buildCommand,
|
|
28395
|
-
outputDirectory: config.frontend?.outputDirectory,
|
|
28396
|
-
buildEnv: config.frontend?.buildEnv
|
|
28397
|
-
}]];
|
|
28398
|
-
if (targets.length === 0)
|
|
28399
|
-
throw new Error("supacloud.json defines no deploy targets");
|
|
28400
|
-
if (requestedName) {
|
|
28401
|
-
const selected = targets.find(([name]) => name === requestedName);
|
|
28402
|
-
if (!selected)
|
|
28403
|
-
throw new Error(`Deploy target '${requestedName}' was not found in supacloud.json`);
|
|
28404
|
-
return { name: selected[0], config: selected[1], root: targetRoot(configRoot, selected[1], selected[0]) };
|
|
28405
|
-
}
|
|
28406
|
-
const containing = targets.map(([name, target]) => ({ name, target, root: targetRoot(configRoot, target, name) })).filter((candidate) => invocationDirectory === candidate.root || invocationDirectory.startsWith(`${candidate.root}${sep5}`)).sort((left, right) => right.root.length - left.root.length);
|
|
28407
|
-
if (containing.length === 1 || containing.length > 1 && containing[0].root !== containing[1].root) {
|
|
28408
|
-
const selected = containing[0];
|
|
28409
|
-
return { name: selected.name, config: selected.target, root: selected.root };
|
|
28410
|
-
}
|
|
28411
|
-
if (config.defaultTarget) {
|
|
28412
|
-
const selected = targets.find(([name]) => name === config.defaultTarget);
|
|
28413
|
-
if (!selected)
|
|
28414
|
-
throw new Error(`supacloud.json defaultTarget '${config.defaultTarget}' was not found`);
|
|
28415
|
-
return { name: selected[0], config: selected[1], root: targetRoot(configRoot, selected[1], selected[0]) };
|
|
28416
|
-
}
|
|
28417
|
-
if (targets.length === 1) {
|
|
28418
|
-
const selected = targets[0];
|
|
28419
|
-
return { name: selected[0], config: selected[1], root: targetRoot(configRoot, selected[1], selected[0]) };
|
|
28420
|
-
}
|
|
28421
|
-
throw new Error(`Multiple deploy targets found (${targets.map(([name]) => name).sort().join(", ")}). Pass --target`);
|
|
28422
|
-
}
|
|
28423
28380
|
function defaultOutputDirectory(framework) {
|
|
28424
28381
|
switch (framework) {
|
|
28425
28382
|
case "sveltekit-static":
|
|
@@ -28599,24 +28556,8 @@ async function stageApplicationWebRelease(http, projectRef, environmentId, confi
|
|
|
28599
28556
|
await archive.cleanup();
|
|
28600
28557
|
}
|
|
28601
28558
|
}
|
|
28602
|
-
function deployResponse(result, json) {
|
|
28603
|
-
if (json)
|
|
28604
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
28605
|
-
const lines = [
|
|
28606
|
-
result.unchanged === true ? "Already up to date." : `Deployed ${String(result.target || result.deployment_id || result.slug)}.`,
|
|
28607
|
-
result.url ? `URL: ${String(result.url)}` : null,
|
|
28608
|
-
result.release_id ? `Release: ${String(result.release_id)}` : null,
|
|
28609
|
-
result.active_version ? `Active version: ${String(result.active_version)}` : null,
|
|
28610
|
-
result.file_count ? `Files: ${String(result.file_count)}` : null
|
|
28611
|
-
].filter((line) => Boolean(line));
|
|
28612
|
-
return { content: [{ type: "text", text: lines.join(`
|
|
28613
|
-
`) }] };
|
|
28614
|
-
}
|
|
28615
28559
|
function registerDeployTools(server, http, options = {}) {
|
|
28616
28560
|
registerTool(server, "deploy", "Build and publish the linked project with one command", deployToolSchema, async (args) => {
|
|
28617
|
-
if (args.dry_run === true && (args.plan === true || args.diff === true)) {
|
|
28618
|
-
throw new Error("Use dry_run for resolution only, or plan/diff for a built release comparison, not both");
|
|
28619
|
-
}
|
|
28620
28561
|
const invocationDirectory = resolve22(String(args.cwd || options.cwd || process.cwd()));
|
|
28621
28562
|
const projectRef = String(args.ref || options.projectRef || "").trim();
|
|
28622
28563
|
if (!projectRef)
|
|
@@ -28627,8 +28568,8 @@ function registerDeployTools(server, http, options = {}) {
|
|
|
28627
28568
|
...config.application,
|
|
28628
28569
|
environmentId: options.environment || config.application.environmentId
|
|
28629
28570
|
} : undefined;
|
|
28630
|
-
if (
|
|
28631
|
-
throw new Error(
|
|
28571
|
+
if (await hasProjectReleaseManifest(configRoot)) {
|
|
28572
|
+
throw new Error(`${DEPLOYMENT_UPGRADE} Replace deploy-manifest.json with the current application delivery configuration; no remote mutation was performed.`);
|
|
28632
28573
|
}
|
|
28633
28574
|
if (application?.id) {
|
|
28634
28575
|
if (Object.values(config.targets ?? {}).some((target) => target.type === "edge_function")) {
|
|
@@ -28637,12 +28578,6 @@ function registerDeployTools(server, http, options = {}) {
|
|
|
28637
28578
|
if (!options.applicationRelease) {
|
|
28638
28579
|
throw new Error("Application release support is unavailable in this CLI context");
|
|
28639
28580
|
}
|
|
28640
|
-
if (args.target || args.build_command || args.output_dir) {
|
|
28641
|
-
throw new Error("Application profile deploy does not accept frontend target, build command, or output directory flags");
|
|
28642
|
-
}
|
|
28643
|
-
if (args.dry_run === true || args.plan === true || args.diff === true) {
|
|
28644
|
-
throw new Error("Application profile deploy does not support dry_run, plan, or diff; use app deploy-plan for a read-only plan");
|
|
28645
|
-
}
|
|
28646
28581
|
const environmentId = application.environmentId?.trim();
|
|
28647
28582
|
if (!environmentId || !Check2(ApplicationIdSchema, environmentId) || !Check2(ApplicationIdSchema, application.id)) {
|
|
28648
28583
|
throw new Error("Application profile requires SUPACLOUD_APPLICATION_ENVIRONMENT_ID or SUPACLOUD_ENV");
|
|
@@ -28652,6 +28587,7 @@ function registerDeployTools(server, http, options = {}) {
|
|
|
28652
28587
|
if (configurationPath && configurationId) {
|
|
28653
28588
|
throw new Error("Application profile must set only one of SUPACLOUD_APPLICATION_CONFIGURATION_PATH or SUPACLOUD_APPLICATION_CONFIGURATION_ID");
|
|
28654
28589
|
}
|
|
28590
|
+
await requireProjectDeploymentProtocol(http, projectRef, environmentId);
|
|
28655
28591
|
const stagedWeb = await stageApplicationWebRelease(http, projectRef, environmentId, configRoot, args.skip_build === true, args.json === true, options.environmentVariables);
|
|
28656
28592
|
const result = await options.applicationRelease({
|
|
28657
28593
|
action: "release",
|
|
@@ -28668,215 +28604,7 @@ function registerDeployTools(server, http, options = {}) {
|
|
|
28668
28604
|
});
|
|
28669
28605
|
return result;
|
|
28670
28606
|
}
|
|
28671
|
-
|
|
28672
|
-
if (!args.target && target.config.type === "frontend" && (Object.values(config.targets ?? {}).some((candidate) => candidate.type === "edge_function") || await pathExists(join17(configRoot, "supacloud.config.ts")) || await pathExists(join17(configRoot, "supabase", "migrations")) || await pathExists(join17(configRoot, "supabase", "functions")))) {
|
|
28673
|
-
throw new Error("PROJECT_DEPLOYMENT_UNSUPPORTED: Backend declarations were detected but are not covered by this deployment. No Web-only fallback or remote mutation was performed.");
|
|
28674
|
-
}
|
|
28675
|
-
const projectDirectory = target.root;
|
|
28676
|
-
const targetState = await lstat5(projectDirectory).catch(() => null);
|
|
28677
|
-
if (!targetState?.isDirectory() || targetState.isSymbolicLink()) {
|
|
28678
|
-
throw new Error(`Deploy target '${target.name}' root is not a regular directory: ${projectDirectory}`);
|
|
28679
|
-
}
|
|
28680
|
-
const requestedId = String(args.id || target.config.id || "").trim() || undefined;
|
|
28681
|
-
const build = createBuildEnvironment(target.config.buildEnv, options.environmentVariables);
|
|
28682
|
-
if (target.config.type === "edge_function") {
|
|
28683
|
-
if (args.diff === true)
|
|
28684
|
-
throw new Error("Function release diffs require an immutable function release manifest");
|
|
28685
|
-
if (!options.edgeFunctionDeploy)
|
|
28686
|
-
throw new Error("Edge Function deploy support is unavailable in this CLI context");
|
|
28687
|
-
const slug = target.config.slug || requestedId || target.name;
|
|
28688
|
-
const functionListResponse = await options.edgeFunctionDeploy({
|
|
28689
|
-
action: "list",
|
|
28690
|
-
ref: projectRef
|
|
28691
|
-
});
|
|
28692
|
-
if (functionListResponse.isError) {
|
|
28693
|
-
throw new Error(`Unable to read the current identity for Edge Function '${slug}'`);
|
|
28694
|
-
}
|
|
28695
|
-
const functionListText = functionListResponse.content.find((chunk) => chunk.type === "text")?.text;
|
|
28696
|
-
if (!functionListText)
|
|
28697
|
-
throw new Error(`Unable to read the current identity for Edge Function '${slug}'`);
|
|
28698
|
-
const functions = projectedFunctionList(JSON.parse(functionListText));
|
|
28699
|
-
if (!functions)
|
|
28700
|
-
throw new Error(`Invalid current identity for Edge Function '${slug}'`);
|
|
28701
|
-
const current = functions?.find((candidate) => candidate.slug === slug);
|
|
28702
|
-
const activeVersion = current && typeof current.version === "number" ? String(current.version) : "absent";
|
|
28703
|
-
const activationId = current && typeof current.activation_id === "string" ? current.activation_id : "legacy";
|
|
28704
|
-
if (!activeVersion || !activationId)
|
|
28705
|
-
throw new Error(`Edge Function '${slug}' has an invalid active identity`);
|
|
28706
|
-
const buildCommand = String(args.build_command || target.config.buildCommand || "").trim() || (args.skip_build === true ? "" : await defaultBuildCommand(projectDirectory, configRoot));
|
|
28707
|
-
const bundleDirectory = resolveInsideProject(projectDirectory, String(args.output_dir || target.config.bundleDirectory || target.config.outputDirectory || "dist"), true);
|
|
28708
|
-
if (args.dry_run === true || args.plan === true) {
|
|
28709
|
-
return { content: [{ type: "text", text: JSON.stringify({
|
|
28710
|
-
ok: true,
|
|
28711
|
-
dry_run: true,
|
|
28712
|
-
type: target.config.type,
|
|
28713
|
-
target: target.name,
|
|
28714
|
-
project_ref: projectRef,
|
|
28715
|
-
slug,
|
|
28716
|
-
build_command: args.skip_build === true ? null : buildCommand,
|
|
28717
|
-
build_env: build.names,
|
|
28718
|
-
missing_build_env: build.missing,
|
|
28719
|
-
bundle_directory: bundleDirectory,
|
|
28720
|
-
expected_active_version: activeVersion,
|
|
28721
|
-
expected_activation_id: activationId
|
|
28722
|
-
}, null, 2) }] };
|
|
28723
|
-
}
|
|
28724
|
-
assertBuildEnvironment(build, args.skip_build === true);
|
|
28725
|
-
if (args.skip_build !== true) {
|
|
28726
|
-
await runBuild(buildCommand, projectDirectory, build.env, args.json === true);
|
|
28727
|
-
}
|
|
28728
|
-
const deployed = await options.edgeFunctionDeploy({
|
|
28729
|
-
action: "deploy_bundle",
|
|
28730
|
-
ref: projectRef,
|
|
28731
|
-
slug,
|
|
28732
|
-
"bundle-dir": bundleDirectory,
|
|
28733
|
-
entrypoint: target.config.entrypoint || "index.ts",
|
|
28734
|
-
minify: target.config.minify,
|
|
28735
|
-
verify_jwt: target.config.verifyJwt,
|
|
28736
|
-
framework: target.config.framework,
|
|
28737
|
-
"expected-active-version": activeVersion,
|
|
28738
|
-
"expected-activation-id": activationId
|
|
28739
|
-
});
|
|
28740
|
-
if (deployed.isError)
|
|
28741
|
-
return deployed;
|
|
28742
|
-
const deployedText = deployed.content.find((chunk) => chunk.type === "text")?.text;
|
|
28743
|
-
const deployedPayload = deployedText ? record4(JSON.parse(deployedText)) : null;
|
|
28744
|
-
return deployResponse({
|
|
28745
|
-
ok: true,
|
|
28746
|
-
unchanged: false,
|
|
28747
|
-
type: target.config.type,
|
|
28748
|
-
target: target.name,
|
|
28749
|
-
project_ref: projectRef,
|
|
28750
|
-
slug,
|
|
28751
|
-
active_version: deployedPayload?.active_version,
|
|
28752
|
-
activation_id: deployedPayload?.activation_id
|
|
28753
|
-
}, args.json === true);
|
|
28754
|
-
}
|
|
28755
|
-
const listResponse = await http.get(`/v1/projects/${encodeURIComponent(projectRef)}/frontend/deployments`);
|
|
28756
|
-
if (!listResponse.ok)
|
|
28757
|
-
throw new Error(`Failed to list frontend deployments (HTTP ${listResponse.status})`);
|
|
28758
|
-
const selected = selectDeployment(deploymentList(listResponse.data), requestedId, await packageName(projectDirectory));
|
|
28759
|
-
const configuredBuildCommand = String(args.build_command || target.config.buildCommand || selected.buildCommand || "").trim();
|
|
28760
|
-
const buildCommand = configuredBuildCommand || (args.skip_build === true ? "" : await defaultBuildCommand(projectDirectory, configRoot));
|
|
28761
|
-
const configuredOutput = String(args.output_dir || target.config.outputDirectory || "").trim();
|
|
28762
|
-
const remoteOutput = selected.outputDirectory && selected.outputDirectory !== "." ? selected.outputDirectory : "";
|
|
28763
|
-
const outputDirectory = resolveInsideProject(projectDirectory, configuredOutput || remoteOutput || defaultOutputDirectory(selected.framework));
|
|
28764
|
-
if (args.dry_run === true) {
|
|
28765
|
-
return { content: [{ type: "text", text: JSON.stringify({
|
|
28766
|
-
ok: true,
|
|
28767
|
-
dry_run: true,
|
|
28768
|
-
project_ref: projectRef,
|
|
28769
|
-
deployment_id: selected.id,
|
|
28770
|
-
build_command: args.skip_build === true ? null : buildCommand,
|
|
28771
|
-
build_env: build.names,
|
|
28772
|
-
missing_build_env: build.missing,
|
|
28773
|
-
output_directory: outputDirectory
|
|
28774
|
-
}, null, 2) }] };
|
|
28775
|
-
}
|
|
28776
|
-
assertBuildEnvironment(build, args.skip_build === true);
|
|
28777
|
-
if (args.skip_build !== true) {
|
|
28778
|
-
await runBuild(buildCommand, projectDirectory, build.env, args.json === true);
|
|
28779
|
-
}
|
|
28780
|
-
const archive = await createFrontendArchive(outputDirectory);
|
|
28781
|
-
try {
|
|
28782
|
-
const inventory = payload(await readFrontendReleaseAuthority(http, projectRef, selected.id));
|
|
28783
|
-
const activeReleaseId = typeof inventory.active_release_id === "string" ? inventory.active_release_id : null;
|
|
28784
|
-
const activeActivationId = typeof inventory.active_activation_id === "string" ? inventory.active_activation_id : null;
|
|
28785
|
-
const current = Array.isArray(inventory.releases) ? record4(inventory.releases[0]) : null;
|
|
28786
|
-
if (archive.sha256 === activeReleaseId && archive.treeSha256 !== current?.tree_sha256) {
|
|
28787
|
-
throw new Error("Active release tree does not match the local archive");
|
|
28788
|
-
}
|
|
28789
|
-
const unchanged = archive.treeSha256 === current?.tree_sha256;
|
|
28790
|
-
if (args.plan === true || args.diff === true) {
|
|
28791
|
-
return { content: [{ type: "text", text: JSON.stringify({
|
|
28792
|
-
schema: "supacloud.frontend-deploy-plan.v1",
|
|
28793
|
-
ok: true,
|
|
28794
|
-
planned: true,
|
|
28795
|
-
published: false,
|
|
28796
|
-
unchanged,
|
|
28797
|
-
environment: options.environment ?? null,
|
|
28798
|
-
type: "frontend",
|
|
28799
|
-
target: target.name,
|
|
28800
|
-
project_ref: projectRef,
|
|
28801
|
-
deployment_id: selected.id,
|
|
28802
|
-
comparison: "immutable_release",
|
|
28803
|
-
archive_format: FRONTEND_ARCHIVE_FORMAT,
|
|
28804
|
-
release_id: archive.sha256,
|
|
28805
|
-
tree_sha256: archive.treeSha256,
|
|
28806
|
-
active_release_id: activeReleaseId,
|
|
28807
|
-
active_tree_sha256: current?.tree_sha256 ?? null,
|
|
28808
|
-
expected_active_release_id: activeReleaseId ?? "absent",
|
|
28809
|
-
expected_activation_id: activeActivationId ?? "absent",
|
|
28810
|
-
actions: unchanged ? [] : ["upload_release", "activate_release"],
|
|
28811
|
-
file_count: archive.fileCount,
|
|
28812
|
-
source_bytes: archive.sourceBytes,
|
|
28813
|
-
archive_bytes: archive.archiveBytes
|
|
28814
|
-
}, null, 2) }] };
|
|
28815
|
-
}
|
|
28816
|
-
if (unchanged) {
|
|
28817
|
-
return deployResponse({
|
|
28818
|
-
ok: true,
|
|
28819
|
-
unchanged: true,
|
|
28820
|
-
project_ref: projectRef,
|
|
28821
|
-
deployment_id: selected.id,
|
|
28822
|
-
release_id: activeReleaseId,
|
|
28823
|
-
tree_sha256: archive.treeSha256,
|
|
28824
|
-
url: selected.deploymentUrl,
|
|
28825
|
-
file_count: archive.fileCount,
|
|
28826
|
-
source_bytes: archive.sourceBytes
|
|
28827
|
-
}, args.json === true);
|
|
28828
|
-
}
|
|
28829
|
-
const uploaded = payload(await uploadFrontendRelease(http, projectRef, selected.id, archive.archivePath));
|
|
28830
|
-
const release = record4(uploaded.release);
|
|
28831
|
-
const releaseId = requiredString(release?.release_id, "release.release_id");
|
|
28832
|
-
if (releaseId !== archive.sha256)
|
|
28833
|
-
throw new Error("Uploaded release identity does not match the local archive");
|
|
28834
|
-
if (release?.tree_sha256 !== archive.treeSha256)
|
|
28835
|
-
throw new Error("Uploaded release tree does not match the local build");
|
|
28836
|
-
const mutationId = crypto.randomUUID();
|
|
28837
|
-
const activationResponse = await activateFrontendRelease(http, {
|
|
28838
|
-
projectRef,
|
|
28839
|
-
deploymentId: selected.id,
|
|
28840
|
-
releaseId,
|
|
28841
|
-
expectedActiveReleaseId: activeReleaseId || "absent",
|
|
28842
|
-
expectedActivationId: activeActivationId || "absent",
|
|
28843
|
-
mutationId
|
|
28844
|
-
});
|
|
28845
|
-
if (activationResponse.isError) {
|
|
28846
|
-
const failure = record4(JSON.parse(activationResponse.content[0]?.text ?? "{}"));
|
|
28847
|
-
return {
|
|
28848
|
-
isError: true,
|
|
28849
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
28850
|
-
...failure,
|
|
28851
|
-
operation: "deploy",
|
|
28852
|
-
project_ref: projectRef,
|
|
28853
|
-
deployment_id: selected.id,
|
|
28854
|
-
mutation_id: mutationId,
|
|
28855
|
-
target_release_id: releaseId,
|
|
28856
|
-
expected_active_release_id: activeReleaseId ?? "absent",
|
|
28857
|
-
expected_activation_id: activeActivationId ?? "absent"
|
|
28858
|
-
}) }]
|
|
28859
|
-
};
|
|
28860
|
-
}
|
|
28861
|
-
const activated = payload(activationResponse);
|
|
28862
|
-
const finalResponse = await http.get(`/v1/projects/${encodeURIComponent(projectRef)}/frontend/deployments/${encodeURIComponent(selected.id)}`);
|
|
28863
|
-
const finalDeployment = finalResponse.ok ? frontendDeployment(finalResponse.data) : selected;
|
|
28864
|
-
return deployResponse({
|
|
28865
|
-
ok: true,
|
|
28866
|
-
unchanged: false,
|
|
28867
|
-
project_ref: projectRef,
|
|
28868
|
-
deployment_id: selected.id,
|
|
28869
|
-
release_id: activated.active_release_id,
|
|
28870
|
-
activation_id: activated.activation_id,
|
|
28871
|
-
previous_release_id: activeReleaseId,
|
|
28872
|
-
previous_activation_id: activeActivationId,
|
|
28873
|
-
url: finalDeployment.deploymentUrl,
|
|
28874
|
-
file_count: archive.fileCount,
|
|
28875
|
-
source_bytes: archive.sourceBytes
|
|
28876
|
-
}, args.json === true);
|
|
28877
|
-
} finally {
|
|
28878
|
-
await archive.cleanup();
|
|
28879
|
-
}
|
|
28607
|
+
throw new Error(DEPLOYMENT_UPGRADE);
|
|
28880
28608
|
});
|
|
28881
28609
|
}
|
|
28882
28610
|
|
|
@@ -29168,6 +28896,7 @@ var TEST_PROFILE = [
|
|
|
29168
28896
|
"SUPACLOUD_API_URL=https://management.test.example.com",
|
|
29169
28897
|
"SUPACLOUD_API_TOKEN=<test-management-api-token>",
|
|
29170
28898
|
"SUPACLOUD_PROJECT_REF=<test-project-ref>",
|
|
28899
|
+
"SUPACLOUD_APPLICATION_ID=<application-id>",
|
|
29171
28900
|
""
|
|
29172
28901
|
].join(`
|
|
29173
28902
|
`);
|
|
@@ -29176,6 +28905,7 @@ var PRODUCTION_PROFILE = [
|
|
|
29176
28905
|
"SUPACLOUD_API_URL=https://management.example.com",
|
|
29177
28906
|
"SUPACLOUD_API_TOKEN=<production-management-api-token>",
|
|
29178
28907
|
"SUPACLOUD_PROJECT_REF=<production-project-ref>",
|
|
28908
|
+
"SUPACLOUD_APPLICATION_ID=<application-id>",
|
|
29179
28909
|
""
|
|
29180
28910
|
].join(`
|
|
29181
28911
|
`);
|
|
@@ -29299,6 +29029,7 @@ async function initializeProject(request, options) {
|
|
|
29299
29029
|
},
|
|
29300
29030
|
next: [
|
|
29301
29031
|
"cp .env.supacloud.test.example .env.supacloud.test",
|
|
29032
|
+
"Set SUPACLOUD_APPLICATION_ID to the application's ID and configure its current SupaCloud delivery build.",
|
|
29302
29033
|
"supacloud env check",
|
|
29303
29034
|
"supacloud --env test deploy"
|
|
29304
29035
|
]
|
|
@@ -29346,17 +29077,16 @@ function statusMissing(context) {
|
|
|
29346
29077
|
}
|
|
29347
29078
|
return ["SUPACLOUD_API_URL", "SUPACLOUD_API_TOKEN", "SUPACLOUD_PROJECT_REF"];
|
|
29348
29079
|
}
|
|
29349
|
-
function deployMissing(context) {
|
|
29080
|
+
function deployMissing(context, application) {
|
|
29350
29081
|
const missing = statusMissing(context);
|
|
29351
29082
|
if (context.credentialScope !== "management") {
|
|
29352
29083
|
return [...missing, "Management API context"];
|
|
29353
29084
|
}
|
|
29354
|
-
if (context.applicationId) {
|
|
29355
|
-
|
|
29356
|
-
|
|
29357
|
-
|
|
29358
|
-
|
|
29359
|
-
}
|
|
29085
|
+
if (!Check2(ApplicationIdSchema, context.applicationId || application?.id)) {
|
|
29086
|
+
missing.push("valid SUPACLOUD_APPLICATION_ID or supacloud.json application.id");
|
|
29087
|
+
}
|
|
29088
|
+
if (!Check2(ApplicationIdSchema, context.applicationEnvironmentId || application?.environmentId)) {
|
|
29089
|
+
missing.push("valid SUPACLOUD_APPLICATION_ENVIRONMENT_ID or SUPACLOUD_ENV");
|
|
29360
29090
|
}
|
|
29361
29091
|
return missing;
|
|
29362
29092
|
}
|
|
@@ -29366,7 +29096,13 @@ async function inspectProfile(profile) {
|
|
|
29366
29096
|
environmentName: profile.name
|
|
29367
29097
|
});
|
|
29368
29098
|
const status = statusMissing(context);
|
|
29369
|
-
const
|
|
29099
|
+
const configText = await readFile8(join19(dirname12(profile.path), "supacloud.json"), "utf8").catch((error) => {
|
|
29100
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
29101
|
+
return "{}";
|
|
29102
|
+
throw error;
|
|
29103
|
+
});
|
|
29104
|
+
const config = record5(JSON.parse(configText));
|
|
29105
|
+
const deploy = deployMissing(context, record5(config?.application));
|
|
29370
29106
|
return {
|
|
29371
29107
|
name: profile.name,
|
|
29372
29108
|
path: profile.path,
|
|
@@ -29459,109 +29195,10 @@ function registerDeveloperExperienceTools(server, options = {}) {
|
|
|
29459
29195
|
registerTool(server, "init", "Create SupaCloud deployment configuration and non-secret environment examples without overwriting project files.", initToolSchema, (request) => initializeProject(request, options));
|
|
29460
29196
|
registerTool(server, "env", "List or locally validate named SupaCloud environment profiles without contacting the platform or printing secrets.", environmentToolSchema, (request) => inspectEnvironment(request, options));
|
|
29461
29197
|
}
|
|
29462
|
-
|
|
29463
|
-
// src/shared/tools/project-deployment.ts
|
|
29464
|
-
var operation = "applications.project_deploy";
|
|
29465
|
-
var scopeSchema = _Object_({
|
|
29466
|
-
project_ref: String2({ pattern: "^[a-z0-9-]{1,20}$" }),
|
|
29467
|
-
application_id: ApplicationIdSchema,
|
|
29468
|
-
environment_id: ApplicationIdSchema
|
|
29469
|
-
});
|
|
29470
|
-
var statusSchema = _Object_({
|
|
29471
|
-
schema: Literal("supacloud.project-deployment-status.v1"),
|
|
29472
|
-
deployment_id: ApplicationDeployRequestSchema.properties.mutation_id,
|
|
29473
|
-
status: Union([
|
|
29474
|
-
Literal("pending"),
|
|
29475
|
-
Literal("running"),
|
|
29476
|
-
Literal("succeeded"),
|
|
29477
|
-
Literal("failed_retryable"),
|
|
29478
|
-
Literal("failed_terminal"),
|
|
29479
|
-
Literal("outcome_unknown")
|
|
29480
|
-
]),
|
|
29481
|
-
phase: Union([
|
|
29482
|
-
Literal("queued"),
|
|
29483
|
-
Literal("prepared"),
|
|
29484
|
-
Literal("migrating"),
|
|
29485
|
-
Literal("activating"),
|
|
29486
|
-
Literal("binding-web"),
|
|
29487
|
-
Literal("verifying")
|
|
29488
|
-
]),
|
|
29489
|
-
operation: Literal("application.release.deploy"),
|
|
29490
|
-
...scopeSchema.properties,
|
|
29491
|
-
receipt: Optional(Unknown()),
|
|
29492
|
-
failure_code: Optional(String2({ pattern: "^[A-Z][A-Z0-9_]{0,63}$" }))
|
|
29493
|
-
}, { additionalProperties: false });
|
|
29494
|
-
function responseReceipt(data, expected) {
|
|
29495
|
-
if (!data || typeof data !== "object" || Array.isArray(data) || !("ok" in data) || data.ok !== true) {
|
|
29496
|
-
throw new Error("Invalid project deployment response");
|
|
29497
|
-
}
|
|
29498
|
-
const { ok: _ok, ...receipt } = data;
|
|
29499
|
-
return parseApplicationDeployResult(receipt, expected);
|
|
29500
|
-
}
|
|
29501
|
-
async function runProjectDeployment(http, args) {
|
|
29502
|
-
const scope = { project_ref: args.ref, application_id: args.id, environment_id: args.environment_id };
|
|
29503
|
-
const request = {
|
|
29504
|
-
mutation_id: args.mutation_id,
|
|
29505
|
-
release_id: args.release_id,
|
|
29506
|
-
...args.configuration_id === undefined ? {} : { configuration_id: args.configuration_id },
|
|
29507
|
-
...args.configuration_write === undefined ? {} : { configuration_write: args.configuration_write },
|
|
29508
|
-
...args.expected_plan_sha256 === undefined ? {} : { expected_plan_sha256: args.expected_plan_sha256 },
|
|
29509
|
-
...args.approved_migration_digest === undefined ? {} : { approved_migration_digest: args.approved_migration_digest },
|
|
29510
|
-
...args.web_release === undefined ? {} : { web_release: args.web_release }
|
|
29511
|
-
};
|
|
29512
|
-
if (!Check2(scopeSchema, scope) || !Check2(ApplicationDeployRequestSchema, request) || request.configuration_id !== undefined && request.configuration_write !== undefined && request.configuration_id !== request.configuration_write.configuration_id) {
|
|
29513
|
-
return releaseControlFailure(operation, "INVALID_RESPONSE", null);
|
|
29514
|
-
}
|
|
29515
|
-
const expected = { ...scope, request };
|
|
29516
|
-
const identity = {
|
|
29517
|
-
...scope,
|
|
29518
|
-
deployment_id: request.mutation_id,
|
|
29519
|
-
mutation_id: request.mutation_id,
|
|
29520
|
-
release_id: request.release_id
|
|
29521
|
-
};
|
|
29522
|
-
const endpoint = `/v1/projects/${encodeURIComponent(scope.project_ref)}/environments/${encodeURIComponent(scope.environment_id)}/deployments`;
|
|
29523
|
-
const { mutation_id, ...candidate } = request;
|
|
29524
|
-
const result = await http.post(endpoint, {
|
|
29525
|
-
...candidate,
|
|
29526
|
-
deployment_id: mutation_id,
|
|
29527
|
-
application_id: scope.application_id
|
|
29528
|
-
}, { timeoutMs: 120000, maxJsonBytes: 131072, responseTimeoutMs: 30000 });
|
|
29529
|
-
if (result.ok && result.status === 200) {
|
|
29530
|
-
try {
|
|
29531
|
-
return releaseControlSuccess(operation, {
|
|
29532
|
-
...responseReceipt(result.data, expected),
|
|
29533
|
-
deployment_id: mutation_id
|
|
29534
|
-
});
|
|
29535
|
-
} catch {}
|
|
29536
|
-
} else if (!result.ok && !result.transportError && !result.responseReadError && result.status < 500 && result.status !== 408) {
|
|
29537
|
-
return releaseControlMutationFailure(operation, result, identity);
|
|
29538
|
-
}
|
|
29539
|
-
const observed = await http.get(`${endpoint}/${encodeURIComponent(mutation_id)}`, {
|
|
29540
|
-
retry: false,
|
|
29541
|
-
totalTimeoutMs: 30000,
|
|
29542
|
-
maxJsonBytes: 131072
|
|
29543
|
-
});
|
|
29544
|
-
try {
|
|
29545
|
-
if (!observed.ok || observed.status !== 200 || !Check2(statusSchema, observed.data))
|
|
29546
|
-
throw new Error;
|
|
29547
|
-
const state = observed.data;
|
|
29548
|
-
if (state.deployment_id !== mutation_id || state.project_ref !== scope.project_ref || state.application_id !== scope.application_id || state.environment_id !== scope.environment_id || state.status !== "succeeded" || state.phase !== "verifying")
|
|
29549
|
-
throw new Error;
|
|
29550
|
-
const receipt = parseApplicationDeployResult(state.receipt, expected);
|
|
29551
|
-
return releaseControlSuccess(operation, {
|
|
29552
|
-
...receipt,
|
|
29553
|
-
deployment_id: mutation_id,
|
|
29554
|
-
replayed: true,
|
|
29555
|
-
execution_performed: false
|
|
29556
|
-
});
|
|
29557
|
-
} catch {
|
|
29558
|
-
return releaseControlFailure(operation, "OUTCOME_UNKNOWN", result.transportError ? null : result.status, identity);
|
|
29559
|
-
}
|
|
29560
|
-
}
|
|
29561
29198
|
// package.json
|
|
29562
29199
|
var package_default = {
|
|
29563
29200
|
name: "@supacloud/cli",
|
|
29564
|
-
version: "0.
|
|
29201
|
+
version: "0.94.0",
|
|
29565
29202
|
description: "Project-scoped CLI for SupaCloud users",
|
|
29566
29203
|
type: "module",
|
|
29567
29204
|
main: "./dist/index.js",
|
|
@@ -29615,7 +29252,7 @@ var package_default = {
|
|
|
29615
29252
|
dependencies: {
|
|
29616
29253
|
"@supacloud/compiler": "^0.44.1",
|
|
29617
29254
|
"@supacloud/delivery": "0.16.0",
|
|
29618
|
-
"@supacloud/db": "0.16.
|
|
29255
|
+
"@supacloud/db": "0.16.2",
|
|
29619
29256
|
typebox: "1.3.36"
|
|
29620
29257
|
},
|
|
29621
29258
|
overrides: {
|
|
@@ -29837,8 +29474,6 @@ EXAMPLES
|
|
|
29837
29474
|
${preferredCommand} deploy
|
|
29838
29475
|
${preferredCommand} --env test deploy
|
|
29839
29476
|
${preferredCommand} --env production deploy --confirm-production <project-ref>
|
|
29840
|
-
${preferredCommand} deploy --target web
|
|
29841
|
-
${preferredCommand} deploy --target api
|
|
29842
29477
|
${preferredCommand} dev sync --env test --target functions --function api
|
|
29843
29478
|
${preferredCommand} dev status --env test
|
|
29844
29479
|
${preferredCommand} dev watch --env test --target project
|
|
@@ -30194,8 +29829,7 @@ function createCliTools(context, confirmProduction) {
|
|
|
30194
29829
|
applicationRelease: (args) => tools.app ? tools.app.callback(args) : Promise.resolve({
|
|
30195
29830
|
isError: true,
|
|
30196
29831
|
content: [{ type: "text", text: "Application release support is unavailable in this CLI context" }]
|
|
30197
|
-
})
|
|
30198
|
-
edgeFunctionDeploy: advancedTools.edge_functions?.callback
|
|
29832
|
+
})
|
|
30199
29833
|
})));
|
|
30200
29834
|
assign(captureTools((server) => registerGatewayTools(server, http, {
|
|
30201
29835
|
projectRef: context.projectRef || undefined
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const DEPLOYMENT_UPGRADE = "DEPLOYMENT_UPGRADE_REQUIRED: Legacy deployment is no longer supported. Upgrade with `npm install -g @supacloud/cli@latest`, bind application.id and application.environmentId in supacloud.json (or SUPACLOUD_APPLICATION_ID and SUPACLOUD_ENV), then run `supacloud deploy`. SupaCloud owns deployment planning and activation; do not retry with legacy commands.";
|
|
2
|
+
export declare function assertCurrentDeploymentInput(tool: string, input: unknown): void;
|
|
@@ -9,7 +9,7 @@ export declare const APPLICATION_TOOL_SCHEMA: {
|
|
|
9
9
|
ttl_seconds: Type.TOptional<Type.TInteger>;
|
|
10
10
|
wait: Type.TOptional<Type.TBoolean>;
|
|
11
11
|
timeout_seconds: Type.TOptional<Type.TInteger>;
|
|
12
|
-
action: Type.TEnum<("
|
|
12
|
+
action: Type.TEnum<("upload_release" | "activate_release" | "list_releases" | "get_release" | "logs" | "get_runtime" | "get_deployment_evidence" | "get_configuration" | "get_release_transfer_plan" | "get_promotion_plan" | "get_promotion_status" | "get_rollback_snapshot" | "get_history" | "get_deploy_plan" | "get_preview_plan" | "list_previews" | "get_preview" | "put_configuration" | "deploy_application" | "transfer_release" | "promote_application" | "reconcile_promotion" | "create_preview" | "reconcile_preview" | "cleanup_preview" | "deploy_release" | "rollback_release" | "reconcile_activation" | "retire_activation")[]>;
|
|
13
13
|
ref: Type.TString;
|
|
14
14
|
id: Type.TString;
|
|
15
15
|
environment_id: Type.TOptional<Type.TString>;
|
|
@@ -14,7 +14,6 @@ interface DeployToolOptions {
|
|
|
14
14
|
manifestPath?: string;
|
|
15
15
|
};
|
|
16
16
|
applicationRelease?: (args: Record<string, unknown>) => Promise<ToolResponse>;
|
|
17
|
-
edgeFunctionDeploy?: (args: Record<string, unknown>) => Promise<ToolResponse>;
|
|
18
17
|
}
|
|
19
18
|
interface ToolResponse {
|
|
20
19
|
content: Array<{
|
|
@@ -25,15 +24,8 @@ interface ToolResponse {
|
|
|
25
24
|
}
|
|
26
25
|
export declare const deployToolSchema: {
|
|
27
26
|
ref: Type.TOptional<Type.TString>;
|
|
28
|
-
target: Type.TOptional<Type.TString>;
|
|
29
|
-
id: Type.TOptional<Type.TString>;
|
|
30
27
|
cwd: Type.TOptional<Type.TString>;
|
|
31
|
-
build_command: Type.TOptional<Type.TString>;
|
|
32
|
-
output_dir: Type.TOptional<Type.TString>;
|
|
33
28
|
skip_build: Type.TOptional<Type.TBoolean>;
|
|
34
|
-
dry_run: Type.TOptional<Type.TBoolean>;
|
|
35
|
-
plan: Type.TOptional<Type.TBoolean>;
|
|
36
|
-
diff: Type.TOptional<Type.TBoolean>;
|
|
37
29
|
json: Type.TOptional<Type.TBoolean>;
|
|
38
30
|
};
|
|
39
31
|
export declare function findDeployConfigRoot(startDirectory: string, environmentName?: string): Promise<string>;
|
|
@@ -15,7 +15,7 @@ export declare const environmentToolSchema: ToolSchema;
|
|
|
15
15
|
declare function outputDirectoryFor(manifest: PackageManifest): string;
|
|
16
16
|
declare function profileNameFromFile(name: string): string | null;
|
|
17
17
|
declare function statusMissing(context: ResolvedContext): string[];
|
|
18
|
-
declare function deployMissing(context: ResolvedContext): string[];
|
|
18
|
+
declare function deployMissing(context: ResolvedContext, application?: Record<string, unknown> | null): string[];
|
|
19
19
|
export declare function registerDeveloperExperienceTools(server: ToolServer, options?: DeveloperExperienceOptions): void;
|
|
20
20
|
export declare const __internal: {
|
|
21
21
|
outputDirectoryFor: typeof outputDirectoryFor;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { ToolResult } from "../tool-server";
|
|
2
2
|
import type { HttpTransport } from "../transports/http";
|
|
3
|
+
export declare function requireProjectDeploymentProtocol(http: Pick<HttpTransport, "get">, projectRef: string, environmentId: string): Promise<void>;
|
|
3
4
|
export declare function runProjectDeployment(http: Pick<HttpTransport, "post" | "get">, args: Record<string, unknown>): Promise<ToolResult>;
|
|
@@ -20,7 +20,7 @@ export interface RemoteDevToolOptions {
|
|
|
20
20
|
}
|
|
21
21
|
export declare const remoteDevToolSchema: {
|
|
22
22
|
action: Type.TEnum<("status" | "migrate" | "watch" | "sync")[]>;
|
|
23
|
-
target: Type.TOptional<Type.TEnum<("
|
|
23
|
+
target: Type.TOptional<Type.TEnum<("frontend" | "project" | "db" | "functions")[]>>;
|
|
24
24
|
project_dir: Type.TOptional<Type.TString>;
|
|
25
25
|
remote_root: Type.TOptional<Type.TString>;
|
|
26
26
|
remote_host: Type.TOptional<Type.TString>;
|
package/dist/tool-runtime.js
CHANGED
|
@@ -129,12 +129,37 @@ function schemaProperties(schema) {
|
|
|
129
129
|
// src/shared/tool-server.ts
|
|
130
130
|
import { Type as Type2 } from "typebox";
|
|
131
131
|
import { Value as Value2 } from "typebox/value";
|
|
132
|
+
|
|
133
|
+
// src/shared/deployment-upgrade.ts
|
|
134
|
+
var DEPLOYMENT_UPGRADE = "DEPLOYMENT_UPGRADE_REQUIRED: Legacy deployment is no longer supported. Upgrade with `npm install -g @supacloud/cli@latest`, bind application.id and application.environmentId in supacloud.json (or SUPACLOUD_APPLICATION_ID and SUPACLOUD_ENV), then run `supacloud deploy`. SupaCloud owns deployment planning and activation; do not retry with legacy commands.";
|
|
135
|
+
var removedFrontendActions = new Set([
|
|
136
|
+
"deploy_git",
|
|
137
|
+
"deploy_upload",
|
|
138
|
+
"redeploy",
|
|
139
|
+
"upload_release",
|
|
140
|
+
"activate_release"
|
|
141
|
+
]);
|
|
142
|
+
var removedDeployFlags = ["target", "id", "build_command", "output_dir", "plan", "diff", "dry_run"];
|
|
143
|
+
function assertCurrentDeploymentInput(tool, input) {
|
|
144
|
+
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
145
|
+
return;
|
|
146
|
+
const args = input;
|
|
147
|
+
if (tool === "frontend" && typeof args["action"] === "string" && removedFrontendActions.has(args["action"])) {
|
|
148
|
+
throw new Error(DEPLOYMENT_UPGRADE);
|
|
149
|
+
}
|
|
150
|
+
if (tool === "deploy" && removedDeployFlags.some((flag) => Object.hasOwn(args, flag))) {
|
|
151
|
+
throw new Error(`${DEPLOYMENT_UPGRADE} Put build settings in the frontend configuration. For read-only review use \`supacloud app deploy-plan\`.`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/shared/tool-server.ts
|
|
132
156
|
var resultSchema = Type2.Object({
|
|
133
157
|
content: Type2.Array(Type2.Object({ type: Type2.Literal("text"), text: Type2.String() })),
|
|
134
158
|
isError: Type2.Optional(Type2.Boolean())
|
|
135
159
|
});
|
|
136
160
|
function registerTool(server, name, description, schema, callback) {
|
|
137
161
|
server.tool(name, description, schema, async (input) => {
|
|
162
|
+
assertCurrentDeploymentInput(name, input);
|
|
138
163
|
const args = parseToolArguments(schema, input);
|
|
139
164
|
const result = await callback(args);
|
|
140
165
|
if (!Value2.Check(resultSchema, result))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supacloud/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.94.0",
|
|
4
4
|
"description": "Project-scoped CLI for SupaCloud users",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@supacloud/compiler": "^0.44.1",
|
|
56
56
|
"@supacloud/delivery": "0.16.0",
|
|
57
|
-
"@supacloud/db": "0.16.
|
|
57
|
+
"@supacloud/db": "0.16.2",
|
|
58
58
|
"typebox": "1.3.36"
|
|
59
59
|
},
|
|
60
60
|
"overrides": {
|
|
@@ -29,6 +29,15 @@ Use `supacloud` as the project-level control surface and keep durable changes re
|
|
|
29
29
|
|
|
30
30
|
## Workflow
|
|
31
31
|
|
|
32
|
+
For normal application deployment, run `supacloud --env <name> deploy`.
|
|
33
|
+
Use the current application delivery configuration and a bound application ID.
|
|
34
|
+
Do not assemble a client-side sequence of scope generation, backup, migration
|
|
35
|
+
apply, PostgREST refresh, Function deployment and Web activation.
|
|
36
|
+
The server owns that sequence. Old `deploy --target` and `frontend` publishing
|
|
37
|
+
actions are removed: upgrade CLI and server together, migrate the configuration,
|
|
38
|
+
and retry the unified command. Do not substitute a legacy endpoint when it fails.
|
|
39
|
+
The inspection and database-maintenance workflow below is not a deployment checklist.
|
|
40
|
+
|
|
32
41
|
1. Classify the request using [references/command-map.md](references/command-map.md).
|
|
33
42
|
2. For database work, load [references/database-workflow.md](references/database-workflow.md) and choose either new-change or historical-drift reconciliation.
|
|
34
43
|
3. For AI-assisted database changes, first request bounded context:
|
|
@@ -6,6 +6,7 @@ Load this reference when selecting a command surface or when a user asks an AI t
|
|
|
6
6
|
|
|
7
7
|
| Intent | Use | Guardrail |
|
|
8
8
|
| --- | --- | --- |
|
|
9
|
+
| Deploy the bound application | `supacloud --env <name> deploy` | Use current CLI/server and application delivery configuration; never fall back to standalone target or direct frontend activation commands |
|
|
9
10
|
| Inspect current project binding | `supacloud status` | Read-only; run first |
|
|
10
11
|
| Inspect selected project API/Auth/Studio origins | `supacloud project endpoints` | Uses the Management API's authoritative projection; do not reconstruct domains locally |
|
|
11
12
|
| Enumerate projects or endpoint projections | `supacloud-admin project list` / `project list_endpoints` | Platform-wide read; never promote a project credential to Admin authority |
|
|
@@ -23,9 +24,6 @@ Load this reference when selecting a command surface or when a user asks an AI t
|
|
|
23
24
|
| Inspect or back up a remote database | `supabase db_pull`, `migration_list`, `db_dump`, `gen_types` | Requires explicit PostgreSQL DSN; redact it |
|
|
24
25
|
| 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 |
|
|
25
26
|
| 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 |
|
|
26
|
-
| Rebind release scope manifest to upstream base commit | `release scope_rebind` | Local maintenance action; requires the exact `--environment_id`, updates only scopes bound to that environment, supports `--dry_run`/`--task`, and recalculates `scope_sha256` |
|
|
27
|
-
| Inspect release scope manifest and git alignment | `release scope_inspect` | Local maintenance action; requires the exact `--environment_id`, rejects legacy or cross-environment scopes, returns canonical hash, and checks alignment with HEAD / origin/main |
|
|
28
|
-
| Scaffold a new canonical release scope manifest | `release scope_create` | Local maintenance action; requires `--environment_id` and outputs a current-main-bound scope with sorted functions, migrations, and environment identity |
|
|
29
27
|
| Preview/apply migrations remotely | `supabase push` | Always dry-run first; production needs explicit approval |
|
|
30
28
|
| Mark proven-equivalent historical migrations as applied | `database baseline_migrations` | Dry-run, schema-equivalence proof, backup, explicit approval |
|
|
31
29
|
| 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 |
|
|
@@ -47,11 +45,11 @@ until a project-scoped context is resolved.
|
|
|
47
45
|
- `auth`: provider/configuration plus bounded user lookup and production-confirmed `magiclink`, `recovery`, or `invite` generation. Search/email/redirect inputs and returned action URLs are bounded and validated before use.
|
|
48
46
|
- `storage`: buckets and object-management workflows.
|
|
49
47
|
- `edge_functions`: list, atomically read one active or deleted identity with `get_config`, read immutable source, deploy, activate, configure, and delete Edge Functions. For every mutation, pass the `activation_id` read from the same `list` or `get_config` snapshot as `--expected-activation-id`; use `legacy` only for a never-created or listed legacy Function, not for a deleted slug with a tombstone UUID. Deploy and activate actions also require the non-negative observed version as `--expected-active-version`; use `absent` for a never-created slug or a `get_config` tombstone. Version `0` is a legacy version token and cannot be used as a source or activation target.
|
|
50
|
-
- `frontend`:
|
|
48
|
+
- `frontend`: hosting management, release inspection and operator rollback. Publishing uses `supacloud deploy`; `deploy_git`, `deploy_upload`, `redeploy`, `upload_release` and `activate_release` are removed.
|
|
51
49
|
- `secrets`: project secret management; never print values after write.
|
|
52
50
|
- `queue`, `task_events`, `diagnostics`: asynchronous workload operations and bounded diagnostics.
|
|
53
51
|
- `gateway`: project route/config/rebuild operations.
|
|
54
|
-
- `release`:
|
|
52
|
+
- `release`: explicit backup/PostgREST maintenance and release-canary replay diagnostics. Do not run these or generate release scopes as prerequisites for `supacloud --env <name> deploy`; the platform owns deployment orchestration.
|
|
55
53
|
- `ai`: inspect or install this packaged Skill.
|
|
56
54
|
|
|
57
55
|
## Safe inspection pattern
|