@supacloud/cli 0.110.0 → 0.111.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 +13 -0
- package/dist/index.js +244 -70
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -605,6 +605,19 @@ supacloud release release_canary_fixture_disable_replay --ref abc123 \
|
|
|
605
605
|
--issuer <issuer-url> --subject <central-subject-uuid>
|
|
606
606
|
```
|
|
607
607
|
|
|
608
|
+
A standard cross-component release contract can be validated without mutation:
|
|
609
|
+
|
|
610
|
+
```bash
|
|
611
|
+
supacloud release contract_plan --file supacloud.release.json \
|
|
612
|
+
--ref abc123 --environment_id production --require_current_source
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
The plan rejects project or environment binding mismatches and returns the
|
|
616
|
+
canonical `scope_sha256`; it does not apply migrations, Functions, Application
|
|
617
|
+
releases, or frontend assets. See
|
|
618
|
+
[docs/application-release-contract.md](../../docs/application-release-contract.md).
|
|
619
|
+
|
|
620
|
+
|
|
608
621
|
Backup creation reports success only after the CLI verifies exactly one new
|
|
609
622
|
logical-backup receipt against the inventory before and after the mutation.
|
|
610
623
|
Logical restore is project-scoped: pause the selected project first, obtain the
|
package/dist/index.js
CHANGED
|
@@ -9341,7 +9341,7 @@ var ACTION_POLICY = {
|
|
|
9341
9341
|
mutations: { read: ["status"] },
|
|
9342
9342
|
release: {
|
|
9343
9343
|
read: ["logical_backup_list", "logical_backup_status", "postgrest_status"],
|
|
9344
|
-
local: ["scope_inspect", "scope_rebind", "scope_create"],
|
|
9344
|
+
local: ["scope_inspect", "scope_rebind", "scope_create", "contract_plan"],
|
|
9345
9345
|
write: ["logical_backup_create", "logical_backup_restore", "postgrest_restart", "release_canary_fixture_stage_replay", "release_canary_fixture_disable_replay"]
|
|
9346
9346
|
},
|
|
9347
9347
|
secrets: { read: ["list"], write: ["upsert", "delete"] },
|
|
@@ -9975,7 +9975,7 @@ class HttpTransport {
|
|
|
9975
9975
|
}
|
|
9976
9976
|
|
|
9977
9977
|
// src/shared/tools/database-tools.ts
|
|
9978
|
-
import { createHash as
|
|
9978
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
9979
9979
|
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync as statSync2 } from "node:fs";
|
|
9980
9980
|
import { basename as basename3, join as join4 } from "node:path";
|
|
9981
9981
|
|
|
@@ -11486,6 +11486,108 @@ function buildDeliveryMigrationPlan(archive, inventory, ref) {
|
|
|
11486
11486
|
dataRecovery: "separate-required"
|
|
11487
11487
|
};
|
|
11488
11488
|
}
|
|
11489
|
+
// node_modules/@supacloud/delivery/src/release-contract.ts
|
|
11490
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
11491
|
+
var RELEASE_CONTRACT_SCHEMA = "supacloud.release-control.v2";
|
|
11492
|
+
var SHA256 = /^[0-9a-f]{64}$/;
|
|
11493
|
+
var COMMIT = /^[0-9a-f]{40}$/;
|
|
11494
|
+
var PROJECT_REF = /^[a-z0-9]{20}$/;
|
|
11495
|
+
var ENVIRONMENT_ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
11496
|
+
var FUNCTION_SLUG = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
11497
|
+
var MIGRATION_NAME = /^\d{14}_[a-z0-9_]+$/;
|
|
11498
|
+
function record3(value, label) {
|
|
11499
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
11500
|
+
throw new Error(`${label} must be an object`);
|
|
11501
|
+
}
|
|
11502
|
+
return value;
|
|
11503
|
+
}
|
|
11504
|
+
function exactKeys(value, expected, label) {
|
|
11505
|
+
const actual = Object.keys(value).sort();
|
|
11506
|
+
const keys = [...expected].sort();
|
|
11507
|
+
if (actual.length !== keys.length || actual.some((key, index) => key !== keys[index])) {
|
|
11508
|
+
throw new Error(`${label} contains unsupported or missing fields`);
|
|
11509
|
+
}
|
|
11510
|
+
}
|
|
11511
|
+
function nonEmptyString(value, label, pattern) {
|
|
11512
|
+
if (typeof value !== "string" || value.length === 0 || pattern && !pattern.test(value)) {
|
|
11513
|
+
throw new Error(`${label} is invalid`);
|
|
11514
|
+
}
|
|
11515
|
+
return value;
|
|
11516
|
+
}
|
|
11517
|
+
function parseComponent(value, index) {
|
|
11518
|
+
const candidate = record3(value, `Release component ${index}`);
|
|
11519
|
+
if (candidate["kind"] === "migration") {
|
|
11520
|
+
exactKeys(candidate, ["kind", "name"], `Release migration ${index}`);
|
|
11521
|
+
return { kind: "migration", name: nonEmptyString(candidate["name"], `Release migration ${index}`, MIGRATION_NAME) };
|
|
11522
|
+
}
|
|
11523
|
+
if (candidate["kind"] === "function") {
|
|
11524
|
+
exactKeys(candidate, ["artifactSha256", "kind", "slug"], `Release Function ${index}`);
|
|
11525
|
+
return {
|
|
11526
|
+
kind: "function",
|
|
11527
|
+
slug: nonEmptyString(candidate["slug"], `Release Function ${index}`, FUNCTION_SLUG),
|
|
11528
|
+
artifactSha256: nonEmptyString(candidate["artifactSha256"], `Release Function ${index}`, SHA256)
|
|
11529
|
+
};
|
|
11530
|
+
}
|
|
11531
|
+
if (candidate["kind"] === "web") {
|
|
11532
|
+
exactKeys(candidate, ["artifactSha256", "kind"], `Release Web ${index}`);
|
|
11533
|
+
return {
|
|
11534
|
+
kind: "web",
|
|
11535
|
+
artifactSha256: nonEmptyString(candidate["artifactSha256"], `Release Web ${index}`, SHA256)
|
|
11536
|
+
};
|
|
11537
|
+
}
|
|
11538
|
+
throw new Error(`Release component ${index} kind is invalid`);
|
|
11539
|
+
}
|
|
11540
|
+
function componentKey(component) {
|
|
11541
|
+
return component.kind === "migration" ? `${component.kind}:${component.name}` : component.kind === "function" ? `${component.kind}:${component.slug}` : component.kind;
|
|
11542
|
+
}
|
|
11543
|
+
function parseReleaseScope(value) {
|
|
11544
|
+
const candidate = record3(value, "Release scope");
|
|
11545
|
+
exactKeys(candidate, [
|
|
11546
|
+
"components",
|
|
11547
|
+
"releaseId",
|
|
11548
|
+
"schema",
|
|
11549
|
+
"sourceCommit",
|
|
11550
|
+
"targetEnvironmentId",
|
|
11551
|
+
"targetProjectRef"
|
|
11552
|
+
], "Release scope");
|
|
11553
|
+
if (candidate["schema"] !== RELEASE_CONTRACT_SCHEMA) {
|
|
11554
|
+
throw new Error("Release scope schema is invalid");
|
|
11555
|
+
}
|
|
11556
|
+
if (!Array.isArray(candidate["components"]) || candidate["components"].length === 0) {
|
|
11557
|
+
throw new Error("Release scope components must be non-empty");
|
|
11558
|
+
}
|
|
11559
|
+
const components = candidate["components"].map(parseComponent);
|
|
11560
|
+
const componentKeys = components.map(componentKey);
|
|
11561
|
+
if (new Set(componentKeys).size !== componentKeys.length || componentKeys.some((key, index) => key !== [...componentKeys].sort()[index])) {
|
|
11562
|
+
throw new Error("Release scope components must be unique and sorted");
|
|
11563
|
+
}
|
|
11564
|
+
return {
|
|
11565
|
+
schema: RELEASE_CONTRACT_SCHEMA,
|
|
11566
|
+
releaseId: nonEmptyString(candidate["releaseId"], "Release scope releaseId"),
|
|
11567
|
+
sourceCommit: nonEmptyString(candidate["sourceCommit"], "Release scope sourceCommit", COMMIT),
|
|
11568
|
+
targetEnvironmentId: nonEmptyString(candidate["targetEnvironmentId"], "Release scope targetEnvironmentId", ENVIRONMENT_ID),
|
|
11569
|
+
targetProjectRef: nonEmptyString(candidate["targetProjectRef"], "Release scope targetProjectRef", PROJECT_REF),
|
|
11570
|
+
components
|
|
11571
|
+
};
|
|
11572
|
+
}
|
|
11573
|
+
function canonical2(value) {
|
|
11574
|
+
if (Array.isArray(value))
|
|
11575
|
+
return value.map(canonical2);
|
|
11576
|
+
if (!value || typeof value !== "object")
|
|
11577
|
+
return value;
|
|
11578
|
+
return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, canonical2(item)]));
|
|
11579
|
+
}
|
|
11580
|
+
function releaseScopeSha256(scope) {
|
|
11581
|
+
return createHash3("sha256").update(JSON.stringify(canonical2(scope))).digest("hex");
|
|
11582
|
+
}
|
|
11583
|
+
var ALLOWED_PHASE_TRANSITIONS = new Map([
|
|
11584
|
+
["planned", ["mutating", "rollback-required"]],
|
|
11585
|
+
["mutating", ["applied", "rollback-required"]],
|
|
11586
|
+
["applied", ["reconciled", "rollback-required"]],
|
|
11587
|
+
["reconciled", []],
|
|
11588
|
+
["rollback-required", ["rolled-back", "reconciled"]],
|
|
11589
|
+
["rolled-back", []]
|
|
11590
|
+
]);
|
|
11489
11591
|
|
|
11490
11592
|
// node_modules/@supacloud/delivery/src/index.ts
|
|
11491
11593
|
async function readDeliveryExecutableArchive(manifestPath) {
|
|
@@ -12276,7 +12378,7 @@ async function writeMigrationImportPlan(plan, outputDirectory) {
|
|
|
12276
12378
|
}
|
|
12277
12379
|
return written;
|
|
12278
12380
|
}
|
|
12279
|
-
function
|
|
12381
|
+
function record4(value) {
|
|
12280
12382
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
12281
12383
|
}
|
|
12282
12384
|
function sqlFile(file, root, source) {
|
|
@@ -12288,17 +12390,17 @@ function sqlFile(file, root, source) {
|
|
|
12288
12390
|
};
|
|
12289
12391
|
}
|
|
12290
12392
|
function hasuraMetadataSql(input) {
|
|
12291
|
-
const metadata =
|
|
12393
|
+
const metadata = record4(input["metadata"]) ?? input;
|
|
12292
12394
|
const sources = Array.isArray(metadata["sources"]) ? metadata["sources"] : [];
|
|
12293
12395
|
const sql = [];
|
|
12294
12396
|
for (const rawSource of sources) {
|
|
12295
|
-
const source =
|
|
12397
|
+
const source = record4(rawSource);
|
|
12296
12398
|
if (!source)
|
|
12297
12399
|
continue;
|
|
12298
12400
|
const tables = Array.isArray(source["tables"]) ? source["tables"] : [];
|
|
12299
12401
|
for (const rawTable of tables) {
|
|
12300
|
-
const table =
|
|
12301
|
-
const tableName =
|
|
12402
|
+
const table = record4(rawTable);
|
|
12403
|
+
const tableName = record4(table?.["table"]);
|
|
12302
12404
|
const schema = typeof tableName?.["schema"] === "string" ? tableName["schema"] : "public";
|
|
12303
12405
|
const name = typeof tableName?.["name"] === "string" ? tableName["name"] : null;
|
|
12304
12406
|
if (!name || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(schema) || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(name))
|
|
@@ -12341,7 +12443,7 @@ function createMigrationImportPlan(input) {
|
|
|
12341
12443
|
}
|
|
12342
12444
|
}
|
|
12343
12445
|
if (input.source === "hasura" && input.metadata !== undefined) {
|
|
12344
|
-
const metadata =
|
|
12446
|
+
const metadata = record4(input.metadata) ?? {};
|
|
12345
12447
|
const generated = hasuraMetadataSql(metadata);
|
|
12346
12448
|
if (generated.length > 0) {
|
|
12347
12449
|
files.push({ file: "999999999999_hasura_rls_review.sql", name: "hasura_rls_review", sql: `${generated.join(`
|
|
@@ -12354,10 +12456,10 @@ function createMigrationImportPlan(input) {
|
|
|
12354
12456
|
const actions = Array.isArray(metadata["actions"]) ? metadata["actions"] : [];
|
|
12355
12457
|
const remoteSchemas = Array.isArray(metadata["remote_schemas"]) ? metadata["remote_schemas"] : [];
|
|
12356
12458
|
const eventTriggers = Array.isArray(metadata["sources"]) ? metadata["sources"].flatMap((source) => {
|
|
12357
|
-
const sourceRecord =
|
|
12459
|
+
const sourceRecord = record4(source);
|
|
12358
12460
|
const tables = Array.isArray(sourceRecord?.["tables"]) ? sourceRecord["tables"] : [];
|
|
12359
12461
|
return tables.flatMap((table) => {
|
|
12360
|
-
const tableRecord =
|
|
12462
|
+
const tableRecord = record4(table);
|
|
12361
12463
|
return Array.isArray(tableRecord?.["event_triggers"]) ? tableRecord["event_triggers"] : [];
|
|
12362
12464
|
});
|
|
12363
12465
|
}) : [];
|
|
@@ -12407,7 +12509,7 @@ function isMigrationInventoryAppliedAt(appliedAt) {
|
|
|
12407
12509
|
return appliedAt === null || typeof appliedAt === "string" && appliedAt.trim() === appliedAt && appliedAt.length > 0 && !Number.isNaN(Date.parse(appliedAt));
|
|
12408
12510
|
}
|
|
12409
12511
|
function migrationInventoryChecksum(entry) {
|
|
12410
|
-
return
|
|
12512
|
+
return createHash4("sha256").update(JSON.stringify({
|
|
12411
12513
|
version: entry.version,
|
|
12412
12514
|
name: entry.name,
|
|
12413
12515
|
statements: entry.statements
|
|
@@ -12514,7 +12616,7 @@ function migrationVersionFromFilename(file) {
|
|
|
12514
12616
|
}
|
|
12515
12617
|
return version.toString();
|
|
12516
12618
|
}
|
|
12517
|
-
const digest =
|
|
12619
|
+
const digest = createHash4("sha256").update(basename3(file)).digest("hex");
|
|
12518
12620
|
const offset = BigInt(`0x${digest}`) % FALLBACK_MIGRATION_VERSION_RANGE;
|
|
12519
12621
|
return (FALLBACK_MIGRATION_VERSION_BASE + offset).toString();
|
|
12520
12622
|
}
|
|
@@ -12611,7 +12713,7 @@ function exactIdentityIsApplied(local, remote) {
|
|
|
12611
12713
|
return false;
|
|
12612
12714
|
if (statement === `baseline:${local.name}` || statement === `direct-apply:${local.name}`)
|
|
12613
12715
|
return true;
|
|
12614
|
-
const rawChecksum =
|
|
12716
|
+
const rawChecksum = createHash4("sha256").update(local.rawBytes).digest("hex");
|
|
12615
12717
|
return statement === `sha256:${rawChecksum}` || statement.trim() === local.sql.trim();
|
|
12616
12718
|
}
|
|
12617
12719
|
function legacyIdentityIsApplied(local, remote) {
|
|
@@ -15074,7 +15176,7 @@ Actions: status, list_buckets, get_bucket, create_bucket, update_bucket, delete_
|
|
|
15074
15176
|
}
|
|
15075
15177
|
|
|
15076
15178
|
// src/shared/tools/advanced-tools.ts
|
|
15077
|
-
import { createHash as
|
|
15179
|
+
import { createHash as createHash6, timingSafeEqual } from "node:crypto";
|
|
15078
15180
|
import {
|
|
15079
15181
|
closeSync,
|
|
15080
15182
|
constants as fsConstants,
|
|
@@ -15095,7 +15197,7 @@ import { basename as basename5, join as join5, relative as relative2, resolve as
|
|
|
15095
15197
|
|
|
15096
15198
|
// src/shared/tools/function-aot.ts
|
|
15097
15199
|
import { execFile } from "node:child_process";
|
|
15098
|
-
import { createHash as
|
|
15200
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
15099
15201
|
import { readFileSync as readFileSync5 } from "node:fs";
|
|
15100
15202
|
import { dirname as dirname3, resolve as resolve6 } from "node:path";
|
|
15101
15203
|
import { promisify } from "node:util";
|
|
@@ -15177,7 +15279,7 @@ async function buildFunctionAot(entrypoint, outfile, options = {}) {
|
|
|
15177
15279
|
const code = readFileSync5(outfile, "utf8");
|
|
15178
15280
|
if (!code.trim())
|
|
15179
15281
|
throw new Error("AOT build produced an empty bundle");
|
|
15180
|
-
return { code, prebundled: true, expectedSha256:
|
|
15282
|
+
return { code, prebundled: true, expectedSha256: createHash5("sha256").update(code).digest("hex") };
|
|
15181
15283
|
}
|
|
15182
15284
|
|
|
15183
15285
|
// src/shared/tools/edge-function-response.ts
|
|
@@ -15531,7 +15633,7 @@ function assertExpectedSha256(bytes, expectedSha256) {
|
|
|
15531
15633
|
if (!SHA256_HEX_PATTERN.test(expectedSha256)) {
|
|
15532
15634
|
throw new Error("'--expected-sha256' must be a lowercase 64-character SHA-256");
|
|
15533
15635
|
}
|
|
15534
|
-
const actualDigest =
|
|
15636
|
+
const actualDigest = createHash6("sha256").update(bytes).digest();
|
|
15535
15637
|
const expectedDigest = Buffer.from(expectedSha256, "hex");
|
|
15536
15638
|
if (!timingSafeEqual(actualDigest, expectedDigest)) {
|
|
15537
15639
|
throw new Error("Prebundled file SHA-256 does not match --expected-sha256");
|
|
@@ -16075,7 +16177,8 @@ function validReceiptVersion(activeVersion) {
|
|
|
16075
16177
|
}
|
|
16076
16178
|
function confirmedMutationVersion(receipt, config, expectation) {
|
|
16077
16179
|
const activeVersion = receipt.active_version;
|
|
16078
|
-
|
|
16180
|
+
const configVersion = config.version;
|
|
16181
|
+
if (!validReceiptVersion(activeVersion) || receipt.version !== activeVersion || configVersion !== undefined && configVersion !== activeVersion || expectation.targetVersion !== undefined && activeVersion !== expectation.targetVersion) {
|
|
16079
16182
|
return null;
|
|
16080
16183
|
}
|
|
16081
16184
|
return activeVersion;
|
|
@@ -16565,13 +16668,13 @@ ${JSON.stringify(r.data, null, 2)}` : `❌ Failed (${r.status}): ${JSON.stringif
|
|
|
16565
16668
|
}
|
|
16566
16669
|
|
|
16567
16670
|
// src/shared/tools/frontend-release-control.ts
|
|
16568
|
-
import { createHash as
|
|
16671
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
16569
16672
|
import { constants as fsConstants2 } from "node:fs";
|
|
16570
16673
|
import { open as open2 } from "node:fs/promises";
|
|
16571
16674
|
import { resolve as resolve8 } from "node:path";
|
|
16572
16675
|
|
|
16573
16676
|
// src/shared/mutation-protocol.ts
|
|
16574
|
-
import { createHash as
|
|
16677
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
16575
16678
|
var MUTATION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
16576
16679
|
var FINGERPRINT_PATTERN = /^[0-9a-f]{64}$/;
|
|
16577
16680
|
var OPERATION_PATTERN = /^[a-z0-9][a-z0-9._:-]{0,127}$/;
|
|
@@ -16650,7 +16753,7 @@ function stableStringifyNode(jsonValue) {
|
|
|
16650
16753
|
}
|
|
16651
16754
|
function mutationRequestFingerprint(normalizedRequest) {
|
|
16652
16755
|
assertCanonicalJsonNode(normalizedRequest, new WeakSet, 0);
|
|
16653
|
-
return
|
|
16756
|
+
return createHash7("sha256").update(stableStringifyNode(normalizedRequest)).digest("hex");
|
|
16654
16757
|
}
|
|
16655
16758
|
function objectRecord3(candidate) {
|
|
16656
16759
|
return candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
|
|
@@ -16831,7 +16934,7 @@ function releaseFailure(operation, code, status) {
|
|
|
16831
16934
|
}]
|
|
16832
16935
|
};
|
|
16833
16936
|
}
|
|
16834
|
-
function
|
|
16937
|
+
function exactKeys2(candidate, keys) {
|
|
16835
16938
|
const actual = Object.keys(candidate).sort();
|
|
16836
16939
|
const expected = [...keys].sort();
|
|
16837
16940
|
return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
|
|
@@ -16853,7 +16956,7 @@ function releaseRecord(candidate) {
|
|
|
16853
16956
|
"kind",
|
|
16854
16957
|
"archive_format"
|
|
16855
16958
|
];
|
|
16856
|
-
if (!
|
|
16959
|
+
if (!exactKeys2(record, keys) || record.schema !== FRONTEND_ARCHIVE_RELEASE_SCHEMA || record.archive_format !== FRONTEND_ARCHIVE_FORMAT || typeof record.project_ref !== "string" || !PROJECT_REF_PATTERN3.test(record.project_ref) || typeof record.deployment_id !== "string" || !DEPLOYMENT_ID_PATTERN.test(record.deployment_id) || typeof record.release_id !== "string" || !RELEASE_ID_PATTERN.test(record.release_id) || record.sha256 !== record.release_id || typeof record.tree_sha256 !== "string" || !RELEASE_ID_PATTERN.test(record.tree_sha256) || !Number.isSafeInteger(record.size_bytes) || Number(record.size_bytes) < 1 || !Number.isSafeInteger(record.file_count) || Number(record.file_count) < 1 || !canonicalTimestamp2(record.created_at) || record.kind !== "prebuilt_static")
|
|
16857
16960
|
return null;
|
|
16858
16961
|
return {
|
|
16859
16962
|
project_ref: record.project_ref,
|
|
@@ -16872,7 +16975,7 @@ function releaseEnvelope(candidate, expected) {
|
|
|
16872
16975
|
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate))
|
|
16873
16976
|
return null;
|
|
16874
16977
|
const envelope = candidate;
|
|
16875
|
-
const release =
|
|
16978
|
+
const release = exactKeys2(envelope, ["project_ref", "deployment_id", "release"]) ? releaseRecord(envelope.release) : null;
|
|
16876
16979
|
if (!release || envelope.project_ref !== expected.projectRef || envelope.deployment_id !== expected.deploymentId || release.project_ref !== expected.projectRef || release.deployment_id !== expected.deploymentId || release.release_id !== expected.releaseId)
|
|
16877
16980
|
return null;
|
|
16878
16981
|
return release;
|
|
@@ -16900,7 +17003,7 @@ function releaseInventory(candidate, expected) {
|
|
|
16900
17003
|
"releases",
|
|
16901
17004
|
"next_cursor"
|
|
16902
17005
|
];
|
|
16903
|
-
if (!
|
|
17006
|
+
if (!exactKeys2(record, keys) || typeof record.project_ref !== "string" || !PROJECT_REF_PATTERN3.test(record.project_ref) || typeof record.deployment_id !== "string" || !DEPLOYMENT_ID_PATTERN.test(record.deployment_id) || !Array.isArray(record.releases))
|
|
16904
17007
|
return null;
|
|
16905
17008
|
const activeReleaseId = nullableIdentity(record.active_release_id, RELEASE_ID_PATTERN);
|
|
16906
17009
|
const activeActivationId = nullableIdentity(record.active_activation_id, MUTATION_ID_PATTERN2);
|
|
@@ -16939,7 +17042,7 @@ function sameArchiveIdentity(left, right) {
|
|
|
16939
17042
|
return left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs;
|
|
16940
17043
|
}
|
|
16941
17044
|
async function archiveSha256(handle, sizeBytes) {
|
|
16942
|
-
const hash =
|
|
17045
|
+
const hash = createHash8("sha256");
|
|
16943
17046
|
const chunk = new Uint8Array(Math.min(sizeBytes, ARCHIVE_CHUNK_BYTES));
|
|
16944
17047
|
for (let offset = 0;offset < sizeBytes; ) {
|
|
16945
17048
|
const requested = Math.min(chunk.byteLength, sizeBytes - offset);
|
|
@@ -17074,7 +17177,7 @@ function stableJson(candidate) {
|
|
|
17074
17177
|
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`).join(",")}}`;
|
|
17075
17178
|
}
|
|
17076
17179
|
function activationFingerprint(identity) {
|
|
17077
|
-
return
|
|
17180
|
+
return createHash8("sha256").update(stableJson({
|
|
17078
17181
|
project_ref: identity.projectRef,
|
|
17079
17182
|
deployment_id: identity.deploymentId,
|
|
17080
17183
|
release_id: identity.releaseId,
|
|
@@ -17091,7 +17194,7 @@ function publicMutationReceipt(candidate, expected) {
|
|
|
17091
17194
|
return null;
|
|
17092
17195
|
const envelope = candidate;
|
|
17093
17196
|
const mutation = safeMutationStatus(envelope.mutation);
|
|
17094
|
-
if (!mutation || !
|
|
17197
|
+
if (!mutation || !exactKeys2(envelope, ["project_ref", "mutation"]) || envelope.project_ref !== expected.projectRef || mutation.project_ref !== expected.projectRef || mutation.mutation_id !== expected.mutationId || mutation.resource_key !== expected.resourceKey || mutation.request_fingerprint !== expected.requestFingerprint)
|
|
17095
17198
|
return null;
|
|
17096
17199
|
return {
|
|
17097
17200
|
operation: mutation.operation,
|
|
@@ -17106,14 +17209,14 @@ function activationReceipt(candidate, input) {
|
|
|
17106
17209
|
const record = candidate;
|
|
17107
17210
|
const release = releaseRecord(record.release);
|
|
17108
17211
|
const mutation = record.mutation;
|
|
17109
|
-
if (!release || !mutation || !
|
|
17212
|
+
if (!release || !mutation || !exactKeys2(record, [
|
|
17110
17213
|
"project_ref",
|
|
17111
17214
|
"deployment_id",
|
|
17112
17215
|
"active_release_id",
|
|
17113
17216
|
"activation_id",
|
|
17114
17217
|
"release",
|
|
17115
17218
|
"mutation"
|
|
17116
|
-
]) || !
|
|
17219
|
+
]) || !exactKeys2(mutation, ["mutation_id", "status", "replayed"]) || record.project_ref !== input.projectRef || record.deployment_id !== input.deploymentId || record.active_release_id !== input.releaseId || record.activation_id !== input.mutationId || release.project_ref !== input.projectRef || release.deployment_id !== input.deploymentId || release.release_id !== input.releaseId || mutation.mutation_id !== input.mutationId || mutation.status !== "succeeded" || typeof mutation.replayed !== "boolean")
|
|
17117
17220
|
return null;
|
|
17118
17221
|
return release;
|
|
17119
17222
|
}
|
|
@@ -17370,7 +17473,7 @@ async function readPreviousFrontendRelease(http, projectRef, deploymentId) {
|
|
|
17370
17473
|
const activeActivationId = value ? nullableIdentity(value.active_activation_id, MUTATION_ID_PATTERN2) : undefined;
|
|
17371
17474
|
const previousActivationId = value ? nullableIdentity(value.previous_activation_id, MUTATION_ID_PATTERN2) : undefined;
|
|
17372
17475
|
const previous = value?.previous_release === null ? null : releaseRecord(value?.previous_release);
|
|
17373
|
-
if (!response.ok || response.status !== 200 || !value || !
|
|
17476
|
+
if (!response.ok || response.status !== 200 || !value || !exactKeys2(value, [
|
|
17374
17477
|
"schema",
|
|
17375
17478
|
"project_ref",
|
|
17376
17479
|
"deployment_id",
|
|
@@ -29564,7 +29667,7 @@ import {
|
|
|
29564
29667
|
} from "@supacloud/db";
|
|
29565
29668
|
|
|
29566
29669
|
// src/shared/tools/db-workflow.ts
|
|
29567
|
-
import { createHash as
|
|
29670
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
29568
29671
|
import { lstat as lstat4, mkdir as mkdir6, mkdtemp, readFile as readFile5, readdir as readdir6, rename as rename3, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
|
|
29569
29672
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
29570
29673
|
import { dirname as dirname11, isAbsolute as isAbsolute7, join as join15, relative as relative8, resolve as resolve20 } from "node:path";
|
|
@@ -29853,7 +29956,7 @@ function resolveOfficialSupabaseCommand2(workdir, environment = process.env) {
|
|
|
29853
29956
|
}
|
|
29854
29957
|
|
|
29855
29958
|
// src/shared/tools/db-workflow.ts
|
|
29856
|
-
var hash4 = (content) =>
|
|
29959
|
+
var hash4 = (content) => createHash9("sha256").update(content).digest("hex");
|
|
29857
29960
|
var result = (value, isError = false) => ({
|
|
29858
29961
|
isError,
|
|
29859
29962
|
content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
|
|
@@ -30957,13 +31060,13 @@ var MUTATION_TOOL_SCHEMA = {
|
|
|
30957
31060
|
};
|
|
30958
31061
|
|
|
30959
31062
|
// src/shared/tools/release-tools.ts
|
|
30960
|
-
import { createHash as
|
|
31063
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
30961
31064
|
import { existsSync as existsSync14, mkdirSync as mkdirSync5, readdirSync as readdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "node:fs";
|
|
30962
31065
|
import { dirname as dirname12, join as join17, relative as relative9, resolve as resolve23 } from "node:path";
|
|
30963
31066
|
var SAFE_PROJECT_REF = /^[A-Za-z0-9_-]{1,64}$/;
|
|
30964
31067
|
var SAFE_ENVIRONMENT_ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
30965
31068
|
var BACKUP_ID = /^logical-full_[A-Za-z0-9_-]{1,64}_[a-f0-9]{32}$/;
|
|
30966
|
-
var
|
|
31069
|
+
var SHA2562 = /^[a-f0-9]{64}$/;
|
|
30967
31070
|
var SAFE_DATABASE = /^[^\u0000-\u001f\u007f]{1,128}$/;
|
|
30968
31071
|
var INVENTORY_MAX_BYTES = 1024 * 1024;
|
|
30969
31072
|
var MUTATION_MAX_BYTES = 64 * 1024;
|
|
@@ -30993,7 +31096,7 @@ function backupBelongsToProject(backupId, projectRef) {
|
|
|
30993
31096
|
return BACKUP_ID.test(backupId) && backupId.startsWith(prefix) && /^[a-f0-9]{32}$/.test(backupId.slice(prefix.length));
|
|
30994
31097
|
}
|
|
30995
31098
|
function verifiedBackup(value, projectRef) {
|
|
30996
|
-
if (!isRecord6(value) || typeof value.backup_id !== "string" || !backupBelongsToProject(value.backup_id, projectRef) || value.project_ref !== projectRef || typeof value.database !== "string" || !SAFE_DATABASE.test(value.database) || value.kind !== "logical-full" || !canonicalTimestamp4(value.created_at) || !canonicalTimestamp4(value.completed_at) || new Date(value.completed_at).valueOf() < new Date(value.created_at).valueOf() || typeof value.bytes !== "number" || !Number.isSafeInteger(value.bytes) || value.bytes <= 0 || typeof value.sha256 !== "string" || !
|
|
31099
|
+
if (!isRecord6(value) || typeof value.backup_id !== "string" || !backupBelongsToProject(value.backup_id, projectRef) || value.project_ref !== projectRef || typeof value.database !== "string" || !SAFE_DATABASE.test(value.database) || value.kind !== "logical-full" || !canonicalTimestamp4(value.created_at) || !canonicalTimestamp4(value.completed_at) || new Date(value.completed_at).valueOf() < new Date(value.created_at).valueOf() || typeof value.bytes !== "number" || !Number.isSafeInteger(value.bytes) || value.bytes <= 0 || typeof value.sha256 !== "string" || !SHA2562.test(value.sha256))
|
|
30997
31100
|
return null;
|
|
30998
31101
|
return {
|
|
30999
31102
|
backup_id: value.backup_id,
|
|
@@ -31039,7 +31142,7 @@ function restoreRequest(projectRef, backupId, expectedSha256, restoreConfirmatio
|
|
|
31039
31142
|
if (typeof backupId !== "string" || !backupBelongsToProject(backupId, projectRef)) {
|
|
31040
31143
|
throw new Error("'backup_id' must identify a logical-full backup for 'ref'");
|
|
31041
31144
|
}
|
|
31042
|
-
if (typeof expectedSha256 !== "string" || !
|
|
31145
|
+
if (typeof expectedSha256 !== "string" || !SHA2562.test(expectedSha256)) {
|
|
31043
31146
|
throw new Error("'expected_sha256' must be a lowercase SHA-256 digest");
|
|
31044
31147
|
}
|
|
31045
31148
|
const confirmation = `RESTORE_PROJECT:${projectRef}:${backupId}:${expectedSha256}`;
|
|
@@ -31200,7 +31303,7 @@ function genericScopedReleaseScopeJson(scope) {
|
|
|
31200
31303
|
return JSON.stringify(canonicalValue(scope));
|
|
31201
31304
|
}
|
|
31202
31305
|
function genericScopedReleaseScopeSha256(scope) {
|
|
31203
|
-
return
|
|
31306
|
+
return createHash10("sha256").update(genericScopedReleaseScopeJson(scope)).digest("hex");
|
|
31204
31307
|
}
|
|
31205
31308
|
function scopeEnvironmentId(scope, action) {
|
|
31206
31309
|
const environmentId = isRecord6(scope) ? scope.targetEnvironmentId : undefined;
|
|
@@ -31469,6 +31572,73 @@ function handleScopeInspect(args) {
|
|
|
31469
31572
|
}
|
|
31470
31573
|
});
|
|
31471
31574
|
}
|
|
31575
|
+
function releaseComponentView(component) {
|
|
31576
|
+
if (component.kind === "migration")
|
|
31577
|
+
return { kind: component.kind, name: component.name };
|
|
31578
|
+
if (component.kind === "function") {
|
|
31579
|
+
return { kind: component.kind, slug: component.slug, artifact_sha256: component.artifactSha256 };
|
|
31580
|
+
}
|
|
31581
|
+
return { kind: component.kind, artifact_sha256: component.artifactSha256 };
|
|
31582
|
+
}
|
|
31583
|
+
function handleContractPlan(args, fallbackRef) {
|
|
31584
|
+
const cwd = typeof args.cwd === "string" && args.cwd.trim() ? resolve23(args.cwd.trim()) : process.cwd();
|
|
31585
|
+
const requestedRef = typeof args.ref === "string" && args.ref.trim() || fallbackRef;
|
|
31586
|
+
if (!requestedRef || !validProjectRef(requestedRef)) {
|
|
31587
|
+
throw new Error("'ref' is required for contract_plan and must identify one project");
|
|
31588
|
+
}
|
|
31589
|
+
const requestedEnvironmentId = requestedScopeEnvironment(args, "contract_plan");
|
|
31590
|
+
const file = typeof args.file === "string" && args.file.trim() ? args.file.trim() : "supacloud.release.json";
|
|
31591
|
+
const filePath = resolve23(cwd, file);
|
|
31592
|
+
if (!existsSync14(filePath)) {
|
|
31593
|
+
throw new Error(`Release contract not found: ${file}`);
|
|
31594
|
+
}
|
|
31595
|
+
let parsedValue;
|
|
31596
|
+
try {
|
|
31597
|
+
parsedValue = JSON.parse(readFileSync11(filePath, "utf8"));
|
|
31598
|
+
} catch {
|
|
31599
|
+
throw new Error(`Release contract '${file}' is not valid JSON`);
|
|
31600
|
+
}
|
|
31601
|
+
const scope = parseReleaseScope(parsedValue);
|
|
31602
|
+
if (scope.targetProjectRef !== requestedRef) {
|
|
31603
|
+
throw new Error(`Release contract target project '${scope.targetProjectRef}' does not match requested project '${requestedRef}'`);
|
|
31604
|
+
}
|
|
31605
|
+
if (scope.targetEnvironmentId !== requestedEnvironmentId) {
|
|
31606
|
+
throw new Error(`Release contract target environment '${scope.targetEnvironmentId}' does not match requested environment '${requestedEnvironmentId}'`);
|
|
31607
|
+
}
|
|
31608
|
+
const git = gitCommitInfo(cwd);
|
|
31609
|
+
const sourceMatchesHead = git.head !== null && scope.sourceCommit === git.head;
|
|
31610
|
+
const sourceMatchesOriginMain = git.originMain !== null && scope.sourceCommit === git.originMain;
|
|
31611
|
+
if (args.require_current_source === true && !sourceMatchesHead) {
|
|
31612
|
+
throw new Error(`Release contract source commit '${scope.sourceCommit}' does not match current HEAD '${git.head ?? "unavailable"}'`);
|
|
31613
|
+
}
|
|
31614
|
+
const counts = scope.components.reduce((result, component) => {
|
|
31615
|
+
result[component.kind] += 1;
|
|
31616
|
+
return result;
|
|
31617
|
+
}, { migration: 0, function: 0, web: 0 });
|
|
31618
|
+
return releaseControlSuccess("release.contract_plan", {
|
|
31619
|
+
plan_schema: "supacloud.release-plan.v1",
|
|
31620
|
+
file: relative9(cwd, filePath),
|
|
31621
|
+
release_id: scope.releaseId,
|
|
31622
|
+
scope_sha256: releaseScopeSha256(scope),
|
|
31623
|
+
source_commit: scope.sourceCommit,
|
|
31624
|
+
target_project_ref: scope.targetProjectRef,
|
|
31625
|
+
target_environment_id: scope.targetEnvironmentId,
|
|
31626
|
+
components: scope.components.map(releaseComponentView),
|
|
31627
|
+
component_counts: counts,
|
|
31628
|
+
checks: {
|
|
31629
|
+
source_commit_matches_head: sourceMatchesHead,
|
|
31630
|
+
source_commit_matches_origin_main: sourceMatchesOriginMain,
|
|
31631
|
+
target_project_ref_matches_requested: true,
|
|
31632
|
+
target_environment_id_matches_requested: true,
|
|
31633
|
+
components_unique_and_sorted: true
|
|
31634
|
+
},
|
|
31635
|
+
current_head: git.head,
|
|
31636
|
+
origin_main: git.originMain,
|
|
31637
|
+
source_is_current: sourceMatchesHead,
|
|
31638
|
+
read_only: true,
|
|
31639
|
+
mutations_performed: false
|
|
31640
|
+
});
|
|
31641
|
+
}
|
|
31472
31642
|
function parseCommaList(value) {
|
|
31473
31643
|
if (!value)
|
|
31474
31644
|
return [];
|
|
@@ -31525,7 +31695,7 @@ function handleScopeCreate(args, fallbackRef) {
|
|
|
31525
31695
|
});
|
|
31526
31696
|
}
|
|
31527
31697
|
function registerReleaseTools(server, http, options = {}) {
|
|
31528
|
-
const localActions = ["scope_inspect", "scope_rebind", "scope_create"];
|
|
31698
|
+
const localActions = ["scope_inspect", "scope_rebind", "scope_create", "contract_plan"];
|
|
31529
31699
|
const remoteActions = [
|
|
31530
31700
|
"logical_backup_list",
|
|
31531
31701
|
"logical_backup_status",
|
|
@@ -31540,18 +31710,19 @@ function registerReleaseTools(server, http, options = {}) {
|
|
|
31540
31710
|
registerTool(server, "release", "Verified release controls. Scope management actions operate locally; management actions use the Management API; release canary stage/disable replay additionally require the selected project's SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY.", {
|
|
31541
31711
|
action: withDescription(stringEnum2(allActions), "Release control action"),
|
|
31542
31712
|
ref: optional(String2(), options.projectRef ? "Optional override when not auto-linked" : "Project ref"),
|
|
31543
|
-
file: optional(String2(), "[scope_inspect/scope_rebind/scope_create] Path to release scope JSON file"),
|
|
31713
|
+
file: optional(String2(), "[scope_inspect/scope_rebind/scope_create/contract_plan] Path to release scope or release contract JSON file"),
|
|
31544
31714
|
files: optional(String2(), "[scope_rebind] Comma-separated list of release scope JSON files"),
|
|
31545
31715
|
dir: optional(String2(), "[scope_rebind] Directory containing release scope JSON files (defaults to supacloud/fa/release-scopes, release-scopes, or scopes)"),
|
|
31546
31716
|
task: optional(String2(), "[scope_rebind/scope_create] Task ID or prefix filter for scope files"),
|
|
31547
31717
|
base_commit: optional(String2(), "[scope_rebind/scope_create] Target 40-character base commit SHA or git ref (defaults to origin/main -> main -> HEAD^ -> HEAD)"),
|
|
31548
|
-
environment_id: optional(String2(), "[scope_inspect/scope_rebind/scope_create] Exact target deployment environment"),
|
|
31718
|
+
environment_id: optional(String2(), "[scope_inspect/scope_rebind/scope_create/contract_plan] Exact target deployment environment"),
|
|
31549
31719
|
functions: optional(String2(), "[scope_create] Comma-separated list of functions to deploy"),
|
|
31550
31720
|
excluded_functions: optional(String2(), "[scope_create] Comma-separated list of functions to exclude"),
|
|
31551
31721
|
migrations: optional(String2(), "[scope_create] Comma-separated list of migrations to apply"),
|
|
31552
31722
|
web: optional(Boolean2(), "[scope_create] Whether web assets are included in release scope"),
|
|
31553
31723
|
dry_run: optional(Boolean2(), "[scope_rebind] Perform calculation and validation without writing files"),
|
|
31554
|
-
cwd: optional(String2(), "[scope_inspect/scope_rebind/scope_create] Base directory for relative file and git resolution"),
|
|
31724
|
+
cwd: optional(String2(), "[scope_inspect/scope_rebind/scope_create/contract_plan] Base directory for relative file and git resolution"),
|
|
31725
|
+
require_current_source: optional(Boolean2(), "[contract_plan] Fail unless the contract sourceCommit equals current HEAD"),
|
|
31555
31726
|
backup_id: optional(String2(), "[logical_backup_create/status/restore] Stable project-bound backup ID; create generates one when omitted"),
|
|
31556
31727
|
expected_sha256: optional(String2(), "[logical_backup_restore] Exact lowercase SHA-256 from the selected project inventory"),
|
|
31557
31728
|
restore_confirmation: optional(String2(), "[logical_backup_restore] Exact RESTORE_PROJECT:<ref>:<backup_id>:<sha256> confirmation"),
|
|
@@ -31571,6 +31742,9 @@ function registerReleaseTools(server, http, options = {}) {
|
|
|
31571
31742
|
if (action === "scope_create") {
|
|
31572
31743
|
return handleScopeCreate(args, options.projectRef);
|
|
31573
31744
|
}
|
|
31745
|
+
if (action === "contract_plan") {
|
|
31746
|
+
return handleContractPlan(args, options.projectRef);
|
|
31747
|
+
}
|
|
31574
31748
|
if (options.localOnly || !http) {
|
|
31575
31749
|
return {
|
|
31576
31750
|
isError: true,
|
|
@@ -31744,7 +31918,7 @@ function registerReleaseTools(server, http, options = {}) {
|
|
|
31744
31918
|
// src/shared/tools/deploy-tools.ts
|
|
31745
31919
|
import { access, lstat as lstat5, mkdtemp as mkdtemp2, opendir, readFile as readFile6, rm as rm3, writeFile as writeFile6 } from "node:fs/promises";
|
|
31746
31920
|
import { spawn as spawn2 } from "node:child_process";
|
|
31747
|
-
import { createHash as
|
|
31921
|
+
import { createHash as createHash11 } from "node:crypto";
|
|
31748
31922
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
31749
31923
|
import { basename as basename8, dirname as dirname13, join as join18, relative as relative10, resolve as resolve24, sep as sep5 } from "node:path";
|
|
31750
31924
|
|
|
@@ -31840,7 +32014,7 @@ var deployToolSchema = {
|
|
|
31840
32014
|
status: optional(String2({ format: "uuid" }), "Read a backend deployment status without publishing"),
|
|
31841
32015
|
json: optional(Boolean2(), "Print only the final JSON result")
|
|
31842
32016
|
};
|
|
31843
|
-
function
|
|
32017
|
+
function record5(candidate) {
|
|
31844
32018
|
return candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
|
|
31845
32019
|
}
|
|
31846
32020
|
function requiredString(candidate, field) {
|
|
@@ -31850,7 +32024,7 @@ function requiredString(candidate, field) {
|
|
|
31850
32024
|
return candidate.trim();
|
|
31851
32025
|
}
|
|
31852
32026
|
function frontendDeployment(candidate) {
|
|
31853
|
-
const value =
|
|
32027
|
+
const value = record5(candidate);
|
|
31854
32028
|
if (!value)
|
|
31855
32029
|
throw new Error("Invalid frontend deployment response");
|
|
31856
32030
|
return {
|
|
@@ -31920,7 +32094,7 @@ function optionalStringArray(value, field) {
|
|
|
31920
32094
|
});
|
|
31921
32095
|
}
|
|
31922
32096
|
function deployTargetConfig(candidate, name) {
|
|
31923
|
-
const value =
|
|
32097
|
+
const value = record5(candidate);
|
|
31924
32098
|
if (!value || value.type !== "frontend" && value.type !== "edge_function") {
|
|
31925
32099
|
throw new Error(`supacloud.json targets.${name}.type must be 'frontend' or 'edge_function'`);
|
|
31926
32100
|
}
|
|
@@ -31949,10 +32123,10 @@ async function readDeployConfig(configRoot) {
|
|
|
31949
32123
|
const candidate = await readJson(configPath);
|
|
31950
32124
|
if (candidate === null)
|
|
31951
32125
|
return {};
|
|
31952
|
-
const config =
|
|
31953
|
-
const frontend =
|
|
31954
|
-
const application =
|
|
31955
|
-
const targetValues =
|
|
32126
|
+
const config = record5(candidate);
|
|
32127
|
+
const frontend = record5(config?.frontend);
|
|
32128
|
+
const application = record5(config?.application);
|
|
32129
|
+
const targetValues = record5(config?.targets);
|
|
31956
32130
|
if (!config || config.frontend !== undefined && !frontend || config.application !== undefined && !application || config.targets !== undefined && !targetValues) {
|
|
31957
32131
|
throw new Error("supacloud.json must contain object-valued 'application', 'frontend' or 'targets' configuration");
|
|
31958
32132
|
}
|
|
@@ -31992,7 +32166,7 @@ async function readDeployConfig(configRoot) {
|
|
|
31992
32166
|
};
|
|
31993
32167
|
}
|
|
31994
32168
|
async function packageName(projectDirectory) {
|
|
31995
|
-
const candidate =
|
|
32169
|
+
const candidate = record5(await readJson(join18(projectDirectory, "package.json")));
|
|
31996
32170
|
return typeof candidate?.name === "string" && candidate.name.trim() ? candidate.name.trim() : null;
|
|
31997
32171
|
}
|
|
31998
32172
|
async function pathExists(path) {
|
|
@@ -32018,8 +32192,8 @@ async function hasProjectReleaseManifest(root) {
|
|
|
32018
32192
|
return false;
|
|
32019
32193
|
}
|
|
32020
32194
|
async function defaultBuildCommand(projectDirectory, configRoot) {
|
|
32021
|
-
const packageJson =
|
|
32022
|
-
const scripts =
|
|
32195
|
+
const packageJson = record5(await readJson(join18(projectDirectory, "package.json")));
|
|
32196
|
+
const scripts = record5(packageJson?.scripts);
|
|
32023
32197
|
if (typeof scripts?.build !== "string" || !scripts.build.trim()) {
|
|
32024
32198
|
throw new Error("No build command was configured and package.json has no build script");
|
|
32025
32199
|
}
|
|
@@ -32041,7 +32215,7 @@ async function defaultBuildCommand(projectDirectory, configRoot) {
|
|
|
32041
32215
|
return "npm run build";
|
|
32042
32216
|
}
|
|
32043
32217
|
function deploymentList(candidate) {
|
|
32044
|
-
const values = Array.isArray(candidate) ? candidate : Array.isArray(
|
|
32218
|
+
const values = Array.isArray(candidate) ? candidate : Array.isArray(record5(candidate)?.deployments) ? record5(candidate)?.deployments : null;
|
|
32045
32219
|
if (!values)
|
|
32046
32220
|
throw new Error("Invalid frontend deployment list response");
|
|
32047
32221
|
return values.map(frontendDeployment);
|
|
@@ -32201,9 +32375,9 @@ async function collectArchiveFiles(root) {
|
|
|
32201
32375
|
throw new Error(`Frontend output exceeds ${MAX_SOURCE_BYTES} bytes`);
|
|
32202
32376
|
const archivePath = relative10(root, path).split(sep5).join("/");
|
|
32203
32377
|
files.set(archivePath, data);
|
|
32204
|
-
digests.push({ path: archivePath, size: data.byteLength, sha256:
|
|
32378
|
+
digests.push({ path: archivePath, size: data.byteLength, sha256: createHash11("sha256").update(data).digest("hex") });
|
|
32205
32379
|
}
|
|
32206
|
-
const tree =
|
|
32380
|
+
const tree = createHash11("sha256");
|
|
32207
32381
|
for (const file of digests.sort((left, right) => left.path.localeCompare(right.path))) {
|
|
32208
32382
|
const pathBytes = Buffer.from(file.path, "utf8");
|
|
32209
32383
|
const frame = Buffer.alloc(12);
|
|
@@ -32219,7 +32393,7 @@ async function createCollectedFrontendArchive(outputDirectory, collected) {
|
|
|
32219
32393
|
try {
|
|
32220
32394
|
const archiveBytes = await createFrontendTarZstd(collected.files);
|
|
32221
32395
|
await writeFile6(archivePath, archiveBytes);
|
|
32222
|
-
const sha256 =
|
|
32396
|
+
const sha256 = createHash11("sha256").update(archiveBytes).digest("hex");
|
|
32223
32397
|
return {
|
|
32224
32398
|
archivePath,
|
|
32225
32399
|
sha256,
|
|
@@ -32341,7 +32515,7 @@ function registerDeployTools(server, http, options = {}) {
|
|
|
32341
32515
|
import { existsSync as existsSync15 } from "node:fs";
|
|
32342
32516
|
import { readFile as readFile7 } from "node:fs/promises";
|
|
32343
32517
|
import { readdir as readdir7 } from "node:fs/promises";
|
|
32344
|
-
import { createHash as
|
|
32518
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
32345
32519
|
import { join as join19, resolve as resolve25 } from "node:path";
|
|
32346
32520
|
import { compileProject as compileProject2 } from "@supacloud/compiler";
|
|
32347
32521
|
var SAFE_TOKEN = /^[A-Za-z0-9._:@/+,-]+$/;
|
|
@@ -32440,7 +32614,7 @@ async function sourceFingerprint(root) {
|
|
|
32440
32614
|
}
|
|
32441
32615
|
};
|
|
32442
32616
|
await visit(root);
|
|
32443
|
-
const hash =
|
|
32617
|
+
const hash = createHash12("sha256");
|
|
32444
32618
|
for (const file of files) {
|
|
32445
32619
|
hash.update(file.slice(root.length).replace(/\\/g, "/"));
|
|
32446
32620
|
hash.update(await readFile7(file));
|
|
@@ -32840,7 +33014,7 @@ function textResult3(text, isError = false) {
|
|
|
32840
33014
|
function jsonResult(value, isError = false) {
|
|
32841
33015
|
return textResult3(JSON.stringify(value, null, 2), isError);
|
|
32842
33016
|
}
|
|
32843
|
-
function
|
|
33017
|
+
function record6(value) {
|
|
32844
33018
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
32845
33019
|
}
|
|
32846
33020
|
function packageDependencies(manifest) {
|
|
@@ -32863,12 +33037,12 @@ async function readPackageManifest(root) {
|
|
|
32863
33037
|
return {};
|
|
32864
33038
|
try {
|
|
32865
33039
|
const parsed = JSON.parse(await readFile8(path, "utf8"));
|
|
32866
|
-
const value =
|
|
33040
|
+
const value = record6(parsed);
|
|
32867
33041
|
if (!value)
|
|
32868
33042
|
throw new Error("package.json must contain an object");
|
|
32869
|
-
const scripts =
|
|
32870
|
-
const dependencies =
|
|
32871
|
-
const devDependencies =
|
|
33043
|
+
const scripts = record6(value.scripts);
|
|
33044
|
+
const dependencies = record6(value.dependencies);
|
|
33045
|
+
const devDependencies = record6(value.devDependencies);
|
|
32872
33046
|
return {
|
|
32873
33047
|
scripts: scripts ?? undefined,
|
|
32874
33048
|
dependencies: dependencies ?? undefined,
|
|
@@ -33026,9 +33200,9 @@ async function inspectProfile(profile) {
|
|
|
33026
33200
|
return "{}";
|
|
33027
33201
|
throw error;
|
|
33028
33202
|
});
|
|
33029
|
-
const config =
|
|
33030
|
-
const hasFrontend =
|
|
33031
|
-
const deploy = deployMissing(context,
|
|
33203
|
+
const config = record6(JSON.parse(configText));
|
|
33204
|
+
const hasFrontend = record6(config?.frontend) !== null || Object.values(record6(config?.targets) ?? {}).some((target) => record6(target)?.type === "frontend");
|
|
33205
|
+
const deploy = deployMissing(context, record6(config?.application), hasFrontend);
|
|
33032
33206
|
return {
|
|
33033
33207
|
name: profile.name,
|
|
33034
33208
|
path: profile.path,
|
|
@@ -33247,7 +33421,7 @@ async function runProjectDeployment(http, args) {
|
|
|
33247
33421
|
// package.json
|
|
33248
33422
|
var package_default = {
|
|
33249
33423
|
name: "@supacloud/cli",
|
|
33250
|
-
version: "0.
|
|
33424
|
+
version: "0.111.0",
|
|
33251
33425
|
description: "Project-scoped CLI for SupaCloud users",
|
|
33252
33426
|
type: "module",
|
|
33253
33427
|
main: "./dist/index.js",
|
|
@@ -33300,13 +33474,13 @@ var package_default = {
|
|
|
33300
33474
|
},
|
|
33301
33475
|
dependencies: {
|
|
33302
33476
|
"@supacloud/compiler": "^0.52.0",
|
|
33303
|
-
"@supacloud/delivery": "0.
|
|
33477
|
+
"@supacloud/delivery": "0.23.0",
|
|
33304
33478
|
"@supacloud/db": "0.20.1",
|
|
33305
33479
|
typebox: "1.4.2"
|
|
33306
33480
|
},
|
|
33307
33481
|
overrides: {
|
|
33308
33482
|
"@supacloud/contracts": "0.15.1",
|
|
33309
|
-
"@supacloud/delivery": "0.
|
|
33483
|
+
"@supacloud/delivery": "0.23.0",
|
|
33310
33484
|
"@graphql-tools/utils": "^12.0.4"
|
|
33311
33485
|
},
|
|
33312
33486
|
devDependencies: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supacloud/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.111.0",
|
|
4
4
|
"description": "Project-scoped CLI for SupaCloud users",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -53,13 +53,13 @@
|
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@supacloud/compiler": "^0.52.0",
|
|
56
|
-
"@supacloud/delivery": "0.
|
|
56
|
+
"@supacloud/delivery": "0.23.0",
|
|
57
57
|
"@supacloud/db": "0.20.1",
|
|
58
58
|
"typebox": "1.4.2"
|
|
59
59
|
},
|
|
60
60
|
"overrides": {
|
|
61
61
|
"@supacloud/contracts": "0.15.1",
|
|
62
|
-
"@supacloud/delivery": "0.
|
|
62
|
+
"@supacloud/delivery": "0.23.0",
|
|
63
63
|
"@graphql-tools/utils": "^12.0.4"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|