@supacloud/cli 0.80.0 → 0.82.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 +17 -0
- package/dist/index.js +1201 -325
- package/dist/shared/mutation-protocol.d.ts +1 -0
- package/dist/shared/tools/app-tools.d.ts +20 -8
- package/dist/shared/tools/application-promotion-help.test.d.ts +1 -0
- package/dist/shared/tools/application-promotion.test.d.ts +1 -0
- package/dist/shared/tools/application-release-transfer.d.ts +8 -0
- package/dist/shared/tools/application-schemas.d.ts +62 -0
- package/dist/shared/tools/application-tools.d.ts +68 -1
- package/dist/shared/tools/application-workflow-schemas.d.ts +236 -0
- package/dist/shared/tools/release-backup-http-status.test.d.ts +1 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -178699,7 +178699,17 @@ function outcomeUnknownGuidance(text) {
|
|
|
178699
178699
|
}
|
|
178700
178700
|
if (typeof payload !== "object" || payload === null || !("ok" in payload) || payload.ok !== false || !("error" in payload) || typeof payload.error !== "object" || payload.error === null || !("code" in payload.error) || payload.error.code !== "OUTCOME_UNKNOWN")
|
|
178701
178701
|
return null;
|
|
178702
|
-
|
|
178702
|
+
const data = payload;
|
|
178703
|
+
const { project_ref: ref, application_id: id, environment_id: environment } = data;
|
|
178704
|
+
const safeScope = typeof ref === "string" && /^[a-z0-9-]{1,20}$/.test(ref) && typeof id === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(id) && typeof environment === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(environment);
|
|
178705
|
+
let command;
|
|
178706
|
+
if (safeScope && (data.operation === "applications.promote_application" || data.operation === "applications.reconcile_promotion") && typeof data.mutation_id === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(data.mutation_id)) {
|
|
178707
|
+
command = `supacloud-cli app promote-status --ref ${ref} --id ${id} --environment_id ${environment} --mutation_id ${data.mutation_id}`;
|
|
178708
|
+
} else if (safeScope && (data.operation === "applications.create_preview" || data.operation === "applications.cleanup_preview")) {
|
|
178709
|
+
command = `supacloud-cli app preview-list --ref ${ref} --id ${id} --environment_id ${environment}`;
|
|
178710
|
+
}
|
|
178711
|
+
return "OUTCOME_UNKNOWN:操作结果无法确认,服务端可能已经完成操作,但客户端未收到可验证的最终结果。" + "请先查询当前状态或操作回执,再决定是否重试;不要直接重复提交。" + (command ? `
|
|
178712
|
+
保持原 --env/--env-file 选择,先查询:${command}` : "");
|
|
178703
178713
|
}
|
|
178704
178714
|
|
|
178705
178715
|
// src/shared/cli.ts
|
|
@@ -179093,20 +179103,8 @@ function resolveSupaCloudContext(env = process.env, cwd = process.cwd(), selecti
|
|
|
179093
179103
|
var ACTION_POLICY = {
|
|
179094
179104
|
deploy: { write: ["deploy"] },
|
|
179095
179105
|
applications: {
|
|
179096
|
-
read: ["list_releases", "get_release", "get_runtime", "get_deployment_evidence", "get_configuration", "get_rollback_snapshot", "get_history", "get_deploy_plan", "
|
|
179097
|
-
write: [
|
|
179098
|
-
"upload_release",
|
|
179099
|
-
"put_configuration",
|
|
179100
|
-
"activate_release",
|
|
179101
|
-
"deploy_release",
|
|
179102
|
-
"rollback_release",
|
|
179103
|
-
"reconcile_activation",
|
|
179104
|
-
"retire_activation",
|
|
179105
|
-
"create_preview",
|
|
179106
|
-
"list_previews",
|
|
179107
|
-
"get_preview",
|
|
179108
|
-
"cleanup_preview"
|
|
179109
|
-
]
|
|
179106
|
+
read: ["list_releases", "get_release", "get_runtime", "get_deployment_evidence", "get_configuration", "get_release_transfer_plan", "get_promotion_plan", "get_rollback_snapshot", "get_history", "get_deploy_plan", "get_promotion_status", "get_preview_plan", "logs"],
|
|
179107
|
+
write: ["upload_release", "put_configuration", "transfer_release", "promote_application", "reconcile_promotion", "create_preview", "list_previews", "get_preview", "cleanup_preview", "activate_release", "deploy_release", "rollback_release", "reconcile_activation", "retire_activation"]
|
|
179110
179108
|
},
|
|
179111
179109
|
project: {
|
|
179112
179110
|
read: ["get", "endpoints", "health", "logs", "api_keys", "settings", "tasks", "task_detail", "task_stats", "dlq", "background_settings"],
|
|
@@ -179178,7 +179176,7 @@ var ACTION_POLICY = {
|
|
|
179178
179176
|
scheduled_functions: { read: ["list", "get"], write: ["create", "update", "delete"] },
|
|
179179
179177
|
mutations: { read: ["status"] },
|
|
179180
179178
|
release: {
|
|
179181
|
-
read: ["logical_backup_list", "postgrest_status"],
|
|
179179
|
+
read: ["logical_backup_list", "logical_backup_status", "postgrest_status"],
|
|
179182
179180
|
local: ["scope_inspect", "scope_rebind", "scope_create"],
|
|
179183
179181
|
write: ["logical_backup_create", "logical_backup_restore", "postgrest_restart", "release_canary_fixture_stage_replay", "release_canary_fixture_disable_replay"]
|
|
179184
179182
|
},
|
|
@@ -179201,8 +179199,8 @@ var ACTION_POLICY = {
|
|
|
179201
179199
|
ai: { local: ["show_skill", "install_skill"] },
|
|
179202
179200
|
app: {
|
|
179203
179201
|
local: ["init", "generate", "dev", "watch", "verify-plan", "compile", "check", "graph", "explain", "export-tools", "context", "doctor", "fix", "plan", "build"],
|
|
179204
|
-
read: ["status", "logs", "rollback-plan", "history", "deploy-plan", "diff", "preview-plan"],
|
|
179205
|
-
write: ["upload", "configure", "deploy", "rollback", "reconcile", "retire", "preview", "previews", "preview-status", "preview-cleanup"]
|
|
179202
|
+
read: ["status", "logs", "transfer-plan", "promote-plan", "rollback-plan", "history", "deploy-plan", "diff", "promote-status", "preview-plan"],
|
|
179203
|
+
write: ["upload", "configure", "transfer", "promote", "promote-reconcile", "deploy", "rollback", "reconcile", "retire", "preview", "previews", "preview-list", "preview-create", "preview-status", "preview-cleanup"]
|
|
179206
179204
|
},
|
|
179207
179205
|
db: {
|
|
179208
179206
|
local: ["context", "lint", "explain", "diff", "role_sql"],
|
|
@@ -180419,6 +180417,52 @@ function parseApplicationActivationHistory(value) {
|
|
|
180419
180417
|
}
|
|
180420
180418
|
return structuredClone(value);
|
|
180421
180419
|
}
|
|
180420
|
+
// node_modules/@supacloud/delivery/src/application-release-transfer.ts
|
|
180421
|
+
var projectRef = String2({ pattern: "^[A-Za-z0-9_-]{1,20}$" });
|
|
180422
|
+
var source = _Object_({
|
|
180423
|
+
project_ref: projectRef,
|
|
180424
|
+
release_id: ApplicationReleaseIdSchema,
|
|
180425
|
+
manifest_sha256: ApplicationReleaseIdSchema
|
|
180426
|
+
}, { additionalProperties: false });
|
|
180427
|
+
var identity = {
|
|
180428
|
+
project_ref: projectRef,
|
|
180429
|
+
application_id: ApplicationIdSchema,
|
|
180430
|
+
source,
|
|
180431
|
+
candidate_release_id: ApplicationReleaseIdSchema
|
|
180432
|
+
};
|
|
180433
|
+
var ApplicationReleaseTransferPlanSchema = _Object_({
|
|
180434
|
+
schema: Literal("supacloud.application-release-transfer-plan.v1"),
|
|
180435
|
+
...identity,
|
|
180436
|
+
action: Union([Literal("materialize"), Literal("no-op")]),
|
|
180437
|
+
execution_performed: Literal(false)
|
|
180438
|
+
}, { additionalProperties: false });
|
|
180439
|
+
var ApplicationReleaseTransferResultSchema = _Object_({
|
|
180440
|
+
schema: Literal("supacloud.application-release-transfer-result.v1"),
|
|
180441
|
+
...identity,
|
|
180442
|
+
release: ApplicationReleaseRecordSchema,
|
|
180443
|
+
activation_performed: Literal(false)
|
|
180444
|
+
}, { additionalProperties: false });
|
|
180445
|
+
function assertIdentity(value) {
|
|
180446
|
+
if (value.source.release_id !== applicationReleaseId(value.source.project_ref, value.application_id, value.source.manifest_sha256) || value.candidate_release_id !== applicationReleaseId(value.project_ref, value.application_id, value.source.manifest_sha256)) {
|
|
180447
|
+
throw new Error("Invalid application release transfer identity");
|
|
180448
|
+
}
|
|
180449
|
+
}
|
|
180450
|
+
function parseApplicationReleaseTransferPlan(candidate) {
|
|
180451
|
+
if (!Check2(ApplicationReleaseTransferPlanSchema, candidate))
|
|
180452
|
+
throw new Error("Invalid application release transfer plan");
|
|
180453
|
+
assertIdentity(candidate);
|
|
180454
|
+
return candidate;
|
|
180455
|
+
}
|
|
180456
|
+
function parseApplicationReleaseTransferResult(candidate) {
|
|
180457
|
+
if (!Check2(ApplicationReleaseTransferResultSchema, candidate))
|
|
180458
|
+
throw new Error("Invalid application release transfer result");
|
|
180459
|
+
assertIdentity(candidate);
|
|
180460
|
+
const release = parseApplicationReleaseRecord(candidate.release);
|
|
180461
|
+
if (release.project_ref !== candidate.project_ref || release.application_id !== candidate.application_id || release.release_id !== candidate.candidate_release_id || release.manifest_sha256 !== candidate.source.manifest_sha256) {
|
|
180462
|
+
throw new Error("Invalid application release transfer result");
|
|
180463
|
+
}
|
|
180464
|
+
return candidate;
|
|
180465
|
+
}
|
|
180422
180466
|
// node_modules/@supacloud/delivery/src/deployment-evidence.ts
|
|
180423
180467
|
var closed3 = { additionalProperties: false };
|
|
180424
180468
|
var sha256 = String2({ pattern: "^[a-f0-9]{64}$" });
|
|
@@ -180544,6 +180588,116 @@ function parseDeploymentEvidence(value) {
|
|
|
180544
180588
|
}
|
|
180545
180589
|
return structuredClone(value);
|
|
180546
180590
|
}
|
|
180591
|
+
// node_modules/@supacloud/delivery/src/application-promotion.ts
|
|
180592
|
+
var closed4 = { additionalProperties: false };
|
|
180593
|
+
var projectRef2 = String2({ pattern: "^[a-z0-9-]{1,20}$" });
|
|
180594
|
+
var sha2562 = ApplicationReleaseIdSchema;
|
|
180595
|
+
var nullableActivation = Union([ApplicationActivationIdSchema, Null()]);
|
|
180596
|
+
var nullableDigest = Union([sha2562, Null()]);
|
|
180597
|
+
var observation = {
|
|
180598
|
+
activation_id: nullableActivation,
|
|
180599
|
+
receipt_confirmed: Boolean2(),
|
|
180600
|
+
ready: Boolean2(),
|
|
180601
|
+
smoke_verified: Boolean2(),
|
|
180602
|
+
evidence_sha256: nullableDigest,
|
|
180603
|
+
migration_ledger_digest: nullableDigest
|
|
180604
|
+
};
|
|
180605
|
+
var blocker = [
|
|
180606
|
+
"SOURCE_NOT_ACTIVE",
|
|
180607
|
+
"SOURCE_RECEIPT_UNCONFIRMED",
|
|
180608
|
+
"SOURCE_NOT_READY",
|
|
180609
|
+
"SOURCE_SMOKE_UNVERIFIED",
|
|
180610
|
+
"TARGET_CONFIGURATION_MISSING",
|
|
180611
|
+
"TARGET_CONFIGURATION_MISMATCH",
|
|
180612
|
+
"MIGRATION_PENDING",
|
|
180613
|
+
"MIGRATION_CONFLICT",
|
|
180614
|
+
"OPERATOR_PROVISIONING_REQUIRED",
|
|
180615
|
+
"BACKUP_REQUIRED"
|
|
180616
|
+
];
|
|
180617
|
+
var ApplicationPromotionPlanSchema = _Object_({
|
|
180618
|
+
schema: Literal("supacloud.application-promotion-plan.v1"),
|
|
180619
|
+
project_ref: projectRef2,
|
|
180620
|
+
application_id: ApplicationIdSchema,
|
|
180621
|
+
environment_id: ApplicationIdSchema,
|
|
180622
|
+
manifest_sha256: sha2562,
|
|
180623
|
+
plan_sha256: sha2562,
|
|
180624
|
+
source: _Object_({
|
|
180625
|
+
project_ref: projectRef2,
|
|
180626
|
+
environment_id: ApplicationIdSchema,
|
|
180627
|
+
release_id: sha2562,
|
|
180628
|
+
...observation
|
|
180629
|
+
}, closed4),
|
|
180630
|
+
target: _Object_({
|
|
180631
|
+
candidate_release_id: sha2562,
|
|
180632
|
+
current_release_id: Union([sha2562, Null()]),
|
|
180633
|
+
artifact_action: Union([Literal("materialize"), Literal("reuse")]),
|
|
180634
|
+
configuration_id: Union([ApplicationConfigurationIdSchema, Null()]),
|
|
180635
|
+
configuration: Union([ApplicationConfigurationViewSchema, Null()]),
|
|
180636
|
+
...observation
|
|
180637
|
+
}, closed4),
|
|
180638
|
+
migrations: _Object_({
|
|
180639
|
+
ledger_digest: sha2562,
|
|
180640
|
+
ledger_compatible: Boolean2(),
|
|
180641
|
+
project_migrations_applied: Boolean2(),
|
|
180642
|
+
pending_versions: _Array_(String2({ pattern: "^\\d{1,20}$" }), { maxItems: 4096, uniqueItems: true }),
|
|
180643
|
+
operator_provisioning_required: Boolean2()
|
|
180644
|
+
}, closed4),
|
|
180645
|
+
backup: _Object_({
|
|
180646
|
+
required: Boolean2(),
|
|
180647
|
+
confirmed: Literal(false)
|
|
180648
|
+
}, closed4),
|
|
180649
|
+
action: Union([Literal("promote"), Literal("no-op"), Literal("blocked")]),
|
|
180650
|
+
blockers: _Array_(Enum([...blocker]), {
|
|
180651
|
+
maxItems: blocker.length,
|
|
180652
|
+
uniqueItems: true
|
|
180653
|
+
}),
|
|
180654
|
+
steps: _Array_(String2({ minLength: 1, maxLength: 64 }), { maxItems: 8, uniqueItems: true }),
|
|
180655
|
+
execution_performed: Literal(false),
|
|
180656
|
+
data_recovery: Literal("separate-required")
|
|
180657
|
+
}, closed4);
|
|
180658
|
+
function applicationPromotionPlanDigest(value) {
|
|
180659
|
+
return digest(canonical(value));
|
|
180660
|
+
}
|
|
180661
|
+
function applicationPromotionAction(value) {
|
|
180662
|
+
if (value.blockers.length > 0)
|
|
180663
|
+
return "blocked";
|
|
180664
|
+
return value.target.artifact_action === "reuse" && value.target.current_release_id === value.target.candidate_release_id && value.target.configuration !== null && value.target.activation_id !== null && value.target.receipt_confirmed && value.target.ready && value.target.smoke_verified && value.migrations.project_migrations_applied && !value.migrations.operator_provisioning_required && (!value.backup.required || value.backup.confirmed) ? "no-op" : "promote";
|
|
180665
|
+
}
|
|
180666
|
+
function applicationPromotionSteps(value) {
|
|
180667
|
+
if (value.action === "no-op")
|
|
180668
|
+
return [];
|
|
180669
|
+
return [
|
|
180670
|
+
...value.blockers.some((code) => code.startsWith("SOURCE_")) ? ["verify-source"] : [],
|
|
180671
|
+
...value.blockers.some((code) => code.startsWith("TARGET_CONFIGURATION_")) ? ["bind-target-configuration"] : [],
|
|
180672
|
+
...value.blockers.includes("MIGRATION_CONFLICT") ? ["resolve-migration-conflicts"] : [],
|
|
180673
|
+
...!value.backup.required || value.backup.confirmed ? [] : ["backup"],
|
|
180674
|
+
...value.migrations.project_migrations_applied ? [] : ["review-and-apply-migrations"],
|
|
180675
|
+
...value.migrations.operator_provisioning_required ? ["verify-operator-provisioning"] : [],
|
|
180676
|
+
...value.action === "promote" ? [
|
|
180677
|
+
...value.target.artifact_action === "materialize" ? ["transfer"] : [],
|
|
180678
|
+
"activate-with-cas",
|
|
180679
|
+
"verify-runtime-and-smoke"
|
|
180680
|
+
] : []
|
|
180681
|
+
];
|
|
180682
|
+
}
|
|
180683
|
+
function parseApplicationPromotionPlan(value) {
|
|
180684
|
+
if (!Check2(ApplicationPromotionPlanSchema, value))
|
|
180685
|
+
throw new Error("Invalid application promotion plan");
|
|
180686
|
+
const { plan_sha256: _planSha256, ...content } = value;
|
|
180687
|
+
if (value.migrations.project_migrations_applied && (!value.migrations.ledger_compatible || value.migrations.pending_versions.length > 0) || !value.source.activation_id && (value.source.ready || value.source.receipt_confirmed || value.source.smoke_verified) || !value.target.activation_id && (value.target.ready || value.target.receipt_confirmed || value.target.smoke_verified) || !value.blockers.length && (!value.source.ready || !value.source.receipt_confirmed || !value.source.smoke_verified || !value.source.activation_id || !value.target.configuration || !value.migrations.ledger_compatible)) {
|
|
180688
|
+
throw new Error("Invalid application promotion prerequisites");
|
|
180689
|
+
}
|
|
180690
|
+
if (value.backup.required !== !value.migrations.project_migrations_applied || [value.source, value.target].some((observation) => observation.smoke_verified && (observation.evidence_sha256 === null || observation.migration_ledger_digest === null)) || value.target.smoke_verified && value.target.migration_ledger_digest !== value.migrations.ledger_digest) {
|
|
180691
|
+
throw new Error("Invalid application promotion migration binding");
|
|
180692
|
+
}
|
|
180693
|
+
if (value.source.release_id !== applicationReleaseId(value.source.project_ref, value.application_id, value.manifest_sha256) || value.target.candidate_release_id !== applicationReleaseId(value.project_ref, value.application_id, value.manifest_sha256) || value.action !== applicationPromotionAction(content) || canonical(value.steps) !== canonical(applicationPromotionSteps(content)) || value.plan_sha256 !== applicationPromotionPlanDigest(content) || value.target.configuration !== null && (() => {
|
|
180694
|
+
const configuration = parseApplicationConfigurationView(value.target.configuration);
|
|
180695
|
+
return configuration.project_ref !== value.project_ref || configuration.application_id !== value.application_id || configuration.environment_id !== value.environment_id || configuration.configuration_id !== value.target.configuration_id;
|
|
180696
|
+
})()) {
|
|
180697
|
+
throw new Error("Invalid application promotion plan identity");
|
|
180698
|
+
}
|
|
180699
|
+
return structuredClone(value);
|
|
180700
|
+
}
|
|
180547
180701
|
// node_modules/@supacloud/delivery/src/application-runtime-identity.ts
|
|
180548
180702
|
var ApplicationRuntimeIdentitySchema = _Object_({
|
|
180549
180703
|
schema: Literal("supacloud.application-runtime.v1"),
|
|
@@ -181551,7 +181705,7 @@ ALTER TABLE supacloud_commands.execution_receipts
|
|
|
181551
181705
|
DROP COLUMN IF EXISTS recovery_attempts;
|
|
181552
181706
|
`;
|
|
181553
181707
|
var MAX_MIGRATION_VERSION = 9223372036854775807n;
|
|
181554
|
-
function
|
|
181708
|
+
function sha2563(value) {
|
|
181555
181709
|
return createHash22("sha256").update(value).digest("hex");
|
|
181556
181710
|
}
|
|
181557
181711
|
function normalizedSql(value) {
|
|
@@ -181620,7 +181774,7 @@ function planMigrationRebase(migrations, options, baselineSql) {
|
|
|
181620
181774
|
file: migration.file,
|
|
181621
181775
|
version: migration.version,
|
|
181622
181776
|
name: migration.name,
|
|
181623
|
-
sha256:
|
|
181777
|
+
sha256: sha2563(normalizedSql(migration.sql))
|
|
181624
181778
|
}));
|
|
181625
181779
|
return {
|
|
181626
181780
|
version: 1,
|
|
@@ -181630,8 +181784,8 @@ function planMigrationRebase(migrations, options, baselineSql) {
|
|
|
181630
181784
|
baselineMigration,
|
|
181631
181785
|
retainedMigrations: retained.map((migration) => migration.file),
|
|
181632
181786
|
archivedMigrations: archived.map((migration) => migration.file),
|
|
181633
|
-
sourceHistorySha256:
|
|
181634
|
-
baselineSha256:
|
|
181787
|
+
sourceHistorySha256: sha2563(JSON.stringify(sourceHistory)),
|
|
181788
|
+
baselineSha256: sha2563(normalizedBaseline),
|
|
181635
181789
|
warnings: [
|
|
181636
181790
|
"The baseline must come from a schema-only snapshot of the verified target database.",
|
|
181637
181791
|
"Data backfills, reference data and non-replayable side effects need a separate review.",
|
|
@@ -181667,7 +181821,7 @@ async function rebaseMigrations(options) {
|
|
|
181667
181821
|
file: migration.file,
|
|
181668
181822
|
version: migration.version,
|
|
181669
181823
|
name: migration.name,
|
|
181670
|
-
sha256:
|
|
181824
|
+
sha256: sha2563(normalizedSql(migration.sql))
|
|
181671
181825
|
}))
|
|
181672
181826
|
}, null, 2) + `
|
|
181673
181827
|
`);
|
|
@@ -184569,7 +184723,7 @@ function clientId(value) {
|
|
|
184569
184723
|
}
|
|
184570
184724
|
return value;
|
|
184571
184725
|
}
|
|
184572
|
-
function
|
|
184726
|
+
function projectRef3(value) {
|
|
184573
184727
|
if (typeof value !== "string" || !value.trim())
|
|
184574
184728
|
throw new Error("'ref' is required");
|
|
184575
184729
|
return projectRefPathSegment(value.trim(), "OAuth client");
|
|
@@ -184721,7 +184875,7 @@ function registerOAuthClientTools(server2, http) {
|
|
|
184721
184875
|
client_id: optional(String2(), "[get/delete] Exact release-canary public OAuth client ID"),
|
|
184722
184876
|
redirect_uri: optional(String2(), "[create/delete] Exact HTTPS or port-bound RFC 8252 loopback callback")
|
|
184723
184877
|
}, async ({ action, ref, client_id, redirect_uri }) => {
|
|
184724
|
-
const targetRef =
|
|
184878
|
+
const targetRef = projectRef3(ref);
|
|
184725
184879
|
if (action === "list")
|
|
184726
184880
|
return listReleaseCanaryClients(http, targetRef);
|
|
184727
184881
|
if (action === "get")
|
|
@@ -187393,6 +187547,432 @@ var ApplicationActivationResultSchema2 = ApplicationActivationResultSchema;
|
|
|
187393
187547
|
var ApplicationActivationRetirementResultSchema2 = ApplicationActivationRetirementResultSchema;
|
|
187394
187548
|
var ApplicationRollbackSnapshotSchema2 = ApplicationRollbackSnapshotSchema;
|
|
187395
187549
|
var ApplicationActivationHistoryCursorSchema2 = ApplicationActivationHistoryCursorSchema;
|
|
187550
|
+
var ApplicationPromotionPlanSchema2 = ApplicationPromotionPlanSchema;
|
|
187551
|
+
|
|
187552
|
+
// src/shared/mutation-protocol.ts
|
|
187553
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
187554
|
+
var MUTATION_ID_PATTERN2 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
187555
|
+
var FINGERPRINT_PATTERN = /^[0-9a-f]{64}$/;
|
|
187556
|
+
var OPERATION_PATTERN = /^[a-z0-9][a-z0-9._:-]{0,127}$/;
|
|
187557
|
+
var RESOURCE_KEY_PATTERN = /^v1\/(?:[a-z0-9][a-z0-9._-]{0,63})\/([A-Za-z0-9_-]{2,171})$/;
|
|
187558
|
+
var RESOURCE_ID_CONTROL_PATTERN = /[\u0000-\u001f\u007f-\u009f]/u;
|
|
187559
|
+
var FAILURE_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
187560
|
+
var LEASE_OWNER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,254}$/;
|
|
187561
|
+
var TIMESTAMP_PATTERN2 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
187562
|
+
var MAX_STATUS_RESPONSE_BYTES = 196608;
|
|
187563
|
+
var MAX_JSON_DEPTH = 32;
|
|
187564
|
+
var MAX_RESOURCE_ID_BYTES = 128;
|
|
187565
|
+
var FATAL_UTF8_DECODER = new TextDecoder("utf-8", { fatal: true });
|
|
187566
|
+
var MUTATION_STATUSES = new Set([
|
|
187567
|
+
"pending",
|
|
187568
|
+
"running",
|
|
187569
|
+
"succeeded",
|
|
187570
|
+
"failed_retryable",
|
|
187571
|
+
"failed_terminal",
|
|
187572
|
+
"outcome_unknown"
|
|
187573
|
+
]);
|
|
187574
|
+
var MUTATION_RESPONSE_KEYS = ["project_ref", "mutation"];
|
|
187575
|
+
var MUTATION_KEYS = [
|
|
187576
|
+
"project_ref",
|
|
187577
|
+
"mutation_id",
|
|
187578
|
+
"operation",
|
|
187579
|
+
"resource_key",
|
|
187580
|
+
"request_fingerprint",
|
|
187581
|
+
"principal",
|
|
187582
|
+
"status",
|
|
187583
|
+
"checkpoint",
|
|
187584
|
+
"receipt",
|
|
187585
|
+
"response_status",
|
|
187586
|
+
"failure_code",
|
|
187587
|
+
"lease",
|
|
187588
|
+
"completed_at",
|
|
187589
|
+
"created_at",
|
|
187590
|
+
"updated_at"
|
|
187591
|
+
];
|
|
187592
|
+
var PRINCIPAL_KEYS = ["type", "id"];
|
|
187593
|
+
var LEASE_KEYS = ["owner", "expires_at", "fencing_epoch"];
|
|
187594
|
+
function isMutationId(candidate) {
|
|
187595
|
+
return typeof candidate === "string" && MUTATION_ID_PATTERN2.test(candidate);
|
|
187596
|
+
}
|
|
187597
|
+
function assertCanonicalJsonNode(candidate, seen, depth) {
|
|
187598
|
+
if (candidate === null || typeof candidate === "string" || typeof candidate === "boolean")
|
|
187599
|
+
return;
|
|
187600
|
+
if (typeof candidate === "number" && Number.isFinite(candidate))
|
|
187601
|
+
return;
|
|
187602
|
+
if (typeof candidate !== "object" || depth > MAX_JSON_DEPTH) {
|
|
187603
|
+
throw new Error("Mutation request must be bounded JSON");
|
|
187604
|
+
}
|
|
187605
|
+
if (seen.has(candidate))
|
|
187606
|
+
throw new Error("Mutation request must not contain cycles");
|
|
187607
|
+
seen.add(candidate);
|
|
187608
|
+
const prototype = Object.getPrototypeOf(candidate);
|
|
187609
|
+
const plainRecord = prototype === Object.prototype || prototype === null;
|
|
187610
|
+
const children = Array.isArray(candidate) ? candidate : plainRecord ? Object.values(candidate) : null;
|
|
187611
|
+
if (!children)
|
|
187612
|
+
throw new Error("Mutation request must contain only JSON objects and arrays");
|
|
187613
|
+
for (const child of children)
|
|
187614
|
+
assertCanonicalJsonNode(child, seen, depth + 1);
|
|
187615
|
+
seen.delete(candidate);
|
|
187616
|
+
}
|
|
187617
|
+
function stableStringifyNode(jsonValue) {
|
|
187618
|
+
if (jsonValue === null || typeof jsonValue !== "object") {
|
|
187619
|
+
const serialized = JSON.stringify(jsonValue);
|
|
187620
|
+
if (serialized === undefined)
|
|
187621
|
+
throw new Error("Mutation request must contain exact JSON values");
|
|
187622
|
+
return serialized;
|
|
187623
|
+
}
|
|
187624
|
+
if (Array.isArray(jsonValue))
|
|
187625
|
+
return `[${jsonValue.map(stableStringifyNode).join(",")}]`;
|
|
187626
|
+
const record = jsonValue;
|
|
187627
|
+
const fields = Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringifyNode(record[key])}`);
|
|
187628
|
+
return `{${fields.join(",")}}`;
|
|
187629
|
+
}
|
|
187630
|
+
function mutationRequestFingerprint(normalizedRequest) {
|
|
187631
|
+
assertCanonicalJsonNode(normalizedRequest, new WeakSet, 0);
|
|
187632
|
+
return createHash6("sha256").update(stableStringifyNode(normalizedRequest)).digest("hex");
|
|
187633
|
+
}
|
|
187634
|
+
function objectRecord3(candidate) {
|
|
187635
|
+
return candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
|
|
187636
|
+
}
|
|
187637
|
+
function exactRecord(candidate, keys) {
|
|
187638
|
+
const record = objectRecord3(candidate);
|
|
187639
|
+
if (!record || Object.keys(record).length !== keys.length)
|
|
187640
|
+
return null;
|
|
187641
|
+
return keys.every((key) => Object.hasOwn(record, key)) ? record : null;
|
|
187642
|
+
}
|
|
187643
|
+
function emptyProjection(candidate) {
|
|
187644
|
+
const record = objectRecord3(candidate);
|
|
187645
|
+
return record && Object.keys(record).length === 0 ? record : null;
|
|
187646
|
+
}
|
|
187647
|
+
function canonicalTimestamp2(candidate) {
|
|
187648
|
+
if (typeof candidate !== "string" || !TIMESTAMP_PATTERN2.test(candidate))
|
|
187649
|
+
return false;
|
|
187650
|
+
const milliseconds = Date.parse(candidate);
|
|
187651
|
+
return Number.isFinite(milliseconds) && new Date(milliseconds).toISOString() === candidate;
|
|
187652
|
+
}
|
|
187653
|
+
function nullableTimestamp(candidate) {
|
|
187654
|
+
return candidate === null || canonicalTimestamp2(candidate);
|
|
187655
|
+
}
|
|
187656
|
+
function safePrincipal(candidate) {
|
|
187657
|
+
const principal = exactRecord(candidate, PRINCIPAL_KEYS);
|
|
187658
|
+
if (!principal || !["master", "admin", "project"].includes(String(principal["type"])))
|
|
187659
|
+
return null;
|
|
187660
|
+
if (typeof principal["id"] !== "string" || !principal["id"] || principal["id"].length > 320 || principal["id"].trim() !== principal["id"] || /[\u0000-\u001f\u007f]/u.test(principal["id"]))
|
|
187661
|
+
return null;
|
|
187662
|
+
return { type: principal["type"], id: principal["id"] };
|
|
187663
|
+
}
|
|
187664
|
+
function safeLease(candidate) {
|
|
187665
|
+
const lease = exactRecord(candidate, LEASE_KEYS);
|
|
187666
|
+
if (!lease || lease["owner"] !== null && (typeof lease["owner"] !== "string" || !LEASE_OWNER_PATTERN.test(lease["owner"])))
|
|
187667
|
+
return null;
|
|
187668
|
+
if (!nullableTimestamp(lease["expires_at"]) || !Number.isSafeInteger(lease["fencing_epoch"]) || Number(lease["fencing_epoch"]) < 0)
|
|
187669
|
+
return null;
|
|
187670
|
+
return {
|
|
187671
|
+
owner: lease["owner"],
|
|
187672
|
+
expires_at: lease["expires_at"],
|
|
187673
|
+
fencing_epoch: Number(lease["fencing_epoch"])
|
|
187674
|
+
};
|
|
187675
|
+
}
|
|
187676
|
+
function safeResponseStatus(candidate) {
|
|
187677
|
+
if (candidate === null)
|
|
187678
|
+
return null;
|
|
187679
|
+
return Number.isInteger(candidate) && Number(candidate) >= 100 && Number(candidate) <= 599 ? Number(candidate) : undefined;
|
|
187680
|
+
}
|
|
187681
|
+
function validLeaseState(status, lease) {
|
|
187682
|
+
const running = status === "running";
|
|
187683
|
+
return running ? lease.owner !== null && lease.expires_at !== null && lease.fencing_epoch > 0 : lease.owner === null && lease.expires_at === null;
|
|
187684
|
+
}
|
|
187685
|
+
function validMutationLifecycle(mutation, receipt, responseStatus) {
|
|
187686
|
+
const terminal = ["succeeded", "failed_terminal", "outcome_unknown"].includes(String(mutation["status"]));
|
|
187687
|
+
if (mutation["completed_at"] !== null !== terminal)
|
|
187688
|
+
return false;
|
|
187689
|
+
if (mutation["status"] === "succeeded") {
|
|
187690
|
+
return receipt !== null && responseStatus !== null && responseStatus >= 200 && responseStatus < 300 && mutation["failure_code"] === null;
|
|
187691
|
+
}
|
|
187692
|
+
if (mutation["status"] === "failed_terminal") {
|
|
187693
|
+
return receipt !== null && typeof mutation["failure_code"] === "string";
|
|
187694
|
+
}
|
|
187695
|
+
if (mutation["status"] === "failed_retryable" || mutation["status"] === "outcome_unknown") {
|
|
187696
|
+
return receipt !== null && typeof mutation["failure_code"] === "string";
|
|
187697
|
+
}
|
|
187698
|
+
if (mutation["status"] === "pending") {
|
|
187699
|
+
return receipt === null && responseStatus === null && mutation["failure_code"] === null;
|
|
187700
|
+
}
|
|
187701
|
+
return true;
|
|
187702
|
+
}
|
|
187703
|
+
function canonicalMutationResourceKey(candidate) {
|
|
187704
|
+
if (typeof candidate !== "string")
|
|
187705
|
+
return false;
|
|
187706
|
+
const match = RESOURCE_KEY_PATTERN.exec(candidate);
|
|
187707
|
+
if (!match)
|
|
187708
|
+
return false;
|
|
187709
|
+
const encodedResourceId = match[1];
|
|
187710
|
+
const resourceIdBytes = Buffer.from(encodedResourceId, "base64url");
|
|
187711
|
+
if (resourceIdBytes.byteLength < 1 || resourceIdBytes.byteLength > MAX_RESOURCE_ID_BYTES || resourceIdBytes.toString("base64url") !== encodedResourceId)
|
|
187712
|
+
return false;
|
|
187713
|
+
let resourceId;
|
|
187714
|
+
try {
|
|
187715
|
+
resourceId = FATAL_UTF8_DECODER.decode(resourceIdBytes);
|
|
187716
|
+
} catch (decodeError) {
|
|
187717
|
+
if (decodeError instanceof TypeError)
|
|
187718
|
+
return false;
|
|
187719
|
+
throw decodeError;
|
|
187720
|
+
}
|
|
187721
|
+
return resourceId.trim() === resourceId && !RESOURCE_ID_CONTROL_PATTERN.test(resourceId) && Buffer.from(resourceId, "utf8").equals(resourceIdBytes);
|
|
187722
|
+
}
|
|
187723
|
+
function validMutationIdentity(mutation) {
|
|
187724
|
+
if (!isMutationId(mutation["mutation_id"]) || typeof mutation["project_ref"] !== "string")
|
|
187725
|
+
return false;
|
|
187726
|
+
if (typeof mutation["operation"] !== "string" || !OPERATION_PATTERN.test(mutation["operation"]))
|
|
187727
|
+
return false;
|
|
187728
|
+
if (mutation["resource_key"] !== null && !canonicalMutationResourceKey(mutation["resource_key"]))
|
|
187729
|
+
return false;
|
|
187730
|
+
return typeof mutation["request_fingerprint"] === "string" && FINGERPRINT_PATTERN.test(mutation["request_fingerprint"]);
|
|
187731
|
+
}
|
|
187732
|
+
function validMutationTerminalFields(mutation) {
|
|
187733
|
+
if (typeof mutation["status"] !== "string" || !MUTATION_STATUSES.has(mutation["status"]))
|
|
187734
|
+
return false;
|
|
187735
|
+
if (mutation["failure_code"] !== null && (typeof mutation["failure_code"] !== "string" || !FAILURE_CODE_PATTERN.test(mutation["failure_code"])))
|
|
187736
|
+
return false;
|
|
187737
|
+
return nullableTimestamp(mutation["completed_at"]) && canonicalTimestamp2(mutation["created_at"]) && canonicalTimestamp2(mutation["updated_at"]);
|
|
187738
|
+
}
|
|
187739
|
+
function safeMutationStatus(candidate) {
|
|
187740
|
+
const mutation = exactRecord(candidate, MUTATION_KEYS);
|
|
187741
|
+
if (!mutation || !validMutationIdentity(mutation) || !validMutationTerminalFields(mutation))
|
|
187742
|
+
return null;
|
|
187743
|
+
const principal = safePrincipal(mutation["principal"]);
|
|
187744
|
+
const checkpoint = emptyProjection(mutation["checkpoint"]);
|
|
187745
|
+
const receipt = mutation["receipt"] === null ? null : emptyProjection(mutation["receipt"]);
|
|
187746
|
+
const responseStatus = safeResponseStatus(mutation["response_status"]);
|
|
187747
|
+
const lease = safeLease(mutation["lease"]);
|
|
187748
|
+
if (!principal || !checkpoint || mutation["receipt"] !== null && !receipt || responseStatus === undefined || !lease || !validLeaseState(String(mutation["status"]), lease) || !validMutationLifecycle(mutation, receipt, responseStatus))
|
|
187749
|
+
return null;
|
|
187750
|
+
return {
|
|
187751
|
+
project_ref: mutation["project_ref"],
|
|
187752
|
+
mutation_id: mutation["mutation_id"],
|
|
187753
|
+
operation: mutation["operation"],
|
|
187754
|
+
resource_key: mutation["resource_key"],
|
|
187755
|
+
request_fingerprint: mutation["request_fingerprint"],
|
|
187756
|
+
principal,
|
|
187757
|
+
status: mutation["status"],
|
|
187758
|
+
checkpoint,
|
|
187759
|
+
receipt,
|
|
187760
|
+
response_status: responseStatus,
|
|
187761
|
+
failure_code: mutation["failure_code"],
|
|
187762
|
+
lease,
|
|
187763
|
+
completed_at: mutation["completed_at"],
|
|
187764
|
+
created_at: mutation["created_at"],
|
|
187765
|
+
updated_at: mutation["updated_at"]
|
|
187766
|
+
};
|
|
187767
|
+
}
|
|
187768
|
+
function mutationStatusPath(ref, mutationId) {
|
|
187769
|
+
if (!isMutationId(mutationId))
|
|
187770
|
+
throw new Error("'mutation_id' must be a UUIDv4");
|
|
187771
|
+
return `/v1/projects/${projectRefPathSegment(ref, "Mutations")}/mutations/${encodeURIComponent(mutationId)}`;
|
|
187772
|
+
}
|
|
187773
|
+
function mutationStatusResponse(ref, mutationId, payload) {
|
|
187774
|
+
const response = exactRecord(payload, MUTATION_RESPONSE_KEYS);
|
|
187775
|
+
const mutation = safeMutationStatus(response?.["mutation"]);
|
|
187776
|
+
return response?.["project_ref"] === ref && mutation?.project_ref === ref && mutation.mutation_id === mutationId ? mutation : null;
|
|
187777
|
+
}
|
|
187778
|
+
async function fetchMutationStatus(http, ref, mutationId) {
|
|
187779
|
+
const response = await http.get(mutationStatusPath(ref, mutationId), {
|
|
187780
|
+
maxResponseBytes: MAX_STATUS_RESPONSE_BYTES
|
|
187781
|
+
});
|
|
187782
|
+
if (!response.ok) {
|
|
187783
|
+
return { kind: "unavailable", httpStatus: response.transportError ? null : response.status };
|
|
187784
|
+
}
|
|
187785
|
+
const mutation = mutationStatusResponse(ref, mutationId, response.data);
|
|
187786
|
+
return mutation ? { kind: "available", mutation } : { kind: "invalid" };
|
|
187787
|
+
}
|
|
187788
|
+
|
|
187789
|
+
// src/shared/tools/application-workflow-schemas.ts
|
|
187790
|
+
var closed5 = { additionalProperties: false };
|
|
187791
|
+
var ref5 = String2({ pattern: "^[A-Za-z0-9_-]{1,20}$" });
|
|
187792
|
+
var uuid = ApplicationActivationIdSchema2;
|
|
187793
|
+
var nullableUuid = Union([uuid, Null()]);
|
|
187794
|
+
var nullableDigest2 = Union([ApplicationReleaseIdSchema2, Null()]);
|
|
187795
|
+
var timestamp2 = String2({ pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$" });
|
|
187796
|
+
var scope2 = { project_ref: ref5, application_id: ApplicationIdSchema2, environment_id: ApplicationIdSchema2 };
|
|
187797
|
+
var ApplicationPromotionResultSchema = _Object_({
|
|
187798
|
+
...scope2,
|
|
187799
|
+
mutation_id: uuid,
|
|
187800
|
+
activation_id: uuid,
|
|
187801
|
+
plan_sha256: ApplicationReleaseIdSchema2,
|
|
187802
|
+
action: Literal("promote"),
|
|
187803
|
+
replayed: Boolean2()
|
|
187804
|
+
}, closed5);
|
|
187805
|
+
var ApplicationPromotionStatusSchema = _Object_({
|
|
187806
|
+
...scope2,
|
|
187807
|
+
mutation: Unknown(),
|
|
187808
|
+
promotion: _Object_({
|
|
187809
|
+
phase: Union([
|
|
187810
|
+
Literal("prepared"),
|
|
187811
|
+
Literal("transferring"),
|
|
187812
|
+
Literal("migrating"),
|
|
187813
|
+
Literal("activating"),
|
|
187814
|
+
Literal("verifying"),
|
|
187815
|
+
Null()
|
|
187816
|
+
]),
|
|
187817
|
+
plan_sha256: nullableDigest2,
|
|
187818
|
+
activation_id: nullableUuid,
|
|
187819
|
+
release_id: nullableDigest2,
|
|
187820
|
+
configuration_id: Union([ApplicationConfigurationIdSchema2, Null()]),
|
|
187821
|
+
backup_id: Union([String2({ pattern: "^logical-full_[a-z0-9-]{1,20}_[a-f0-9]{32}$" }), Null()]),
|
|
187822
|
+
data_recovery: Literal("separate-required")
|
|
187823
|
+
}, closed5)
|
|
187824
|
+
}, closed5);
|
|
187825
|
+
var phase = Union([Literal("pending"), Literal("ready"), Literal("failed"), Literal("cleaned")]);
|
|
187826
|
+
var previewId = String2({ pattern: "^[a-f0-9-]{8,64}$" });
|
|
187827
|
+
var name = String2({ minLength: 1, maxLength: 256 });
|
|
187828
|
+
var checks = _Array_(String2({ minLength: 1, maxLength: 128 }), { maxItems: 64, uniqueItems: true });
|
|
187829
|
+
var ApplicationPreviewReceiptSchema = _Object_({
|
|
187830
|
+
schema: Literal("supacloud.application-preview.v1"),
|
|
187831
|
+
...scope2,
|
|
187832
|
+
preview_id: previewId,
|
|
187833
|
+
release_id: ApplicationReleaseIdSchema2,
|
|
187834
|
+
expires_at: Union([timestamp2, Null()]),
|
|
187835
|
+
status: Union([
|
|
187836
|
+
Literal("planned"),
|
|
187837
|
+
Literal("provisioning"),
|
|
187838
|
+
Literal("ready"),
|
|
187839
|
+
Literal("failed"),
|
|
187840
|
+
Literal("cleaned")
|
|
187841
|
+
]),
|
|
187842
|
+
resources: _Object_({
|
|
187843
|
+
build_artifact: _Object_({ status: phase, release_id: ApplicationReleaseIdSchema2 }, closed5),
|
|
187844
|
+
database_branch: _Object_({
|
|
187845
|
+
status: phase,
|
|
187846
|
+
branch_ref: ref5,
|
|
187847
|
+
data_mode: Union([Literal("schema_only"), Literal("full_clone")])
|
|
187848
|
+
}, closed5),
|
|
187849
|
+
queue_namespace: _Object_({ status: phase, namespace: name }, closed5),
|
|
187850
|
+
storage_namespace: _Object_({ status: phase, namespace: name }, closed5),
|
|
187851
|
+
test_secret: _Object_({ status: phase, name, value_issued: Literal(false) }, closed5),
|
|
187852
|
+
configuration_revision: _Object_({ status: phase, configuration_id: Union([ApplicationConfigurationIdSchema2, Null()]) }, closed5),
|
|
187853
|
+
application_activation: _Object_({ status: phase, activation_id: nullableUuid }, closed5),
|
|
187854
|
+
smoke_test: _Object_({ status: phase, checks, passed: checks, failed: checks }, closed5)
|
|
187855
|
+
}, closed5),
|
|
187856
|
+
cleanup: _Object_({
|
|
187857
|
+
required: Boolean2(),
|
|
187858
|
+
completed: Boolean2(),
|
|
187859
|
+
error: Union([String2({ pattern: "^[A-Z][A-Z0-9_]{0,63}$" }), Null()])
|
|
187860
|
+
}, closed5),
|
|
187861
|
+
branch_name: Optional(String2({ minLength: 1, maxLength: 80 })),
|
|
187862
|
+
queue_name: Optional(name),
|
|
187863
|
+
test_secret_name: Optional(name),
|
|
187864
|
+
source_configuration_id: Optional(Union([ApplicationConfigurationIdSchema2, Null()])),
|
|
187865
|
+
created_at: Optional(timestamp2),
|
|
187866
|
+
updated_at: Optional(timestamp2)
|
|
187867
|
+
}, closed5);
|
|
187868
|
+
var previewListSchema = _Object_({
|
|
187869
|
+
...scope2,
|
|
187870
|
+
previews: _Array_(ApplicationPreviewReceiptSchema, { maxItems: 1000 })
|
|
187871
|
+
}, closed5);
|
|
187872
|
+
function assertScope(value, identity) {
|
|
187873
|
+
if (value.project_ref !== identity.project_ref || value.application_id !== identity.application_id || value.environment_id !== identity.environment_id)
|
|
187874
|
+
throw new Error("Application workflow scope mismatch");
|
|
187875
|
+
}
|
|
187876
|
+
function parsePromotionStatus(value, identity) {
|
|
187877
|
+
if (!Check2(ApplicationPromotionStatusSchema, value))
|
|
187878
|
+
throw new Error("Invalid promotion status");
|
|
187879
|
+
assertScope(value, identity);
|
|
187880
|
+
const mutation = safeMutationStatus(value.mutation);
|
|
187881
|
+
const resourceId = mutationRequestFingerprint({
|
|
187882
|
+
applicationId: identity.application_id,
|
|
187883
|
+
environmentId: identity.environment_id
|
|
187884
|
+
});
|
|
187885
|
+
const resourceKey = `v1/application_release/${Buffer.from(resourceId).toString("base64url")}`;
|
|
187886
|
+
if (!mutation || mutation.project_ref !== identity.project_ref || mutation.mutation_id !== identity.mutation_id || mutation.operation !== "application.release.promote" || mutation.resource_key !== resourceKey || value.promotion.activation_id === identity.mutation_id) {
|
|
187887
|
+
throw new Error("Invalid promotion mutation identity");
|
|
187888
|
+
}
|
|
187889
|
+
if (mutation.status === "succeeded" && (mutation.response_status !== 200 || mutation.receipt === null || value.promotion.phase !== "verifying" || value.promotion.activation_id === null || value.promotion.plan_sha256 === null || value.promotion.release_id === null || value.promotion.configuration_id === null || value.promotion.backup_id === null)) {
|
|
187890
|
+
throw new Error("Invalid promotion success state");
|
|
187891
|
+
}
|
|
187892
|
+
if (value.promotion.backup_id !== null && !value.promotion.backup_id.startsWith(`logical-full_${identity.project_ref}_`)) {
|
|
187893
|
+
throw new Error("Invalid promotion backup identity");
|
|
187894
|
+
}
|
|
187895
|
+
return structuredClone({ ...value, mutation });
|
|
187896
|
+
}
|
|
187897
|
+
function parsePreviewReceipt(value, identity) {
|
|
187898
|
+
if (!Check2(ApplicationPreviewReceiptSchema, value))
|
|
187899
|
+
throw new Error("Invalid preview receipt");
|
|
187900
|
+
assertScope(value, identity);
|
|
187901
|
+
if (identity.preview_id !== undefined && value.preview_id !== identity.preview_id || identity.release_id !== undefined && value.release_id !== identity.release_id || identity.branch_ref !== undefined && value.resources.database_branch.branch_ref !== identity.branch_ref || identity.data_mode !== undefined && value.resources.database_branch.data_mode !== identity.data_mode || identity.configuration_id !== undefined && value.source_configuration_id !== identity.configuration_id || value.resources.build_artifact.release_id !== value.release_id)
|
|
187902
|
+
throw new Error("Invalid preview identity");
|
|
187903
|
+
return structuredClone(value);
|
|
187904
|
+
}
|
|
187905
|
+
function parsePreviewList(value, identity) {
|
|
187906
|
+
if (!Check2(previewListSchema, value))
|
|
187907
|
+
throw new Error("Invalid preview list");
|
|
187908
|
+
assertScope(value, identity);
|
|
187909
|
+
const ids = new Set;
|
|
187910
|
+
for (const item of value.previews) {
|
|
187911
|
+
parsePreviewReceipt(item, identity);
|
|
187912
|
+
if (ids.has(item.preview_id))
|
|
187913
|
+
throw new Error("Duplicate preview identity");
|
|
187914
|
+
ids.add(item.preview_id);
|
|
187915
|
+
}
|
|
187916
|
+
return structuredClone(value);
|
|
187917
|
+
}
|
|
187918
|
+
|
|
187919
|
+
// src/shared/tools/application-release-transfer.ts
|
|
187920
|
+
async function applicationReleaseTransfer(http, input, project) {
|
|
187921
|
+
const operation = `applications.${input.action}`;
|
|
187922
|
+
const identity = {
|
|
187923
|
+
project_ref: input.ref,
|
|
187924
|
+
application_id: input.id,
|
|
187925
|
+
source_ref: input.sourceRef,
|
|
187926
|
+
source_release_id: input.sourceReleaseId
|
|
187927
|
+
};
|
|
187928
|
+
const path = `/v1/projects/${project}/applications/${encodeURIComponent(input.id)}`;
|
|
187929
|
+
const query = new URLSearchParams({ source_ref: input.sourceRef, source_release_id: input.sourceReleaseId });
|
|
187930
|
+
const response = await http.get(`${path}/release-transfer-plan?${query}`, { maxJsonBytes: 65536, responseTimeoutMs: 30000 });
|
|
187931
|
+
if (!response.ok)
|
|
187932
|
+
return releaseControlFailure(operation, "HTTP_ERROR", response.transportError ? null : response.status, identity);
|
|
187933
|
+
let plan;
|
|
187934
|
+
try {
|
|
187935
|
+
if (response.status !== 200)
|
|
187936
|
+
throw new Error;
|
|
187937
|
+
plan = parseApplicationReleaseTransferPlan(response.data);
|
|
187938
|
+
if (plan.project_ref !== input.ref || plan.application_id !== input.id || plan.source.project_ref !== input.sourceRef || plan.source.release_id !== input.sourceReleaseId)
|
|
187939
|
+
throw new Error;
|
|
187940
|
+
} catch {
|
|
187941
|
+
return releaseControlFailure(operation, "INVALID_RESPONSE", response.status, identity);
|
|
187942
|
+
}
|
|
187943
|
+
if (input.action === "get_release_transfer_plan")
|
|
187944
|
+
return releaseControlSuccess(operation, { ...identity, plan });
|
|
187945
|
+
const safeIdentity = {
|
|
187946
|
+
...identity,
|
|
187947
|
+
release_id: plan.candidate_release_id,
|
|
187948
|
+
manifest_sha256: plan.source.manifest_sha256
|
|
187949
|
+
};
|
|
187950
|
+
if (plan.action === "no-op")
|
|
187951
|
+
return releaseControlSuccess(operation, {
|
|
187952
|
+
...safeIdentity,
|
|
187953
|
+
no_op: true,
|
|
187954
|
+
execution_performed: false,
|
|
187955
|
+
activation_performed: false,
|
|
187956
|
+
plan
|
|
187957
|
+
});
|
|
187958
|
+
const result = await http.postReleaseMutation(`${path}/release-transfers`, {
|
|
187959
|
+
source_ref: input.sourceRef,
|
|
187960
|
+
source_release_id: input.sourceReleaseId,
|
|
187961
|
+
expected_manifest_sha256: plan.source.manifest_sha256
|
|
187962
|
+
}, { timeoutMs: 120000 });
|
|
187963
|
+
if (!result.ok)
|
|
187964
|
+
return releaseControlMutationFailure(operation, result, safeIdentity);
|
|
187965
|
+
try {
|
|
187966
|
+
if (result.status !== 200)
|
|
187967
|
+
throw new Error;
|
|
187968
|
+
const receipt = parseApplicationReleaseTransferResult(result.data);
|
|
187969
|
+
if (receipt.project_ref !== input.ref || receipt.application_id !== input.id || receipt.source.project_ref !== input.sourceRef || receipt.source.release_id !== input.sourceReleaseId || receipt.candidate_release_id !== plan.candidate_release_id || receipt.source.manifest_sha256 !== plan.source.manifest_sha256)
|
|
187970
|
+
throw new Error;
|
|
187971
|
+
return releaseControlSuccess(operation, { ...safeIdentity, no_op: false, transfer: receipt });
|
|
187972
|
+
} catch {
|
|
187973
|
+
return releaseControlFailure(operation, "OUTCOME_UNKNOWN", result.status, safeIdentity);
|
|
187974
|
+
}
|
|
187975
|
+
}
|
|
187396
187976
|
|
|
187397
187977
|
// src/shared/tools/application-preview-tools.ts
|
|
187398
187978
|
var APPLICATION_PREVIEW_ACTIONS = [
|
|
@@ -187420,9 +188000,9 @@ var APPLICATION_PREVIEW_FIELDS = {
|
|
|
187420
188000
|
timeout_seconds: optional(waitSecondsSchema, "[create_preview/get_preview] Observation budget with wait, from 1 to 3600 seconds (default 300)")
|
|
187421
188001
|
};
|
|
187422
188002
|
var strict = { additionalProperties: false };
|
|
187423
|
-
var
|
|
188003
|
+
var phase2 = stringEnum(["pending", "ready", "failed", "cleaned"]);
|
|
187424
188004
|
var check5 = String2({ pattern: "^[a-z][a-z0-9_]{0,63}$" });
|
|
187425
|
-
var
|
|
188005
|
+
var checks2 = _Array_(check5, { maxItems: 64, uniqueItems: true });
|
|
187426
188006
|
var revision = Union([ApplicationConfigurationIdSchema2, Null()]);
|
|
187427
188007
|
var receiptSchema = _Object_({
|
|
187428
188008
|
schema: Literal("supacloud.application-preview.v1"),
|
|
@@ -187434,14 +188014,14 @@ var receiptSchema = _Object_({
|
|
|
187434
188014
|
expires_at: Union([timestampSchema, Null()]),
|
|
187435
188015
|
status: stringEnum(["planned", "provisioning", "ready", "failed", "cleaned"]),
|
|
187436
188016
|
resources: _Object_({
|
|
187437
|
-
build_artifact: _Object_({ status:
|
|
187438
|
-
database_branch: _Object_({ status:
|
|
187439
|
-
queue_namespace: _Object_({ status:
|
|
187440
|
-
storage_namespace: _Object_({ status:
|
|
187441
|
-
test_secret: _Object_({ status:
|
|
187442
|
-
configuration_revision: _Object_({ status:
|
|
187443
|
-
application_activation: _Object_({ status:
|
|
187444
|
-
smoke_test: _Object_({ status:
|
|
188017
|
+
build_artifact: _Object_({ status: phase2, release_id: ApplicationReleaseIdSchema2 }, strict),
|
|
188018
|
+
database_branch: _Object_({ status: phase2, branch_ref: branchRefSchema, data_mode: stringEnum(["schema_only", "full_clone"]) }, strict),
|
|
188019
|
+
queue_namespace: _Object_({ status: phase2, namespace: String2({ minLength: 1, maxLength: 128 }) }, strict),
|
|
188020
|
+
storage_namespace: _Object_({ status: phase2, namespace: branchRefSchema }, strict),
|
|
188021
|
+
test_secret: _Object_({ status: phase2, name: String2({ pattern: "^[A-Z0-9_-]{1,128}$" }), value_issued: Literal(false) }, strict),
|
|
188022
|
+
configuration_revision: _Object_({ status: phase2, configuration_id: revision }, strict),
|
|
188023
|
+
application_activation: _Object_({ status: phase2, activation_id: Union([previewIdSchema, Null()]) }, strict),
|
|
188024
|
+
smoke_test: _Object_({ status: phase2, checks: checks2, passed: checks2, failed: checks2 }, strict)
|
|
187445
188025
|
}, strict),
|
|
187446
188026
|
cleanup: _Object_({
|
|
187447
188027
|
required: Boolean2(),
|
|
@@ -187479,14 +188059,14 @@ var actionFields = {
|
|
|
187479
188059
|
get_preview: ["preview_id", "wait", "timeout_seconds"],
|
|
187480
188060
|
cleanup_preview: ["preview_id"]
|
|
187481
188061
|
};
|
|
187482
|
-
function
|
|
188062
|
+
function timestamp3(value) {
|
|
187483
188063
|
return Number.isFinite(Date.parse(value)) && new Date(value).toISOString() === value;
|
|
187484
188064
|
}
|
|
187485
188065
|
function receipt(value, identity) {
|
|
187486
188066
|
if (!Check2(receiptSchema, value) || value.project_ref !== identity.ref || value.application_id !== identity.id || value.environment_id !== identity.environment || identity.previewId && value.preview_id !== identity.previewId || value.resources.build_artifact.release_id !== value.release_id || value.resources.storage_namespace.namespace !== value.resources.database_branch.branch_ref) {
|
|
187487
188067
|
throw new Error("Invalid preview receipt");
|
|
187488
188068
|
}
|
|
187489
|
-
if (value.expires_at !== null && !
|
|
188069
|
+
if (value.expires_at !== null && !timestamp3(value.expires_at) || value.created_at !== undefined && !timestamp3(value.created_at) || value.updated_at !== undefined && !timestamp3(value.updated_at) || value.expires_at !== null && value.created_at !== undefined && !Check2(ttlSecondsSchema, (Date.parse(value.expires_at) - Date.parse(value.created_at)) / 1000)) {
|
|
187490
188070
|
throw new Error("Invalid preview expiry");
|
|
187491
188071
|
}
|
|
187492
188072
|
if (value.status === "ready") {
|
|
@@ -187710,7 +188290,11 @@ async function applicationPreviewAction(http, args, project) {
|
|
|
187710
188290
|
throw new Error;
|
|
187711
188291
|
}
|
|
187712
188292
|
if (preview.status === "failed" || action === "cleanup_preview" && preview.status !== "cleaned") {
|
|
187713
|
-
return releaseControlFailure(operation, "MUTATION_NOT_SUCCEEDED", result.status, {
|
|
188293
|
+
return releaseControlFailure(operation, "MUTATION_NOT_SUCCEEDED", result.status, {
|
|
188294
|
+
...identity,
|
|
188295
|
+
preview_id: preview.preview_id,
|
|
188296
|
+
preview
|
|
188297
|
+
});
|
|
187714
188298
|
}
|
|
187715
188299
|
if (timeoutSeconds !== undefined)
|
|
187716
188300
|
return await waitForPreview(http, path, operation, identity, preview, timeoutSeconds, startedAt);
|
|
@@ -187732,12 +188316,23 @@ var APPLICATION_TOOL_SCHEMA = {
|
|
|
187732
188316
|
"put_configuration",
|
|
187733
188317
|
"get_deploy_plan",
|
|
187734
188318
|
"deploy_release",
|
|
188319
|
+
"get_release_transfer_plan",
|
|
188320
|
+
"transfer_release",
|
|
188321
|
+
"get_promotion_plan",
|
|
188322
|
+
"promote_application",
|
|
188323
|
+
"get_promotion_status",
|
|
188324
|
+
"reconcile_promotion",
|
|
188325
|
+
"get_preview_plan",
|
|
188326
|
+
"list_previews",
|
|
188327
|
+
"create_preview",
|
|
188328
|
+
"get_preview",
|
|
188329
|
+
"cleanup_preview",
|
|
187735
188330
|
"activate_release",
|
|
188331
|
+
"reconcile_activation",
|
|
188332
|
+
"retire_activation",
|
|
187736
188333
|
"rollback_release",
|
|
187737
188334
|
"get_rollback_snapshot",
|
|
187738
188335
|
"get_history",
|
|
187739
|
-
"reconcile_activation",
|
|
187740
|
-
"retire_activation",
|
|
187741
188336
|
...APPLICATION_PREVIEW_ACTIONS,
|
|
187742
188337
|
"logs"
|
|
187743
188338
|
]), "Action"),
|
|
@@ -187750,6 +188345,12 @@ var APPLICATION_TOOL_SCHEMA = {
|
|
|
187750
188345
|
configuration_path: optional(String2(), "[put_configuration] Local configuration write JSON including revision and expected revision"),
|
|
187751
188346
|
manifest_path: optional(String2(), "[upload_release] Local delivery.manifest.json"),
|
|
187752
188347
|
release_id: optional(ApplicationReleaseIdSchema2, "[get_release/get_deploy_plan/deploy_release/activate_release/reconcile_activation/rollback_release] Immutable application release ID; platform selects previous for default rollback"),
|
|
188348
|
+
source_ref: optional(String2({ pattern: "^[A-Za-z0-9_-]{1,20}$" }), "[get_release_transfer_plan/transfer_release] Source project ref"),
|
|
188349
|
+
source_release_id: optional(ApplicationReleaseIdSchema2, "[get_release_transfer_plan/transfer_release] Source immutable release ID"),
|
|
188350
|
+
source_environment_id: optional(ApplicationIdSchema2, "[get_promotion_plan] Source environment"),
|
|
188351
|
+
mutation_id: optional(ApplicationActivationIdSchema2, "[promote_application/get_promotion_status/reconcile_promotion] Stable promotion mutation ID"),
|
|
188352
|
+
approved_migration_digest: optional(ApplicationReleaseIdSchema2, "[promote_application] Exact reviewed migration execution-plan digest"),
|
|
188353
|
+
plan: optional(ApplicationPromotionPlanSchema2, "[promote_application] Immutable plan returned by get_promotion_plan"),
|
|
187753
188354
|
cursor: optional(Union([ApplicationReleaseIdSchema2, ApplicationActivationHistoryCursorSchema2]), "[list_releases/get_history] Returned page cursor"),
|
|
187754
188355
|
limit: optional(Integer({ minimum: 1, maximum: 100 }), "[list_releases/get_history] Page size, default 50/20"),
|
|
187755
188356
|
offset: optional(Integer({ minimum: 0, maximum: 1e6 }), "[logs] Result offset"),
|
|
@@ -187770,6 +188371,12 @@ var inventorySchema = _Object_({
|
|
|
187770
188371
|
releases: _Array_(ApplicationReleaseRecordSchema2, { maxItems: 100 }),
|
|
187771
188372
|
next_cursor: Union([ApplicationReleaseIdSchema2, Null()])
|
|
187772
188373
|
});
|
|
188374
|
+
function promotionPath(project, id, environmentId) {
|
|
188375
|
+
return `/v1/projects/${project}/applications/${encodeURIComponent(id)}/environments/${encodeURIComponent(environmentId)}/promotions`;
|
|
188376
|
+
}
|
|
188377
|
+
function previewPath(project, id, environmentId) {
|
|
188378
|
+
return `/v1/projects/${project}/applications/${encodeURIComponent(id)}/environments/${encodeURIComponent(environmentId)}`;
|
|
188379
|
+
}
|
|
187773
188380
|
function text(args, field) {
|
|
187774
188381
|
const value = args[field];
|
|
187775
188382
|
if (typeof value !== "string" || !value.trim())
|
|
@@ -188045,13 +188652,59 @@ function registerApplicationTools(server2, http) {
|
|
|
188045
188652
|
throw new Error("Invalid application ID");
|
|
188046
188653
|
const path = `/v1/projects/${project}/applications/${encodeURIComponent(id)}/releases`;
|
|
188047
188654
|
const operation = `applications.${action}`;
|
|
188048
|
-
if (action === "
|
|
188049
|
-
const
|
|
188050
|
-
|
|
188051
|
-
|
|
188052
|
-
|
|
188053
|
-
|
|
188054
|
-
|
|
188655
|
+
if (action === "get_promotion_plan") {
|
|
188656
|
+
const environmentId = text(args, "environment_id");
|
|
188657
|
+
const sourceRef = text(args, "source_ref"), sourceEnvironmentId = text(args, "source_environment_id");
|
|
188658
|
+
const sourceReleaseId = text(args, "source_release_id");
|
|
188659
|
+
if (!/^[a-z0-9-]{1,20}$/.test(sourceRef) || !Check2(ApplicationReleaseIdSchema2, sourceReleaseId) || !Check2(ApplicationIdSchema2, environmentId) || !Check2(ApplicationIdSchema2, sourceEnvironmentId)) {
|
|
188660
|
+
throw new Error("Invalid promotion environment");
|
|
188661
|
+
}
|
|
188662
|
+
const query = new URLSearchParams({
|
|
188663
|
+
source_ref: sourceRef,
|
|
188664
|
+
source_environment_id: sourceEnvironmentId,
|
|
188665
|
+
source_release_id: sourceReleaseId
|
|
188666
|
+
});
|
|
188667
|
+
if (typeof args["configuration_id"] === "string")
|
|
188668
|
+
query.set("configuration_id", args["configuration_id"]);
|
|
188669
|
+
const result = await http.get(`/v1/projects/${project}/applications/${encodeURIComponent(id)}/environments/${encodeURIComponent(environmentId)}/promotion-plan?${query}`, { maxJsonBytes: 262144, responseTimeoutMs: 30000 });
|
|
188670
|
+
const identity = {
|
|
188671
|
+
project_ref: ref,
|
|
188672
|
+
application_id: id,
|
|
188673
|
+
environment_id: environmentId,
|
|
188674
|
+
source_ref: sourceRef,
|
|
188675
|
+
source_environment_id: sourceEnvironmentId,
|
|
188676
|
+
source_release_id: sourceReleaseId
|
|
188677
|
+
};
|
|
188678
|
+
if (!result.ok)
|
|
188679
|
+
return releaseControlFailure(operation, "HTTP_ERROR", result.status, identity);
|
|
188680
|
+
try {
|
|
188681
|
+
if (result.status !== 200)
|
|
188682
|
+
throw new Error;
|
|
188683
|
+
const plan = parseApplicationPromotionPlan(result.data);
|
|
188684
|
+
if (plan.project_ref !== ref || plan.application_id !== id || plan.environment_id !== environmentId || plan.source.project_ref !== sourceRef || plan.source.environment_id !== sourceEnvironmentId || plan.source.release_id !== sourceReleaseId || args["configuration_id"] !== undefined && plan.target.configuration_id !== args["configuration_id"]) {
|
|
188685
|
+
throw new Error;
|
|
188686
|
+
}
|
|
188687
|
+
return releaseControlSuccess(operation, { ...identity, plan });
|
|
188688
|
+
} catch {
|
|
188689
|
+
return releaseControlFailure(operation, "INVALID_RESPONSE", result.status, identity);
|
|
188690
|
+
}
|
|
188691
|
+
}
|
|
188692
|
+
if (action === "get_release_transfer_plan" || action === "transfer_release") {
|
|
188693
|
+
return applicationReleaseTransfer(http, {
|
|
188694
|
+
action,
|
|
188695
|
+
ref,
|
|
188696
|
+
id,
|
|
188697
|
+
sourceRef: text(args, "source_ref"),
|
|
188698
|
+
sourceReleaseId: text(args, "source_release_id")
|
|
188699
|
+
}, project);
|
|
188700
|
+
}
|
|
188701
|
+
if (action === "get_deploy_plan") {
|
|
188702
|
+
const observed = await readDeployPlan(http, args, project);
|
|
188703
|
+
return observed.failure ?? releaseControlSuccess(operation, { ...observed.identity, plan: observed.plan });
|
|
188704
|
+
}
|
|
188705
|
+
if (action === "deploy_release")
|
|
188706
|
+
return deployAction(http, args, project);
|
|
188707
|
+
if (action === "rollback_release" || action === "get_rollback_snapshot") {
|
|
188055
188708
|
return rollbackAction(http, args, project);
|
|
188056
188709
|
}
|
|
188057
188710
|
if (action === "get_history")
|
|
@@ -188116,6 +188769,166 @@ function registerApplicationTools(server2, http) {
|
|
|
188116
188769
|
return releaseControlFailure(operation, "INVALID_RESPONSE", result.status);
|
|
188117
188770
|
}
|
|
188118
188771
|
}
|
|
188772
|
+
if (action === "promote_application") {
|
|
188773
|
+
const environmentId = text(args, "environment_id");
|
|
188774
|
+
const sourceRef = text(args, "source_ref"), sourceEnvironmentId = text(args, "source_environment_id");
|
|
188775
|
+
const sourceReleaseId = text(args, "source_release_id");
|
|
188776
|
+
const mutationId = text(args, "mutation_id");
|
|
188777
|
+
if (!Check2(ApplicationIdSchema2, environmentId) || !/^[a-z0-9-]{1,20}$/.test(sourceRef) || !Check2(ApplicationIdSchema2, sourceEnvironmentId) || !Check2(ApplicationReleaseIdSchema2, sourceReleaseId) || !Check2(ApplicationActivationIdSchema2, mutationId)) {
|
|
188778
|
+
throw new Error("Invalid promotion identity");
|
|
188779
|
+
}
|
|
188780
|
+
const plan = parseApplicationPromotionPlan(args["plan"]);
|
|
188781
|
+
if (plan.project_ref !== ref || plan.application_id !== id || plan.environment_id !== environmentId || plan.source.project_ref !== sourceRef || plan.source.environment_id !== sourceEnvironmentId || plan.source.release_id !== sourceReleaseId || plan.action === "no-op" || plan.blockers.some((code) => !["MIGRATION_PENDING", "BACKUP_REQUIRED"].includes(code)) || plan.target.configuration_id === null) {
|
|
188782
|
+
throw new Error("Promotion plan does not match the request");
|
|
188783
|
+
}
|
|
188784
|
+
const configurationId = text(args, "configuration_id");
|
|
188785
|
+
if (configurationId !== plan.target.configuration_id)
|
|
188786
|
+
throw new Error("Promotion configuration does not match the plan");
|
|
188787
|
+
const identity = {
|
|
188788
|
+
project_ref: ref,
|
|
188789
|
+
application_id: id,
|
|
188790
|
+
environment_id: environmentId,
|
|
188791
|
+
mutation_id: mutationId,
|
|
188792
|
+
plan_sha256: plan.plan_sha256
|
|
188793
|
+
};
|
|
188794
|
+
const result = await http.post(`${promotionPath(project, id, environmentId)}`, {
|
|
188795
|
+
mutation_id: mutationId,
|
|
188796
|
+
source_ref: sourceRef,
|
|
188797
|
+
source_environment_id: sourceEnvironmentId,
|
|
188798
|
+
source_release_id: sourceReleaseId,
|
|
188799
|
+
configuration_id: configurationId,
|
|
188800
|
+
plan,
|
|
188801
|
+
...typeof args["approved_migration_digest"] === "string" ? { approved_migration_digest: args["approved_migration_digest"] } : {}
|
|
188802
|
+
}, { timeoutMs: 120000, maxJsonBytes: 65536, responseTimeoutMs: 30000 });
|
|
188803
|
+
if (!result.ok)
|
|
188804
|
+
return releaseControlMutationFailure(operation, result, identity);
|
|
188805
|
+
try {
|
|
188806
|
+
const body = result.data;
|
|
188807
|
+
if (result.status !== 200 || !Check2(ApplicationPromotionResultSchema, body))
|
|
188808
|
+
throw new Error;
|
|
188809
|
+
if (body.project_ref !== ref || body.application_id !== id || body.environment_id !== environmentId || body.mutation_id !== mutationId || body.plan_sha256 !== plan.plan_sha256 || body.activation_id === mutationId || body.activation_id === plan.target.activation_id) {
|
|
188810
|
+
throw new Error;
|
|
188811
|
+
}
|
|
188812
|
+
return releaseControlSuccess(operation, { ...identity, result: body });
|
|
188813
|
+
} catch {
|
|
188814
|
+
return releaseControlFailure(operation, "OUTCOME_UNKNOWN", result.status, identity);
|
|
188815
|
+
}
|
|
188816
|
+
}
|
|
188817
|
+
if (action === "get_promotion_status" || action === "reconcile_promotion") {
|
|
188818
|
+
const environmentId = text(args, "environment_id"), mutationId = text(args, "mutation_id");
|
|
188819
|
+
if (!Check2(ApplicationIdSchema2, environmentId) || !Check2(ApplicationActivationIdSchema2, mutationId)) {
|
|
188820
|
+
throw new Error("Invalid promotion identity");
|
|
188821
|
+
}
|
|
188822
|
+
const identity = {
|
|
188823
|
+
project_ref: ref,
|
|
188824
|
+
application_id: id,
|
|
188825
|
+
environment_id: environmentId,
|
|
188826
|
+
mutation_id: mutationId
|
|
188827
|
+
};
|
|
188828
|
+
const endpoint = `${promotionPath(project, id, environmentId)}/${mutationId}`;
|
|
188829
|
+
const result = action === "get_promotion_status" ? await http.get(endpoint, { maxJsonBytes: 65536, responseTimeoutMs: 30000 }) : await http.post(`${endpoint}/reconcile`, {}, { timeoutMs: 120000, maxJsonBytes: 65536, responseTimeoutMs: 30000 });
|
|
188830
|
+
if (!result.ok)
|
|
188831
|
+
return action === "reconcile_promotion" ? releaseControlMutationFailure(operation, result, identity) : releaseControlFailure(operation, "HTTP_ERROR", result.status, identity);
|
|
188832
|
+
try {
|
|
188833
|
+
if (result.status !== 200)
|
|
188834
|
+
throw new Error;
|
|
188835
|
+
const status = parsePromotionStatus(result.data, identity);
|
|
188836
|
+
if (action === "reconcile_promotion" && status.mutation.status !== "succeeded") {
|
|
188837
|
+
return releaseControlFailure(operation, status.mutation.status === "outcome_unknown" ? "OUTCOME_UNKNOWN" : "MUTATION_NOT_SUCCEEDED", result.status, { ...identity, ...status });
|
|
188838
|
+
}
|
|
188839
|
+
return releaseControlSuccess(operation, status);
|
|
188840
|
+
} catch {
|
|
188841
|
+
return releaseControlFailure(operation, action === "reconcile_promotion" ? "OUTCOME_UNKNOWN" : "INVALID_RESPONSE", result.status, identity);
|
|
188842
|
+
}
|
|
188843
|
+
}
|
|
188844
|
+
if (action === "get_preview_plan" || action === "list_previews" || action === "create_preview" || action === "get_preview" || action === "cleanup_preview") {
|
|
188845
|
+
const environmentId = text(args, "environment_id");
|
|
188846
|
+
if (!Check2(ApplicationIdSchema2, environmentId))
|
|
188847
|
+
throw new Error("Invalid preview environment");
|
|
188848
|
+
const previewId = action === "get_preview" || action === "cleanup_preview" ? text(args, "preview_id") : undefined;
|
|
188849
|
+
const identity = {
|
|
188850
|
+
project_ref: ref,
|
|
188851
|
+
application_id: id,
|
|
188852
|
+
environment_id: environmentId,
|
|
188853
|
+
...previewId === undefined ? {} : { preview_id: previewId }
|
|
188854
|
+
};
|
|
188855
|
+
const base = previewPath(project, id, environmentId);
|
|
188856
|
+
let result;
|
|
188857
|
+
let sourceRelease;
|
|
188858
|
+
if (action === "get_preview_plan") {
|
|
188859
|
+
if (args["configuration_id"] !== undefined) {
|
|
188860
|
+
throw new Error("preview-plan only plans resource isolation; configuration_id applies to preview-create");
|
|
188861
|
+
}
|
|
188862
|
+
const releaseId = text(args, "release_id"), branchRef = text(args, "branch_ref");
|
|
188863
|
+
if (!Check2(ApplicationReleaseIdSchema2, releaseId) || !/^[A-Za-z0-9_-]{1,20}$/.test(branchRef))
|
|
188864
|
+
throw new Error("Invalid preview plan identity");
|
|
188865
|
+
const query = new URLSearchParams({ release_id: releaseId, branch_ref: branchRef });
|
|
188866
|
+
if (typeof args["data_mode"] === "string")
|
|
188867
|
+
query.set("data_mode", args["data_mode"]);
|
|
188868
|
+
result = await http.get(`${base}/preview-plan?${query}`, { maxJsonBytes: 65536, responseTimeoutMs: 30000 });
|
|
188869
|
+
} else if (action === "list_previews") {
|
|
188870
|
+
result = await http.get(`${base}/previews`, { maxJsonBytes: 262144, responseTimeoutMs: 30000 });
|
|
188871
|
+
} else {
|
|
188872
|
+
if (previewId !== undefined && !/^[a-f0-9-]{8,64}$/.test(previewId))
|
|
188873
|
+
throw new Error("Invalid preview ID");
|
|
188874
|
+
const endpoint = previewId === undefined ? `${base}/previews` : `${base}/previews/${previewId}`;
|
|
188875
|
+
if (action === "create_preview") {
|
|
188876
|
+
const releaseId = text(args, "release_id");
|
|
188877
|
+
if (!Check2(ApplicationReleaseIdSchema2, releaseId))
|
|
188878
|
+
throw new Error("Invalid preview release ID");
|
|
188879
|
+
const source = await http.get(`${path}/${releaseId}`, { maxJsonBytes: 196608, responseTimeoutMs: 30000 });
|
|
188880
|
+
if (!source.ok)
|
|
188881
|
+
return releaseControlFailure(operation, "HTTP_ERROR", source.status, identity);
|
|
188882
|
+
try {
|
|
188883
|
+
if (source.status !== 200 || !Check2(responseSchema, source.data) || source.data.project_ref !== ref || source.data.application_id !== id)
|
|
188884
|
+
throw new Error;
|
|
188885
|
+
sourceRelease = boundRelease(source.data.release, ref, id);
|
|
188886
|
+
if (sourceRelease.release_id !== releaseId)
|
|
188887
|
+
throw new Error;
|
|
188888
|
+
} catch {
|
|
188889
|
+
return releaseControlFailure(operation, "INVALID_RESPONSE", source.status, identity);
|
|
188890
|
+
}
|
|
188891
|
+
result = await http.post(endpoint, {
|
|
188892
|
+
release_id: releaseId,
|
|
188893
|
+
...typeof args["branch_name"] === "string" ? { branch_name: args["branch_name"] } : {},
|
|
188894
|
+
...typeof args["data_mode"] === "string" ? { data_mode: args["data_mode"] } : {},
|
|
188895
|
+
...typeof args["configuration_id"] === "string" ? { configuration_id: args["configuration_id"] } : {}
|
|
188896
|
+
}, { timeoutMs: 120000, maxJsonBytes: 131072, responseTimeoutMs: 30000 });
|
|
188897
|
+
} else if (action === "cleanup_preview") {
|
|
188898
|
+
result = await http.deleteReleaseMutation(endpoint);
|
|
188899
|
+
} else {
|
|
188900
|
+
result = await http.get(endpoint, { maxJsonBytes: 131072, responseTimeoutMs: 30000 });
|
|
188901
|
+
}
|
|
188902
|
+
}
|
|
188903
|
+
if (!result.ok)
|
|
188904
|
+
return action === "create_preview" || action === "cleanup_preview" ? releaseControlMutationFailure(operation, result, identity) : releaseControlFailure(operation, "HTTP_ERROR", result.status, identity);
|
|
188905
|
+
try {
|
|
188906
|
+
if (result.status !== (action === "create_preview" ? 202 : 200))
|
|
188907
|
+
throw new Error;
|
|
188908
|
+
if (action === "list_previews") {
|
|
188909
|
+
const list = parsePreviewList(result.data, identity);
|
|
188910
|
+
return releaseControlSuccess(operation, { ...identity, previews: list.previews });
|
|
188911
|
+
}
|
|
188912
|
+
const preview = parsePreviewReceipt(result.data, {
|
|
188913
|
+
...identity,
|
|
188914
|
+
...typeof args["preview_id"] === "string" ? { preview_id: args["preview_id"] } : {},
|
|
188915
|
+
...action === "get_preview_plan" && typeof args["release_id"] === "string" ? { release_id: args["release_id"] } : {},
|
|
188916
|
+
...action === "get_preview_plan" && typeof args["branch_ref"] === "string" ? { branch_ref: args["branch_ref"] } : {},
|
|
188917
|
+
...action === "get_preview_plan" || action === "create_preview" ? { data_mode: String(args["data_mode"] ?? "schema_only") } : {},
|
|
188918
|
+
...action === "create_preview" && typeof args["configuration_id"] === "string" ? { configuration_id: args["configuration_id"] } : {}
|
|
188919
|
+
});
|
|
188920
|
+
if (action === "get_preview_plan" && preview.status !== "planned" || action === "create_preview" && preview.status !== "provisioning")
|
|
188921
|
+
throw new Error;
|
|
188922
|
+
if (action === "create_preview" && (!sourceRelease || preview.resources.database_branch.branch_ref !== `pv${preview.preview_id.replaceAll("-", "").slice(0, 18)}` || preview.resources.database_branch.branch_ref === ref || preview.release_id !== applicationReleaseId(preview.resources.database_branch.branch_ref, id, sourceRelease.manifest_sha256)))
|
|
188923
|
+
throw new Error;
|
|
188924
|
+
if (action === "cleanup_preview" && (preview.status !== "cleaned" || !preview.cleanup.completed || preview.cleanup.required)) {
|
|
188925
|
+
return releaseControlFailure(operation, "MUTATION_NOT_SUCCEEDED", result.status, { ...identity, preview });
|
|
188926
|
+
}
|
|
188927
|
+
return releaseControlSuccess(operation, { ...identity, preview });
|
|
188928
|
+
} catch {
|
|
188929
|
+
return releaseControlFailure(operation, action === "create_preview" || action === "cleanup_preview" ? "OUTCOME_UNKNOWN" : "INVALID_RESPONSE", result.status, identity);
|
|
188930
|
+
}
|
|
188931
|
+
}
|
|
188119
188932
|
if (action === "logs") {
|
|
188120
188933
|
const environmentId = text(args, "environment_id");
|
|
188121
188934
|
if (!Check2(ApplicationIdSchema2, environmentId))
|
|
@@ -188292,7 +189105,7 @@ function matchingText(candidate, maxLength, pattern) {
|
|
|
188292
189105
|
const candidateText = boundedText2(candidate, maxLength);
|
|
188293
189106
|
return candidateText && pattern.test(candidateText) ? candidateText : null;
|
|
188294
189107
|
}
|
|
188295
|
-
function
|
|
189108
|
+
function canonicalTimestamp3(candidate) {
|
|
188296
189109
|
const timestamp = boundedText2(candidate, 64);
|
|
188297
189110
|
if (!timestamp)
|
|
188298
189111
|
return null;
|
|
@@ -188307,7 +189120,7 @@ function projectedSummary(project) {
|
|
|
188307
189120
|
organization_slug: matchingText(project.organization_slug, 128, SAFE_IDENTIFIER_PATTERN),
|
|
188308
189121
|
name: boundedText2(project.name, 100),
|
|
188309
189122
|
region: matchingText(project.region, 64, REGION_PATTERN),
|
|
188310
|
-
created_at:
|
|
189123
|
+
created_at: canonicalTimestamp3(project.created_at),
|
|
188311
189124
|
status: matchingText(project.status, 64, STATUS_PATTERN)
|
|
188312
189125
|
};
|
|
188313
189126
|
return Object.values(summary).every((field) => field !== null) ? summary : null;
|
|
@@ -194299,6 +195112,16 @@ async function applyScaffoldWrites(plan) {
|
|
|
194299
195112
|
// src/shared/tools/app-tools.ts
|
|
194300
195113
|
var REMOTE_APP_ACTIONS = {
|
|
194301
195114
|
upload: "upload_release",
|
|
195115
|
+
"transfer-plan": "get_release_transfer_plan",
|
|
195116
|
+
"promote-plan": "get_promotion_plan",
|
|
195117
|
+
"promote-status": "get_promotion_status",
|
|
195118
|
+
"promote-reconcile": "reconcile_promotion",
|
|
195119
|
+
"preview-plan": "get_preview_plan",
|
|
195120
|
+
"preview-list": "list_previews",
|
|
195121
|
+
"preview-create": "create_preview",
|
|
195122
|
+
"preview-status": "get_preview",
|
|
195123
|
+
"preview-cleanup": "cleanup_preview",
|
|
195124
|
+
transfer: "transfer_release",
|
|
194302
195125
|
configure: "put_configuration",
|
|
194303
195126
|
deploy: "deploy_release",
|
|
194304
195127
|
"deploy-plan": "get_deploy_plan",
|
|
@@ -194310,33 +195133,35 @@ var REMOTE_APP_ACTIONS = {
|
|
|
194310
195133
|
reconcile: "reconcile_activation",
|
|
194311
195134
|
retire: "retire_activation",
|
|
194312
195135
|
logs: "logs",
|
|
194313
|
-
"preview-plan": "get_preview_plan",
|
|
194314
195136
|
preview: "create_preview",
|
|
194315
|
-
previews: "list_previews"
|
|
194316
|
-
"preview-status": "get_preview",
|
|
194317
|
-
"preview-cleanup": "cleanup_preview"
|
|
195137
|
+
previews: "list_previews"
|
|
194318
195138
|
};
|
|
194319
195139
|
var REMOTE_APP_DESCRIPTIONS = {
|
|
194320
|
-
ref: "[upload/configure/
|
|
194321
|
-
id: "[upload/configure/
|
|
194322
|
-
environment_id: "[configure/
|
|
194323
|
-
configuration_id: "[
|
|
194324
|
-
activation_id: "[deploy/rollback/reconcile/retire]
|
|
194325
|
-
expected_activation_id: "[deploy/rollback]
|
|
195140
|
+
ref: "[upload/configure/transfer-plan/transfer/promote-plan/diff/promote/promote-status/promote-reconcile/preview-plan/preview/previews/preview-list/preview-create/preview-status/preview-cleanup/deploy/deploy-plan/status/rollback/rollback-plan/history/reconcile/retire] Project ref (defaults to context)",
|
|
195141
|
+
id: "[upload/configure/transfer-plan/transfer/promote-plan/diff/promote/promote-status/promote-reconcile/preview-plan/preview/previews/preview-list/preview-create/preview-status/preview-cleanup/deploy/deploy-plan/status/rollback/rollback-plan/history/reconcile/retire] Application ID",
|
|
195142
|
+
environment_id: "[configure/promote-plan/diff/promote/promote-status/promote-reconcile/preview-plan/preview/previews/preview-list/preview-create/preview-status/preview-cleanup/deploy/deploy-plan/status/rollback/rollback-plan/history/reconcile/retire] Target environment ID",
|
|
195143
|
+
configuration_id: "[promote-plan/diff/promote/preview-plan/preview/preview-create/deploy/deploy-plan/rollback] Immutable target configuration revision",
|
|
195144
|
+
activation_id: "[deploy/rollback/reconcile/retire] Required explicit activation ID",
|
|
195145
|
+
expected_activation_id: "[deploy/rollback] Required current activation ID, or absent for first activation",
|
|
194326
195146
|
configuration_path: "[configure] Configuration write JSON including revision and expected revision",
|
|
194327
195147
|
manifest_path: "[upload] Local delivery.manifest.json",
|
|
194328
|
-
release_id: "[deploy/deploy-plan/diff/rollback/reconcile
|
|
194329
|
-
|
|
194330
|
-
|
|
194331
|
-
|
|
194332
|
-
|
|
194333
|
-
|
|
194334
|
-
|
|
194335
|
-
|
|
195148
|
+
release_id: "[preview-plan/preview/preview-create/deploy/deploy-plan/diff/rollback/reconcile] Required immutable application release ID",
|
|
195149
|
+
source_ref: "[transfer-plan/transfer/promote-plan/diff/promote] Source project ref",
|
|
195150
|
+
source_release_id: "[transfer-plan/transfer/promote-plan/diff/promote] Source immutable release ID",
|
|
195151
|
+
source_environment_id: "[promote-plan/diff/promote] Source environment ID",
|
|
195152
|
+
mutation_id: "[promote-status/promote-reconcile] Stable promotion mutation ID",
|
|
195153
|
+
approved_migration_digest: "[promote] Exact reviewed migration execution-plan digest",
|
|
195154
|
+
branch_ref: "[preview-plan] Preview branch identity",
|
|
195155
|
+
branch_name: "[preview/preview-create] Human-readable preview branch name",
|
|
195156
|
+
data_mode: "[preview-plan/preview/preview-create] schema_only or full_clone",
|
|
195157
|
+
ttl_seconds: "[preview-plan/preview/preview-create] Lifetime from 300 to 604800 seconds; creation defaults to the platform TTL",
|
|
195158
|
+
wait: "[preview/preview-create/preview-status] Wait for verified readiness of the selected preview",
|
|
195159
|
+
timeout_seconds: "[preview/preview-create/preview-status] Observation budget with --wait, from 1 to 3600 seconds (default 300)",
|
|
195160
|
+
preview_id: "[preview-status/preview-cleanup] Preview ID",
|
|
194336
195161
|
cursor: "[history] Returned page cursor",
|
|
194337
195162
|
limit: "[history] Page size, default 20 (1-100)"
|
|
194338
195163
|
};
|
|
194339
|
-
var { action: _remoteAction, ...remoteFields } = APPLICATION_TOOL_SCHEMA;
|
|
195164
|
+
var { action: _remoteAction, plan: _remotePlan, ...remoteFields } = APPLICATION_TOOL_SCHEMA;
|
|
194340
195165
|
var REMOTE_APP_SCHEMA = Partial(_Object_(remoteFields)).properties;
|
|
194341
195166
|
for (const [name, schema] of Object.entries(REMOTE_APP_SCHEMA)) {
|
|
194342
195167
|
const description = REMOTE_APP_DESCRIPTIONS[name];
|
|
@@ -195233,6 +196058,30 @@ async function runDelivery(args) {
|
|
|
195233
196058
|
const result = await buildDeliveryProject(options, loaded.delivery);
|
|
195234
196059
|
return textResult(JSON.stringify(result, null, 2), !result.ok);
|
|
195235
196060
|
}
|
|
196061
|
+
function formatReleaseTransfer(result) {
|
|
196062
|
+
if (result.isError)
|
|
196063
|
+
return result;
|
|
196064
|
+
try {
|
|
196065
|
+
const payload = JSON.parse(result.content[0]?.text ?? "");
|
|
196066
|
+
if (!payload || typeof payload !== "object" || !("ok" in payload) || payload.ok !== true)
|
|
196067
|
+
return result;
|
|
196068
|
+
const plan = "plan" in payload ? parseApplicationReleaseTransferPlan(payload.plan) : null;
|
|
196069
|
+
const receipt = !plan && "transfer" in payload ? parseApplicationReleaseTransferResult(payload.transfer) : null;
|
|
196070
|
+
const data = plan ?? receipt;
|
|
196071
|
+
if (!data)
|
|
196072
|
+
return result;
|
|
196073
|
+
return textResult([
|
|
196074
|
+
`${data.project_ref}/${data.application_id}: ${plan ? plan.action === "no-op" ? "No changes" : "Transfer required" : "Artifact transferred"}`,
|
|
196075
|
+
`Source: ${data.source.project_ref} / ${data.source.release_id}`,
|
|
196076
|
+
`Target release: ${data.candidate_release_id}`,
|
|
196077
|
+
`Manifest: ${data.source.manifest_sha256}`,
|
|
196078
|
+
"Activation: not performed"
|
|
196079
|
+
].join(`
|
|
196080
|
+
`));
|
|
196081
|
+
} catch {
|
|
196082
|
+
return result;
|
|
196083
|
+
}
|
|
196084
|
+
}
|
|
195236
196085
|
var historyReceiptSchema = _Object_({
|
|
195237
196086
|
schema: Literal(RELEASE_CONTROL_RESPONSE_SCHEMA),
|
|
195238
196087
|
ok: Literal(true),
|
|
@@ -195302,6 +196151,167 @@ function formatRemoteDeployPlan(result) {
|
|
|
195302
196151
|
return result;
|
|
195303
196152
|
}
|
|
195304
196153
|
}
|
|
196154
|
+
function formatPromotionPlan2(result) {
|
|
196155
|
+
if (result.isError)
|
|
196156
|
+
return result;
|
|
196157
|
+
try {
|
|
196158
|
+
const payload = JSON.parse(result.content[0]?.text ?? "");
|
|
196159
|
+
if (!payload || typeof payload !== "object" || !("plan" in payload))
|
|
196160
|
+
return result;
|
|
196161
|
+
const plan = parseApplicationPromotionPlan(payload.plan);
|
|
196162
|
+
return textResult([
|
|
196163
|
+
`${plan.project_ref}/${plan.application_id}/${plan.environment_id}: ${plan.action}`,
|
|
196164
|
+
`Source: ${plan.source.project_ref}/${plan.source.environment_id}`,
|
|
196165
|
+
...plan.blockers.length ? [`Blocked: ${plan.blockers.join(", ")}`] : [],
|
|
196166
|
+
...plan.steps.length ? [`Next: ${plan.steps.join(" -> ")}`] : [],
|
|
196167
|
+
"Execution: not performed"
|
|
196168
|
+
].join(`
|
|
196169
|
+
`));
|
|
196170
|
+
} catch {
|
|
196171
|
+
return result;
|
|
196172
|
+
}
|
|
196173
|
+
}
|
|
196174
|
+
function formatPromotionExecution(result) {
|
|
196175
|
+
if (result.isError)
|
|
196176
|
+
return result;
|
|
196177
|
+
try {
|
|
196178
|
+
const payload = JSON.parse(result.content[0]?.text ?? "");
|
|
196179
|
+
if (!payload || typeof payload !== "object")
|
|
196180
|
+
return result;
|
|
196181
|
+
const operation = "operation" in payload && typeof payload.operation === "string" ? payload.operation : "";
|
|
196182
|
+
if (operation !== "applications.promote_application")
|
|
196183
|
+
return result;
|
|
196184
|
+
if ("result" in payload && Check2(ApplicationPromotionResultSchema, payload.result)) {
|
|
196185
|
+
const envelope = payload;
|
|
196186
|
+
const receipt = payload.result;
|
|
196187
|
+
if (receipt.project_ref !== envelope.project_ref || receipt.application_id !== envelope.application_id || receipt.environment_id !== envelope.environment_id || receipt.mutation_id !== envelope.mutation_id || receipt.plan_sha256 !== envelope.plan_sha256)
|
|
196188
|
+
return result;
|
|
196189
|
+
return textResult([
|
|
196190
|
+
`${String(envelope.project_ref)}/${String(envelope.application_id)}/${String(envelope.environment_id)}: promoted`,
|
|
196191
|
+
`Mutation: ${String(envelope.mutation_id)}`,
|
|
196192
|
+
`Activation: ${String(receipt.activation_id)}`,
|
|
196193
|
+
`Replayed: ${String(receipt.replayed)}`
|
|
196194
|
+
].join(`
|
|
196195
|
+
`));
|
|
196196
|
+
}
|
|
196197
|
+
} catch {}
|
|
196198
|
+
return result;
|
|
196199
|
+
}
|
|
196200
|
+
function formatWorkflowObservation(result, action) {
|
|
196201
|
+
if (result.isError)
|
|
196202
|
+
return result;
|
|
196203
|
+
try {
|
|
196204
|
+
const payload = JSON.parse(result.content[0]?.text ?? "");
|
|
196205
|
+
if (!payload || typeof payload !== "object")
|
|
196206
|
+
return result;
|
|
196207
|
+
const data = payload;
|
|
196208
|
+
if (typeof data.project_ref !== "string" || typeof data.application_id !== "string" || typeof data.environment_id !== "string")
|
|
196209
|
+
return result;
|
|
196210
|
+
const identity = {
|
|
196211
|
+
project_ref: data.project_ref,
|
|
196212
|
+
application_id: data.application_id,
|
|
196213
|
+
environment_id: data.environment_id
|
|
196214
|
+
};
|
|
196215
|
+
const label = `${identity.project_ref}/${identity.application_id}/${identity.environment_id}`;
|
|
196216
|
+
if (action === "promote-status" || action === "promote-reconcile") {
|
|
196217
|
+
if (!data.mutation || typeof data.mutation !== "object" || !("mutation_id" in data.mutation) || typeof data.mutation.mutation_id !== "string")
|
|
196218
|
+
return result;
|
|
196219
|
+
const status = parsePromotionStatus({ ...identity, mutation: data.mutation, promotion: data.promotion }, { ...identity, mutation_id: data.mutation.mutation_id });
|
|
196220
|
+
return textResult([
|
|
196221
|
+
`${label}: ${status.mutation.status}`,
|
|
196222
|
+
`Mutation: ${status.mutation.mutation_id}`,
|
|
196223
|
+
`Phase: ${status.promotion.phase ?? "not recorded"}`,
|
|
196224
|
+
...status.promotion.backup_id ? [`Backup: ${status.promotion.backup_id}`] : []
|
|
196225
|
+
].join(`
|
|
196226
|
+
`));
|
|
196227
|
+
}
|
|
196228
|
+
if (action === "preview-list") {
|
|
196229
|
+
const list = parsePreviewList({ ...identity, previews: data.previews }, identity);
|
|
196230
|
+
return textResult([
|
|
196231
|
+
`${label}: ${list.previews.length} preview(s)`,
|
|
196232
|
+
...list.previews.map((preview) => `${preview.preview_id} ${preview.status} ${preview.resources.database_branch.branch_ref}`)
|
|
196233
|
+
].join(`
|
|
196234
|
+
`));
|
|
196235
|
+
}
|
|
196236
|
+
if (["preview-create", "preview-status", "preview-cleanup"].includes(action)) {
|
|
196237
|
+
const preview = parsePreviewReceipt(data.preview, identity);
|
|
196238
|
+
return textResult([
|
|
196239
|
+
`${label}: ${preview.status}`,
|
|
196240
|
+
`Preview: ${preview.preview_id}`,
|
|
196241
|
+
`Branch: ${preview.resources.database_branch.branch_ref} (${preview.resources.database_branch.data_mode})`,
|
|
196242
|
+
`Cleanup: ${preview.cleanup.completed ? "completed" : preview.cleanup.required ? "required" : "not required"}`
|
|
196243
|
+
].join(`
|
|
196244
|
+
`));
|
|
196245
|
+
}
|
|
196246
|
+
} catch {}
|
|
196247
|
+
return result;
|
|
196248
|
+
}
|
|
196249
|
+
async function runPromotion(request, options, delegate) {
|
|
196250
|
+
const ref = request.ref ?? options.projectRef;
|
|
196251
|
+
if (request.mutation_id !== undefined)
|
|
196252
|
+
throw new Error("app promote generates a new mutation ID; use promote-status or promote-reconcile to inspect an existing mutation");
|
|
196253
|
+
const { format, json, ...remoteRequest } = request;
|
|
196254
|
+
const planArgs = {
|
|
196255
|
+
...remoteRequest,
|
|
196256
|
+
action: "get_promotion_plan",
|
|
196257
|
+
ref
|
|
196258
|
+
};
|
|
196259
|
+
validateToolArguments(APPLICATION_TOOL_SCHEMA, planArgs);
|
|
196260
|
+
const planned = await delegate(planArgs);
|
|
196261
|
+
if (planned.isError)
|
|
196262
|
+
return planned;
|
|
196263
|
+
let payload;
|
|
196264
|
+
try {
|
|
196265
|
+
payload = JSON.parse(planned.content[0]?.text ?? "");
|
|
196266
|
+
} catch {
|
|
196267
|
+
return textResult("Promotion plan response was not valid JSON.", true);
|
|
196268
|
+
}
|
|
196269
|
+
if (!payload || typeof payload !== "object" || !("plan" in payload)) {
|
|
196270
|
+
return textResult("Promotion plan response was invalid.", true);
|
|
196271
|
+
}
|
|
196272
|
+
let plan;
|
|
196273
|
+
try {
|
|
196274
|
+
plan = parseApplicationPromotionPlan(payload.plan);
|
|
196275
|
+
} catch {
|
|
196276
|
+
return textResult("Promotion plan failed immutable validation.", true);
|
|
196277
|
+
}
|
|
196278
|
+
const identity = { project_ref: ref, application_id: request.id, environment_id: request.environment_id };
|
|
196279
|
+
if (plan.project_ref !== ref || plan.application_id !== request.id || plan.environment_id !== request.environment_id || plan.source.project_ref !== request.source_ref || plan.source.environment_id !== request.source_environment_id || plan.source.release_id !== request.source_release_id || request.configuration_id !== undefined && plan.target.configuration_id !== request.configuration_id) {
|
|
196280
|
+
return releaseControlFailure("applications.promote_application", "INVALID_RESPONSE", null, identity);
|
|
196281
|
+
}
|
|
196282
|
+
if (plan.action === "no-op") {
|
|
196283
|
+
const noOp = releaseControlSuccess("applications.promote_application", {
|
|
196284
|
+
project_ref: plan.project_ref,
|
|
196285
|
+
application_id: plan.application_id,
|
|
196286
|
+
environment_id: plan.environment_id,
|
|
196287
|
+
plan,
|
|
196288
|
+
no_op: true,
|
|
196289
|
+
execution_performed: false
|
|
196290
|
+
});
|
|
196291
|
+
return format === "json" || json ? noOp : textResult(`${plan.project_ref}/${plan.application_id}/${plan.environment_id}: no-op
|
|
196292
|
+
Execution: skipped`);
|
|
196293
|
+
}
|
|
196294
|
+
if (plan.blockers.some((code) => !["MIGRATION_PENDING", "BACKUP_REQUIRED"].includes(code))) {
|
|
196295
|
+
return releaseControlFailure("applications.promote_application", "MUTATION_NOT_SUCCEEDED", null, { ...identity, plan, execution_performed: false });
|
|
196296
|
+
}
|
|
196297
|
+
if (!plan.target.configuration_id)
|
|
196298
|
+
return textResult("Promotion plan has no target configuration revision.", true);
|
|
196299
|
+
const mutationId = crypto.randomUUID();
|
|
196300
|
+
const executeArgs = {
|
|
196301
|
+
...remoteRequest,
|
|
196302
|
+
action: "promote_application",
|
|
196303
|
+
ref,
|
|
196304
|
+
mutation_id: mutationId,
|
|
196305
|
+
source_ref: plan.source.project_ref,
|
|
196306
|
+
source_environment_id: plan.source.environment_id,
|
|
196307
|
+
source_release_id: plan.source.release_id,
|
|
196308
|
+
configuration_id: plan.target.configuration_id,
|
|
196309
|
+
plan
|
|
196310
|
+
};
|
|
196311
|
+
validateToolArguments(APPLICATION_TOOL_SCHEMA, executeArgs);
|
|
196312
|
+
const result = await delegate(executeArgs);
|
|
196313
|
+
return format === "json" || json ? result : formatPromotionExecution(result);
|
|
196314
|
+
}
|
|
195305
196315
|
async function runAppTool(request, options = {}) {
|
|
195306
196316
|
if (request.dry_run !== undefined && request["dry-run"] !== undefined || request.register_in !== undefined && request["register-in"] !== undefined || request.database_url !== undefined && request["database-url"] !== undefined) {
|
|
195307
196317
|
throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "Do not combine hyphenated and underscored aliases of the same flag");
|
|
@@ -195318,14 +196328,22 @@ async function runAppTool(request, options = {}) {
|
|
|
195318
196328
|
if (request.database_url !== undefined && request.action !== "dev" && request.action !== "watch") {
|
|
195319
196329
|
throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "--database-url applies only to app dev --profile integration");
|
|
195320
196330
|
}
|
|
195321
|
-
if (request.json !== undefined && (request.action
|
|
195322
|
-
throw new Error("--json applies only to app
|
|
196331
|
+
if (request.json !== undefined && !Object.hasOwn(REMOTE_APP_ACTIONS, request.action) && request.action !== "promote")
|
|
196332
|
+
throw new Error("--json applies only to remote app actions");
|
|
196333
|
+
if (request.json && request.format === "text")
|
|
196334
|
+
throw new Error("--json and --format text cannot be combined");
|
|
196335
|
+
if (request.action === "promote") {
|
|
196336
|
+
const delegate = options.getApplications?.();
|
|
196337
|
+
if (!delegate)
|
|
196338
|
+
return textResult("App remote actions require a Management API context.", true);
|
|
196339
|
+
return runPromotion(request, options, delegate);
|
|
195323
196340
|
}
|
|
195324
196341
|
if (Object.hasOwn(REMOTE_APP_ACTIONS, request.action)) {
|
|
195325
196342
|
const delegate = options.getApplications?.();
|
|
195326
196343
|
if (!delegate)
|
|
195327
196344
|
return textResult("App remote actions require a Management API context.", true);
|
|
195328
|
-
const
|
|
196345
|
+
const promotionDiff = request.action === "diff" && request.source_ref !== undefined && request.source_environment_id !== undefined && request.source_release_id !== undefined;
|
|
196346
|
+
const action = promotionDiff ? "get_promotion_plan" : REMOTE_APP_ACTIONS[request.action];
|
|
195329
196347
|
const { format, json, ...remoteRequest } = request;
|
|
195330
196348
|
const args = {
|
|
195331
196349
|
...remoteRequest,
|
|
@@ -195337,7 +196355,14 @@ async function runAppTool(request, options = {}) {
|
|
|
195337
196355
|
if (request.action === "history" && format !== "json" && json !== true) {
|
|
195338
196356
|
return historyText(result, request, args.ref);
|
|
195339
196357
|
}
|
|
195340
|
-
|
|
196358
|
+
if ((request.action === "deploy-plan" || request.action === "diff") && format !== "json" && (request.action === "deploy-plan" || request.source_ref === undefined || request.source_environment_id === undefined || request.source_release_id === undefined)) {
|
|
196359
|
+
return formatRemoteDeployPlan(result);
|
|
196360
|
+
}
|
|
196361
|
+
if (request.action === "promote-plan" || request.action === "diff" && request.source_ref !== undefined && request.source_environment_id !== undefined && request.source_release_id !== undefined) {
|
|
196362
|
+
if (!result.isError && format !== "json" && !json)
|
|
196363
|
+
return formatPromotionPlan2(result);
|
|
196364
|
+
}
|
|
196365
|
+
return (request.action === "transfer-plan" || request.action === "transfer") && format !== "json" && !json ? formatReleaseTransfer(result) : format !== "json" && !json ? formatWorkflowObservation(result, request.action) : result;
|
|
195341
196366
|
}
|
|
195342
196367
|
switch (request.action) {
|
|
195343
196368
|
case "verify-plan": {
|
|
@@ -195411,6 +196436,9 @@ function registerAppTools(server2, options = {}) {
|
|
|
195411
196436
|
"build",
|
|
195412
196437
|
"upload",
|
|
195413
196438
|
"configure",
|
|
196439
|
+
"transfer-plan",
|
|
196440
|
+
"transfer",
|
|
196441
|
+
"promote-plan",
|
|
195414
196442
|
"deploy",
|
|
195415
196443
|
"deploy-plan",
|
|
195416
196444
|
"diff",
|
|
@@ -195424,9 +196452,14 @@ function registerAppTools(server2, options = {}) {
|
|
|
195424
196452
|
"preview-plan",
|
|
195425
196453
|
"preview",
|
|
195426
196454
|
"previews",
|
|
196455
|
+
"preview-list",
|
|
196456
|
+
"preview-create",
|
|
195427
196457
|
"preview-status",
|
|
195428
|
-
"preview-cleanup"
|
|
195429
|
-
|
|
196458
|
+
"preview-cleanup",
|
|
196459
|
+
"promote",
|
|
196460
|
+
"promote-status",
|
|
196461
|
+
"promote-reconcile"
|
|
196462
|
+
]), "App action; promote obtains and verifies its plan before one mutation request"),
|
|
195430
196463
|
kind: optional(stringEnum(["module", "command", "query", "controller", "job", "contract", "resource"]), "[generate] Scaffold kind"),
|
|
195431
196464
|
template: optional(stringEnum(["minimal", "http", "command", "edge"]), "[init] Minimal application by default; explicit http/command/edge recipes"),
|
|
195432
196465
|
name: optional(String2(), "[init/generate] Project or object name"),
|
|
@@ -195441,8 +196474,8 @@ function registerAppTools(server2, options = {}) {
|
|
|
195441
196474
|
include: optional(String2(), "[dev/compile/check] Comma-separated glob patterns for source files"),
|
|
195442
196475
|
out_dir: optional(String2(), "[dev/compile/plan/build/export-tools] Output directory (default: configured outDir)"),
|
|
195443
196476
|
strict: optional(Boolean2(), "[dev/compile/check] Promote warnings to errors"),
|
|
195444
|
-
format: optional(stringEnum(["text", "json"]), "[dev/generate/compile/check/plan/deploy-plan/diff/history/graph/export-tools] Output format (default: text)"),
|
|
195445
|
-
json: optional(
|
|
196477
|
+
format: optional(stringEnum(["text", "json"]), "[dev/generate/compile/check/plan/deploy-plan/diff/history/promote-plan/graph/export-tools] Output format (default: text)"),
|
|
196478
|
+
json: optional(Boolean2(), "[history/transfer-plan/transfer/promote-plan] Preserve or return the full machine-readable receipt"),
|
|
195446
196479
|
profile: optional(stringEnum(["fast", "integration"]), "[dev] Run dev or dev:integration; integration verifies an explicit loopback database"),
|
|
195447
196480
|
once: optional(Boolean2(), "[dev] Validate and report once without watching"),
|
|
195448
196481
|
watch: optional(Boolean2(), "[dev] Watch for changes (default: true; --once disables)"),
|
|
@@ -195484,7 +196517,7 @@ import { existsSync as existsSync13 } from "node:fs";
|
|
|
195484
196517
|
import { join as join17, resolve as resolve21 } from "node:path";
|
|
195485
196518
|
|
|
195486
196519
|
// src/shared/tools/db-workflow.ts
|
|
195487
|
-
import { createHash as
|
|
196520
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
195488
196521
|
import { lstat as lstat4, mkdir as mkdir7, mkdtemp, readFile as readFile6, readdir as readdir7, rename as rename3, rm as rm2, writeFile as writeFile6 } from "node:fs/promises";
|
|
195489
196522
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
195490
196523
|
import { dirname as dirname10, isAbsolute as isAbsolute7, join as join16, relative as relative9, resolve as resolve20 } from "node:path";
|
|
@@ -195550,7 +196583,7 @@ function parseDatabaseSourcesConfig(value) {
|
|
|
195550
196583
|
}
|
|
195551
196584
|
|
|
195552
196585
|
// src/shared/tools/db-workflow.ts
|
|
195553
|
-
var hash3 = (content) =>
|
|
196586
|
+
var hash3 = (content) => createHash7("sha256").update(content).digest("hex");
|
|
195554
196587
|
var result = (value, isError = false) => ({
|
|
195555
196588
|
isError,
|
|
195556
196589
|
content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
|
|
@@ -196239,7 +197272,7 @@ function parseHeaderEnvironment(input) {
|
|
|
196239
197272
|
}
|
|
196240
197273
|
}
|
|
196241
197274
|
var headerEnvironmentSchema = Optional(decodedSchema(Union([String2(), headerEnvironmentRecord]), headerEnvironmentRecord, parseHeaderEnvironment));
|
|
196242
|
-
function
|
|
197275
|
+
function objectRecord4(candidate) {
|
|
196243
197276
|
return candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
|
|
196244
197277
|
}
|
|
196245
197278
|
function boundedCronInteger(input, minimum, maximum) {
|
|
@@ -196284,7 +197317,7 @@ function readScheduleBodyFile(bodyPathInput) {
|
|
|
196284
197317
|
throw error;
|
|
196285
197318
|
throw new Error("Scheduled Function body file must contain exact JSON");
|
|
196286
197319
|
}
|
|
196287
|
-
const body =
|
|
197320
|
+
const body = objectRecord4(payload);
|
|
196288
197321
|
if (!body)
|
|
196289
197322
|
throw new Error("Scheduled Function body file must contain a JSON object");
|
|
196290
197323
|
return body;
|
|
@@ -196325,7 +197358,7 @@ function headerValueIsStable(name, value) {
|
|
|
196325
197358
|
function scheduleHeaders(mapping, environment) {
|
|
196326
197359
|
if (mapping === undefined)
|
|
196327
197360
|
return;
|
|
196328
|
-
const headerEnvironment =
|
|
197361
|
+
const headerEnvironment = objectRecord4(mapping);
|
|
196329
197362
|
if (!headerEnvironment)
|
|
196330
197363
|
throw new Error("'header_env' must be a JSON object");
|
|
196331
197364
|
const entries = Object.entries(headerEnvironment).map(([headerName, environmentName]) => resolvedHeaderEntry(headerName, environmentName, environment));
|
|
@@ -196366,8 +197399,8 @@ function safeSchedulePayload(schedule) {
|
|
|
196366
197399
|
return { body_empty: schedule.body_empty, header_names: headerNames };
|
|
196367
197400
|
}
|
|
196368
197401
|
function legacySchedulePayload(schedule) {
|
|
196369
|
-
const body =
|
|
196370
|
-
const headers =
|
|
197402
|
+
const body = objectRecord4(schedule.body);
|
|
197403
|
+
const headers = objectRecord4(schedule.headers);
|
|
196371
197404
|
if (!body || !headers)
|
|
196372
197405
|
return null;
|
|
196373
197406
|
const headerNames = safeHeaderNames(Object.keys(headers));
|
|
@@ -196384,7 +197417,7 @@ function safeHeaderNames(candidate) {
|
|
|
196384
197417
|
return valid && new Set(names).size === names.length ? names.sort() : null;
|
|
196385
197418
|
}
|
|
196386
197419
|
function safeSchedule(candidate) {
|
|
196387
|
-
const schedule =
|
|
197420
|
+
const schedule = objectRecord4(candidate);
|
|
196388
197421
|
if (!schedule || !validSafeSchedule(schedule))
|
|
196389
197422
|
return null;
|
|
196390
197423
|
const safePayload = safeSchedulePayload(schedule);
|
|
@@ -196430,7 +197463,7 @@ function listResponse(ref, response) {
|
|
|
196430
197463
|
const operation = "scheduled_functions.list";
|
|
196431
197464
|
if (!response.ok)
|
|
196432
197465
|
return scheduleFailure(operation, response);
|
|
196433
|
-
const payload =
|
|
197466
|
+
const payload = objectRecord4(response.data);
|
|
196434
197467
|
const rawSchedules = payload?.schedules;
|
|
196435
197468
|
const schedules = Array.isArray(rawSchedules) ? rawSchedules.map(safeSchedule) : null;
|
|
196436
197469
|
if (payload?.project_ref !== ref || !schedules || schedules.some((schedule) => !schedule)) {
|
|
@@ -196445,7 +197478,7 @@ function getResponse(ref, scheduleId, response) {
|
|
|
196445
197478
|
const operation = "scheduled_functions.get";
|
|
196446
197479
|
if (!response.ok)
|
|
196447
197480
|
return scheduleFailure(operation, response);
|
|
196448
|
-
const payload =
|
|
197481
|
+
const payload = objectRecord4(response.data);
|
|
196449
197482
|
const schedule = safeSchedule(payload?.schedule);
|
|
196450
197483
|
if (payload?.project_ref !== ref || !schedule || schedule.id !== scheduleId) {
|
|
196451
197484
|
return releaseControlFailure(operation, "INVALID_RESPONSE", null);
|
|
@@ -196458,7 +197491,7 @@ function mutationResponse(expectation, response) {
|
|
|
196458
197491
|
const operation = `scheduled_functions.${action}`;
|
|
196459
197492
|
if (!response.ok)
|
|
196460
197493
|
return releaseControlMutationFailure(operation, response);
|
|
196461
|
-
const payload =
|
|
197494
|
+
const payload = objectRecord4(response.data);
|
|
196462
197495
|
const schedule = safeSchedule(payload?.schedule);
|
|
196463
197496
|
const confirmsRequest = schedule && Object.entries(expectedFields).every(([field, expected]) => isDeepStrictEqual(schedule[field], expected));
|
|
196464
197497
|
const confirmsRevision = action === "create" || payload?.previous_updated_at === expectation.expectedUpdatedAt && schedule !== null && schedule.updated_at > expectation.expectedUpdatedAt;
|
|
@@ -196476,7 +197509,7 @@ function deleteResponse(ref, scheduleId, expectedUpdatedAt, response) {
|
|
|
196476
197509
|
const operation = "scheduled_functions.delete";
|
|
196477
197510
|
if (!response.ok)
|
|
196478
197511
|
return releaseControlMutationFailure(operation, response);
|
|
196479
|
-
const payload =
|
|
197512
|
+
const payload = objectRecord4(response.data);
|
|
196480
197513
|
if (payload?.deleted !== true || payload.project_ref !== ref || payload.schedule_id !== scheduleId || payload.deleted_updated_at !== expectedUpdatedAt) {
|
|
196481
197514
|
return releaseControlFailure(operation, "OUTCOME_UNKNOWN", response.status);
|
|
196482
197515
|
}
|
|
@@ -196616,204 +197649,6 @@ var SCHEDULE_TOOL_SCHEMA = {
|
|
|
196616
197649
|
header_env: withDescription(headerEnvironmentSchema, "[create/update] JSON map of HTTP header names to environment variable names")
|
|
196617
197650
|
};
|
|
196618
197651
|
|
|
196619
|
-
// src/shared/mutation-protocol.ts
|
|
196620
|
-
var MUTATION_ID_PATTERN2 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
196621
|
-
var FINGERPRINT_PATTERN = /^[0-9a-f]{64}$/;
|
|
196622
|
-
var OPERATION_PATTERN = /^[a-z0-9][a-z0-9._:-]{0,127}$/;
|
|
196623
|
-
var RESOURCE_KEY_PATTERN = /^v1\/(?:[a-z0-9][a-z0-9._-]{0,63})\/([A-Za-z0-9_-]{2,171})$/;
|
|
196624
|
-
var RESOURCE_ID_CONTROL_PATTERN = /[\u0000-\u001f\u007f-\u009f]/u;
|
|
196625
|
-
var FAILURE_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
196626
|
-
var LEASE_OWNER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,254}$/;
|
|
196627
|
-
var TIMESTAMP_PATTERN2 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
196628
|
-
var MAX_STATUS_RESPONSE_BYTES = 196608;
|
|
196629
|
-
var MAX_RESOURCE_ID_BYTES = 128;
|
|
196630
|
-
var FATAL_UTF8_DECODER = new TextDecoder("utf-8", { fatal: true });
|
|
196631
|
-
var MUTATION_STATUSES = new Set([
|
|
196632
|
-
"pending",
|
|
196633
|
-
"running",
|
|
196634
|
-
"succeeded",
|
|
196635
|
-
"failed_retryable",
|
|
196636
|
-
"failed_terminal",
|
|
196637
|
-
"outcome_unknown"
|
|
196638
|
-
]);
|
|
196639
|
-
var MUTATION_RESPONSE_KEYS = ["project_ref", "mutation"];
|
|
196640
|
-
var MUTATION_KEYS = [
|
|
196641
|
-
"project_ref",
|
|
196642
|
-
"mutation_id",
|
|
196643
|
-
"operation",
|
|
196644
|
-
"resource_key",
|
|
196645
|
-
"request_fingerprint",
|
|
196646
|
-
"principal",
|
|
196647
|
-
"status",
|
|
196648
|
-
"checkpoint",
|
|
196649
|
-
"receipt",
|
|
196650
|
-
"response_status",
|
|
196651
|
-
"failure_code",
|
|
196652
|
-
"lease",
|
|
196653
|
-
"completed_at",
|
|
196654
|
-
"created_at",
|
|
196655
|
-
"updated_at"
|
|
196656
|
-
];
|
|
196657
|
-
var PRINCIPAL_KEYS = ["type", "id"];
|
|
196658
|
-
var LEASE_KEYS = ["owner", "expires_at", "fencing_epoch"];
|
|
196659
|
-
function isMutationId(candidate) {
|
|
196660
|
-
return typeof candidate === "string" && MUTATION_ID_PATTERN2.test(candidate);
|
|
196661
|
-
}
|
|
196662
|
-
function objectRecord4(candidate) {
|
|
196663
|
-
return candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
|
|
196664
|
-
}
|
|
196665
|
-
function exactRecord(candidate, keys) {
|
|
196666
|
-
const record = objectRecord4(candidate);
|
|
196667
|
-
if (!record || Object.keys(record).length !== keys.length)
|
|
196668
|
-
return null;
|
|
196669
|
-
return keys.every((key) => Object.hasOwn(record, key)) ? record : null;
|
|
196670
|
-
}
|
|
196671
|
-
function emptyProjection(candidate) {
|
|
196672
|
-
const record = objectRecord4(candidate);
|
|
196673
|
-
return record && Object.keys(record).length === 0 ? record : null;
|
|
196674
|
-
}
|
|
196675
|
-
function canonicalTimestamp3(candidate) {
|
|
196676
|
-
if (typeof candidate !== "string" || !TIMESTAMP_PATTERN2.test(candidate))
|
|
196677
|
-
return false;
|
|
196678
|
-
const milliseconds = Date.parse(candidate);
|
|
196679
|
-
return Number.isFinite(milliseconds) && new Date(milliseconds).toISOString() === candidate;
|
|
196680
|
-
}
|
|
196681
|
-
function nullableTimestamp(candidate) {
|
|
196682
|
-
return candidate === null || canonicalTimestamp3(candidate);
|
|
196683
|
-
}
|
|
196684
|
-
function safePrincipal(candidate) {
|
|
196685
|
-
const principal = exactRecord(candidate, PRINCIPAL_KEYS);
|
|
196686
|
-
if (!principal || !["master", "admin", "project"].includes(String(principal.type)))
|
|
196687
|
-
return null;
|
|
196688
|
-
if (typeof principal.id !== "string" || !principal.id || principal.id.length > 320 || principal.id.trim() !== principal.id || /[\u0000-\u001f\u007f]/u.test(principal.id))
|
|
196689
|
-
return null;
|
|
196690
|
-
return { type: principal.type, id: principal.id };
|
|
196691
|
-
}
|
|
196692
|
-
function safeLease(candidate) {
|
|
196693
|
-
const lease = exactRecord(candidate, LEASE_KEYS);
|
|
196694
|
-
if (!lease || lease.owner !== null && (typeof lease.owner !== "string" || !LEASE_OWNER_PATTERN.test(lease.owner)))
|
|
196695
|
-
return null;
|
|
196696
|
-
if (!nullableTimestamp(lease.expires_at) || !Number.isSafeInteger(lease.fencing_epoch) || Number(lease.fencing_epoch) < 0)
|
|
196697
|
-
return null;
|
|
196698
|
-
return {
|
|
196699
|
-
owner: lease.owner,
|
|
196700
|
-
expires_at: lease.expires_at,
|
|
196701
|
-
fencing_epoch: Number(lease.fencing_epoch)
|
|
196702
|
-
};
|
|
196703
|
-
}
|
|
196704
|
-
function safeResponseStatus(candidate) {
|
|
196705
|
-
if (candidate === null)
|
|
196706
|
-
return null;
|
|
196707
|
-
return Number.isInteger(candidate) && Number(candidate) >= 100 && Number(candidate) <= 599 ? Number(candidate) : undefined;
|
|
196708
|
-
}
|
|
196709
|
-
function validLeaseState(status, lease) {
|
|
196710
|
-
const running = status === "running";
|
|
196711
|
-
return running ? lease.owner !== null && lease.expires_at !== null && lease.fencing_epoch > 0 : lease.owner === null && lease.expires_at === null;
|
|
196712
|
-
}
|
|
196713
|
-
function validMutationLifecycle(mutation, receipt, responseStatus) {
|
|
196714
|
-
const terminal = ["succeeded", "failed_terminal", "outcome_unknown"].includes(String(mutation.status));
|
|
196715
|
-
if (mutation.completed_at !== null !== terminal)
|
|
196716
|
-
return false;
|
|
196717
|
-
if (mutation.status === "succeeded") {
|
|
196718
|
-
return receipt !== null && responseStatus !== null && responseStatus >= 200 && responseStatus < 300 && mutation.failure_code === null;
|
|
196719
|
-
}
|
|
196720
|
-
if (mutation.status === "failed_terminal") {
|
|
196721
|
-
return receipt !== null && typeof mutation.failure_code === "string";
|
|
196722
|
-
}
|
|
196723
|
-
if (mutation.status === "failed_retryable" || mutation.status === "outcome_unknown") {
|
|
196724
|
-
return receipt !== null && typeof mutation.failure_code === "string";
|
|
196725
|
-
}
|
|
196726
|
-
if (mutation.status === "pending") {
|
|
196727
|
-
return receipt === null && responseStatus === null && mutation.failure_code === null;
|
|
196728
|
-
}
|
|
196729
|
-
return true;
|
|
196730
|
-
}
|
|
196731
|
-
function canonicalMutationResourceKey(candidate) {
|
|
196732
|
-
if (typeof candidate !== "string")
|
|
196733
|
-
return false;
|
|
196734
|
-
const match = RESOURCE_KEY_PATTERN.exec(candidate);
|
|
196735
|
-
if (!match)
|
|
196736
|
-
return false;
|
|
196737
|
-
const encodedResourceId = match[1];
|
|
196738
|
-
const resourceIdBytes = Buffer.from(encodedResourceId, "base64url");
|
|
196739
|
-
if (resourceIdBytes.byteLength < 1 || resourceIdBytes.byteLength > MAX_RESOURCE_ID_BYTES || resourceIdBytes.toString("base64url") !== encodedResourceId)
|
|
196740
|
-
return false;
|
|
196741
|
-
let resourceId;
|
|
196742
|
-
try {
|
|
196743
|
-
resourceId = FATAL_UTF8_DECODER.decode(resourceIdBytes);
|
|
196744
|
-
} catch (decodeError) {
|
|
196745
|
-
if (decodeError instanceof TypeError)
|
|
196746
|
-
return false;
|
|
196747
|
-
throw decodeError;
|
|
196748
|
-
}
|
|
196749
|
-
return resourceId.trim() === resourceId && !RESOURCE_ID_CONTROL_PATTERN.test(resourceId) && Buffer.from(resourceId, "utf8").equals(resourceIdBytes);
|
|
196750
|
-
}
|
|
196751
|
-
function validMutationIdentity(mutation) {
|
|
196752
|
-
if (!isMutationId(mutation.mutation_id) || typeof mutation.project_ref !== "string")
|
|
196753
|
-
return false;
|
|
196754
|
-
if (typeof mutation.operation !== "string" || !OPERATION_PATTERN.test(mutation.operation))
|
|
196755
|
-
return false;
|
|
196756
|
-
if (mutation.resource_key !== null && !canonicalMutationResourceKey(mutation.resource_key))
|
|
196757
|
-
return false;
|
|
196758
|
-
return typeof mutation.request_fingerprint === "string" && FINGERPRINT_PATTERN.test(mutation.request_fingerprint);
|
|
196759
|
-
}
|
|
196760
|
-
function validMutationTerminalFields(mutation) {
|
|
196761
|
-
if (typeof mutation.status !== "string" || !MUTATION_STATUSES.has(mutation.status))
|
|
196762
|
-
return false;
|
|
196763
|
-
if (mutation.failure_code !== null && (typeof mutation.failure_code !== "string" || !FAILURE_CODE_PATTERN.test(mutation.failure_code)))
|
|
196764
|
-
return false;
|
|
196765
|
-
return nullableTimestamp(mutation.completed_at) && canonicalTimestamp3(mutation.created_at) && canonicalTimestamp3(mutation.updated_at);
|
|
196766
|
-
}
|
|
196767
|
-
function safeMutationStatus(candidate) {
|
|
196768
|
-
const mutation = exactRecord(candidate, MUTATION_KEYS);
|
|
196769
|
-
if (!mutation || !validMutationIdentity(mutation) || !validMutationTerminalFields(mutation))
|
|
196770
|
-
return null;
|
|
196771
|
-
const principal = safePrincipal(mutation.principal);
|
|
196772
|
-
const checkpoint = emptyProjection(mutation.checkpoint);
|
|
196773
|
-
const receipt = mutation.receipt === null ? null : emptyProjection(mutation.receipt);
|
|
196774
|
-
const responseStatus = safeResponseStatus(mutation.response_status);
|
|
196775
|
-
const lease = safeLease(mutation.lease);
|
|
196776
|
-
if (!principal || !checkpoint || mutation.receipt !== null && !receipt || responseStatus === undefined || !lease || !validLeaseState(String(mutation.status), lease) || !validMutationLifecycle(mutation, receipt, responseStatus))
|
|
196777
|
-
return null;
|
|
196778
|
-
return {
|
|
196779
|
-
project_ref: mutation.project_ref,
|
|
196780
|
-
mutation_id: mutation.mutation_id,
|
|
196781
|
-
operation: mutation.operation,
|
|
196782
|
-
resource_key: mutation.resource_key,
|
|
196783
|
-
request_fingerprint: mutation.request_fingerprint,
|
|
196784
|
-
principal,
|
|
196785
|
-
status: mutation.status,
|
|
196786
|
-
checkpoint,
|
|
196787
|
-
receipt,
|
|
196788
|
-
response_status: responseStatus,
|
|
196789
|
-
failure_code: mutation.failure_code,
|
|
196790
|
-
lease,
|
|
196791
|
-
completed_at: mutation.completed_at,
|
|
196792
|
-
created_at: mutation.created_at,
|
|
196793
|
-
updated_at: mutation.updated_at
|
|
196794
|
-
};
|
|
196795
|
-
}
|
|
196796
|
-
function mutationStatusPath(ref, mutationId) {
|
|
196797
|
-
if (!isMutationId(mutationId))
|
|
196798
|
-
throw new Error("'mutation_id' must be a UUIDv4");
|
|
196799
|
-
return `/v1/projects/${projectRefPathSegment(ref, "Mutations")}/mutations/${encodeURIComponent(mutationId)}`;
|
|
196800
|
-
}
|
|
196801
|
-
function mutationStatusResponse(ref, mutationId, payload) {
|
|
196802
|
-
const response = exactRecord(payload, MUTATION_RESPONSE_KEYS);
|
|
196803
|
-
const mutation = safeMutationStatus(response?.mutation);
|
|
196804
|
-
return response?.project_ref === ref && mutation?.project_ref === ref && mutation.mutation_id === mutationId ? mutation : null;
|
|
196805
|
-
}
|
|
196806
|
-
async function fetchMutationStatus(http, ref, mutationId) {
|
|
196807
|
-
const response = await http.get(mutationStatusPath(ref, mutationId), {
|
|
196808
|
-
maxResponseBytes: MAX_STATUS_RESPONSE_BYTES
|
|
196809
|
-
});
|
|
196810
|
-
if (!response.ok) {
|
|
196811
|
-
return { kind: "unavailable", httpStatus: response.transportError ? null : response.status };
|
|
196812
|
-
}
|
|
196813
|
-
const mutation = mutationStatusResponse(ref, mutationId, response.data);
|
|
196814
|
-
return mutation ? { kind: "available", mutation } : { kind: "invalid" };
|
|
196815
|
-
}
|
|
196816
|
-
|
|
196817
197652
|
// src/shared/tools/mutation-tools.ts
|
|
196818
197653
|
function requiredText3(args, name) {
|
|
196819
197654
|
const candidate = args[name];
|
|
@@ -196852,7 +197687,7 @@ var MUTATION_TOOL_SCHEMA = {
|
|
|
196852
197687
|
};
|
|
196853
197688
|
|
|
196854
197689
|
// src/shared/tools/release-tools.ts
|
|
196855
|
-
import { createHash as
|
|
197690
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
196856
197691
|
import { existsSync as existsSync14, mkdirSync as mkdirSync4, readdirSync as readdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "node:fs";
|
|
196857
197692
|
import { dirname as dirname11, join as join18, relative as relative10, resolve as resolve23 } from "node:path";
|
|
196858
197693
|
var SAFE_PROJECT_REF = /^[A-Za-z0-9_-]{1,64}$/;
|
|
@@ -196883,7 +197718,8 @@ function validProjectRef(ref) {
|
|
|
196883
197718
|
return SAFE_PROJECT_REF.test(ref);
|
|
196884
197719
|
}
|
|
196885
197720
|
function backupBelongsToProject(backupId, projectRef) {
|
|
196886
|
-
|
|
197721
|
+
const prefix = `logical-full_${projectRef}_`;
|
|
197722
|
+
return BACKUP_ID.test(backupId) && backupId.startsWith(prefix) && /^[a-f0-9]{32}$/.test(backupId.slice(prefix.length));
|
|
196887
197723
|
}
|
|
196888
197724
|
function verifiedBackup(value, projectRef) {
|
|
196889
197725
|
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" || !SHA256.test(value.sha256))
|
|
@@ -196922,16 +197758,11 @@ function publicBackup(backup) {
|
|
|
196922
197758
|
function equalBackup(left, right) {
|
|
196923
197759
|
return left.backup_id === right.backup_id && left.project_ref === right.project_ref && left.database === right.database && left.kind === right.kind && left.created_at === right.created_at && left.completed_at === right.completed_at && left.bytes === right.bytes && left.sha256 === right.sha256;
|
|
196924
197760
|
}
|
|
196925
|
-
function
|
|
196926
|
-
|
|
196927
|
-
|
|
196928
|
-
const current = afterById.get(previous.backup_id);
|
|
196929
|
-
if (!current || !equalBackup(previous, current))
|
|
196930
|
-
return null;
|
|
197761
|
+
function requiredBackupId(value, projectRef) {
|
|
197762
|
+
if (typeof value !== "string" || !backupBelongsToProject(value, projectRef)) {
|
|
197763
|
+
throw new Error("'backup_id' must identify a logical-full backup for 'ref'");
|
|
196931
197764
|
}
|
|
196932
|
-
|
|
196933
|
-
const additions = after.filter((backup) => !known.has(backup.backup_id));
|
|
196934
|
-
return additions.length === 1 ? additions[0] : null;
|
|
197765
|
+
return value;
|
|
196935
197766
|
}
|
|
196936
197767
|
function restoreRequest(projectRef, backupId, expectedSha256, restoreConfirmation) {
|
|
196937
197768
|
if (typeof backupId !== "string" || !backupBelongsToProject(backupId, projectRef)) {
|
|
@@ -196957,11 +197788,27 @@ function httpFailure(operation, response) {
|
|
|
196957
197788
|
}
|
|
196958
197789
|
return releaseControlFailure(operation, "HTTP_ERROR", response.transportError ? null : response.status);
|
|
196959
197790
|
}
|
|
196960
|
-
function
|
|
196961
|
-
|
|
196962
|
-
|
|
197791
|
+
function mutationOutcomeUnknown(response) {
|
|
197792
|
+
return Boolean(response.responseReadError || response.transportError || response.status === 408 || response.status >= 500 || response.status >= 200 && response.status < 300 && response.status !== 200);
|
|
197793
|
+
}
|
|
197794
|
+
function mutationFailure(operation, response, safeState = {}) {
|
|
197795
|
+
if (mutationOutcomeUnknown(response)) {
|
|
197796
|
+
return releaseControlFailure(operation, "OUTCOME_UNKNOWN", response.transportError ? null : response.status, safeState);
|
|
196963
197797
|
}
|
|
196964
|
-
return releaseControlFailure(operation, "HTTP_ERROR", response.status);
|
|
197798
|
+
return releaseControlFailure(operation, "HTTP_ERROR", response.status, safeState);
|
|
197799
|
+
}
|
|
197800
|
+
async function readBackup(http, projectRef, backupId) {
|
|
197801
|
+
let response;
|
|
197802
|
+
try {
|
|
197803
|
+
response = await http.get(`${endpoint(projectRef)}/database/backups/logical/${backupId}`, {
|
|
197804
|
+
maxJsonBytes: MUTATION_MAX_BYTES,
|
|
197805
|
+
responseTimeoutMs: RELEASE_READ_RESPONSE_TIMEOUT_MS
|
|
197806
|
+
});
|
|
197807
|
+
} catch {
|
|
197808
|
+
response = { ok: false, status: 0, data: null, transportError: true };
|
|
197809
|
+
}
|
|
197810
|
+
const candidate = response.ok && response.status === 200 && isRecord6(response.data) ? verifiedBackup(response.data.backup, projectRef) : null;
|
|
197811
|
+
return { response, backup: candidate?.backup_id === backupId ? candidate : null };
|
|
196965
197812
|
}
|
|
196966
197813
|
async function readInventory(http, projectRef) {
|
|
196967
197814
|
const response = await http.get(`${endpoint(projectRef)}/database/backups/logical`, {
|
|
@@ -197082,7 +197929,7 @@ function genericScopedReleaseScopeJson(scope) {
|
|
|
197082
197929
|
return JSON.stringify(canonicalValue(scope));
|
|
197083
197930
|
}
|
|
197084
197931
|
function genericScopedReleaseScopeSha256(scope) {
|
|
197085
|
-
return
|
|
197932
|
+
return createHash8("sha256").update(genericScopedReleaseScopeJson(scope)).digest("hex");
|
|
197086
197933
|
}
|
|
197087
197934
|
function runGitRevParse(cwd, args) {
|
|
197088
197935
|
const res = Bun.spawnSync(["git", ...args], { cwd });
|
|
@@ -197374,6 +198221,7 @@ function registerReleaseTools(server2, http, options = {}) {
|
|
|
197374
198221
|
const localActions = ["scope_inspect", "scope_rebind", "scope_create"];
|
|
197375
198222
|
const remoteActions = [
|
|
197376
198223
|
"logical_backup_list",
|
|
198224
|
+
"logical_backup_status",
|
|
197377
198225
|
"logical_backup_create",
|
|
197378
198226
|
"logical_backup_restore",
|
|
197379
198227
|
"postgrest_status",
|
|
@@ -197396,7 +198244,7 @@ function registerReleaseTools(server2, http, options = {}) {
|
|
|
197396
198244
|
web: optional(Boolean2(), "[scope_create] Whether web assets are included in release scope"),
|
|
197397
198245
|
dry_run: optional(Boolean2(), "[scope_rebind] Perform calculation and validation without writing files"),
|
|
197398
198246
|
cwd: optional(String2(), "[scope_inspect/scope_rebind/scope_create] Base directory for relative file and git resolution"),
|
|
197399
|
-
backup_id: optional(String2(), "[
|
|
198247
|
+
backup_id: optional(String2(), "[logical_backup_create/status/restore] Stable project-bound backup ID; create generates one when omitted"),
|
|
197400
198248
|
expected_sha256: optional(String2(), "[logical_backup_restore] Exact lowercase SHA-256 from the selected project inventory"),
|
|
197401
198249
|
restore_confirmation: optional(String2(), "[logical_backup_restore] Exact RESTORE_PROJECT:<ref>:<backup_id>:<sha256> confirmation"),
|
|
197402
198250
|
subject: optional(String2(), "[release_canary_fixture_stage_replay/disable_replay] Exact central subject UUID"),
|
|
@@ -197441,28 +198289,47 @@ function registerReleaseTools(server2, http, options = {}) {
|
|
|
197441
198289
|
});
|
|
197442
198290
|
}
|
|
197443
198291
|
if (action === "logical_backup_create") {
|
|
197444
|
-
const
|
|
197445
|
-
const
|
|
197446
|
-
|
|
197447
|
-
|
|
197448
|
-
|
|
197449
|
-
|
|
197450
|
-
|
|
197451
|
-
|
|
198292
|
+
const backupId = requiredBackupId(backup_id ?? `logical-full_${projectRef}_${crypto.randomUUID().replaceAll("-", "")}`, projectRef);
|
|
198293
|
+
const state = { project_ref: projectRef, backup_id: backupId };
|
|
198294
|
+
let mutation;
|
|
198295
|
+
try {
|
|
198296
|
+
mutation = await http.postReleaseMutation(`${endpoint(projectRef)}/database/backups/logical`, { backup_id: backupId }, { timeoutMs: BACKUP_TIMEOUT_MS });
|
|
198297
|
+
} catch {
|
|
198298
|
+
mutation = { ok: false, status: 0, data: null, transportError: true };
|
|
198299
|
+
}
|
|
197452
198300
|
if (!mutation.ok || mutation.status !== 200) {
|
|
197453
|
-
|
|
198301
|
+
if (mutationOutcomeUnknown(mutation)) {
|
|
198302
|
+
const observed = await readBackup(http, projectRef, backupId);
|
|
198303
|
+
if (observed.backup)
|
|
198304
|
+
return releaseControlFailure("release.logical_backup.create", "OUTCOME_UNKNOWN", mutation.transportError ? null : mutation.status, {
|
|
198305
|
+
...state,
|
|
198306
|
+
backup: publicBackup(observed.backup),
|
|
198307
|
+
backup_verified: true,
|
|
198308
|
+
creation_confirmed: false
|
|
198309
|
+
});
|
|
198310
|
+
}
|
|
198311
|
+
return mutationFailure("release.logical_backup.create", mutation, state);
|
|
197454
198312
|
}
|
|
197455
198313
|
const responseBackup = isRecord6(mutation.data) ? verifiedBackup(mutation.data.backup, projectRef) : null;
|
|
197456
|
-
const
|
|
197457
|
-
|
|
197458
|
-
|
|
197459
|
-
return releaseControlFailure("release.logical_backup.create", "OUTCOME_UNKNOWN", mutation.status);
|
|
198314
|
+
const after = await readBackup(http, projectRef, backupId);
|
|
198315
|
+
if (!responseBackup || responseBackup.backup_id !== backupId || !after.backup || !equalBackup(responseBackup, after.backup)) {
|
|
198316
|
+
return releaseControlFailure("release.logical_backup.create", "OUTCOME_UNKNOWN", mutation.status, state);
|
|
197460
198317
|
}
|
|
197461
198318
|
return releaseControlSuccess("release.logical_backup.create", {
|
|
197462
|
-
|
|
197463
|
-
backup: publicBackup(
|
|
198319
|
+
...state,
|
|
198320
|
+
backup: publicBackup(after.backup)
|
|
197464
198321
|
});
|
|
197465
198322
|
}
|
|
198323
|
+
if (action === "logical_backup_status") {
|
|
198324
|
+
const backupId = requiredBackupId(backup_id, projectRef);
|
|
198325
|
+
const read = await readBackup(http, projectRef, backupId);
|
|
198326
|
+
const state = { project_ref: projectRef, backup_id: backupId };
|
|
198327
|
+
if (!read.response.ok || read.response.status !== 200) {
|
|
198328
|
+
const code = read.response.responseReadError || read.response.status >= 200 && read.response.status < 300 ? "INVALID_RESPONSE" : "HTTP_ERROR";
|
|
198329
|
+
return releaseControlFailure("release.logical_backup.status", code, read.response.transportError ? null : read.response.status, state);
|
|
198330
|
+
}
|
|
198331
|
+
return read.backup ? releaseControlSuccess("release.logical_backup.status", { ...state, backup: publicBackup(read.backup) }) : releaseControlFailure("release.logical_backup.status", "INVALID_RESPONSE", read.response.status, state);
|
|
198332
|
+
}
|
|
197466
198333
|
if (action === "logical_backup_restore") {
|
|
197467
198334
|
const request = restoreRequest(projectRef, backup_id, expected_sha256, restore_confirmation);
|
|
197468
198335
|
const before = await readInventory(http, projectRef);
|
|
@@ -197568,7 +198435,7 @@ function registerReleaseTools(server2, http, options = {}) {
|
|
|
197568
198435
|
|
|
197569
198436
|
// src/shared/tools/deploy-tools.ts
|
|
197570
198437
|
import { access, lstat as lstat5, mkdtemp as mkdtemp2, opendir, readFile as readFile7, rm as rm3, writeFile as writeFile7 } from "node:fs/promises";
|
|
197571
|
-
import { createHash as
|
|
198438
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
197572
198439
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
197573
198440
|
import { basename as basename10, dirname as dirname12, join as join19, relative as relative11, resolve as resolve24, sep as sep5 } from "node:path";
|
|
197574
198441
|
|
|
@@ -198503,7 +199370,7 @@ async function createFrontendArchive(outputDirectory) {
|
|
|
198503
199370
|
try {
|
|
198504
199371
|
const archiveBytes = zipSync(collected.files, { level: 6, mtime: FIXED_ZIP_MTIME });
|
|
198505
199372
|
await writeFile7(archivePath, archiveBytes);
|
|
198506
|
-
const sha256 =
|
|
199373
|
+
const sha256 = createHash9("sha256").update(archiveBytes).digest("hex");
|
|
198507
199374
|
return {
|
|
198508
199375
|
archivePath,
|
|
198509
199376
|
sha256,
|
|
@@ -198726,7 +199593,7 @@ function registerDeployTools(server2, http, options = {}) {
|
|
|
198726
199593
|
import { existsSync as existsSync15 } from "node:fs";
|
|
198727
199594
|
import { readFile as readFile8 } from "node:fs/promises";
|
|
198728
199595
|
import { readdir as readdir8 } from "node:fs/promises";
|
|
198729
|
-
import { createHash as
|
|
199596
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
198730
199597
|
import { join as join20, resolve as resolve25 } from "node:path";
|
|
198731
199598
|
import { compileProject as compileProject2 } from "@supacloud/compiler";
|
|
198732
199599
|
var SAFE_TOKEN = /^[A-Za-z0-9._:@/+,-]+$/;
|
|
@@ -198825,7 +199692,7 @@ async function sourceFingerprint(root) {
|
|
|
198825
199692
|
}
|
|
198826
199693
|
};
|
|
198827
199694
|
await visit(root);
|
|
198828
|
-
const hash =
|
|
199695
|
+
const hash = createHash10("sha256");
|
|
198829
199696
|
for (const file of files) {
|
|
198830
199697
|
hash.update(file.slice(root.length).replace(/\\/g, "/"));
|
|
198831
199698
|
hash.update(await readFile8(file));
|
|
@@ -198994,7 +199861,7 @@ function registerRemoteDevTools(server2, options = {}) {
|
|
|
198994
199861
|
// package.json
|
|
198995
199862
|
var package_default = {
|
|
198996
199863
|
name: "@supacloud/cli",
|
|
198997
|
-
version: "0.
|
|
199864
|
+
version: "0.82.0",
|
|
198998
199865
|
description: "Project-scoped CLI for SupaCloud users",
|
|
198999
199866
|
type: "module",
|
|
199000
199867
|
main: "./dist/index.js",
|
|
@@ -199046,14 +199913,14 @@ var package_default = {
|
|
|
199046
199913
|
},
|
|
199047
199914
|
dependencies: {
|
|
199048
199915
|
"@supacloud/compiler": "^0.43.0",
|
|
199049
|
-
"@supacloud/delivery": "0.
|
|
199916
|
+
"@supacloud/delivery": "0.11.0",
|
|
199050
199917
|
"@supacloud/db": "0.16.0",
|
|
199051
199918
|
fflate: "^0.8.3",
|
|
199052
199919
|
typebox: "1.3.34"
|
|
199053
199920
|
},
|
|
199054
199921
|
overrides: {
|
|
199055
199922
|
"@supacloud/contracts": "0.8.0",
|
|
199056
|
-
"@supacloud/delivery": "0.
|
|
199923
|
+
"@supacloud/delivery": "0.11.0",
|
|
199057
199924
|
"@graphql-tools/utils": "^12.0.3"
|
|
199058
199925
|
},
|
|
199059
199926
|
devDependencies: {
|
|
@@ -199274,6 +200141,7 @@ EXAMPLES
|
|
|
199274
200141
|
${preferredCommand} project logs --log_type database
|
|
199275
200142
|
${preferredCommand} project task_stats
|
|
199276
200143
|
${preferredCommand} release logical_backup_create --ref abc123
|
|
200144
|
+
${preferredCommand} release logical_backup_status --ref abc123 --backup_id <backup_id>
|
|
199277
200145
|
${preferredCommand} release logical_backup_restore --ref abc123 --backup_id <backup_id> --expected_sha256 <sha256> --restore_confirmation RESTORE_PROJECT:abc123:<backup_id>:<sha256>
|
|
199278
200146
|
${preferredCommand} release postgrest_status --ref abc123
|
|
199279
200147
|
${preferredCommand} release postgrest_restart --ref abc123
|
|
@@ -199318,11 +200186,19 @@ EXAMPLES
|
|
|
199318
200186
|
${preferredCommand} app diff --ref abc123 --id orders --environment_id test --release_id <sha256> --configuration_id <uuid>
|
|
199319
200187
|
${preferredCommand} app deploy --ref abc123 --id orders --environment_id test --release_id <sha256> --configuration_id <uuid>
|
|
199320
200188
|
${preferredCommand} app status --ref abc123 --id orders --environment_id test
|
|
200189
|
+
${preferredCommand} app diff --ref abc123 --id orders --environment_id production --source_ref staging --source_environment_id staging --source_release_id <sha256>
|
|
200190
|
+
${preferredCommand} --env production app promote-plan --id orders --environment_id production --source_ref staging --source_environment_id staging --source_release_id <sha256>
|
|
199321
200191
|
${preferredCommand} app preview-plan --ref abc123 --id orders --environment_id test --release_id <sha256> --branch_ref preview-orders
|
|
199322
200192
|
${preferredCommand} app preview --ref abc123 --id orders --environment_id test --release_id <sha256> --configuration_id <uuid> --ttl_seconds 3600 --wait
|
|
199323
200193
|
${preferredCommand} app previews --ref abc123 --id orders --environment_id test
|
|
199324
200194
|
${preferredCommand} app preview-status --ref abc123 --id orders --environment_id test --preview_id <uuid> --wait --timeout_seconds 300
|
|
199325
200195
|
${preferredCommand} app preview-cleanup --ref abc123 --id orders --environment_id test --preview_id <uuid>
|
|
200196
|
+
${preferredCommand} --env production app promote --id orders --environment_id production --source_ref staging --source_environment_id staging --source_release_id <sha256> --confirm-production abc123
|
|
200197
|
+
${preferredCommand} app promote-status --ref abc123 --id orders --environment_id production --mutation_id <uuid>
|
|
200198
|
+
${preferredCommand} app promote-reconcile --ref abc123 --id orders --environment_id production --mutation_id <uuid>
|
|
200199
|
+
${preferredCommand} app preview-create --ref abc123 --id orders --environment_id test --release_id <sha256> --data_mode schema_only
|
|
200200
|
+
${preferredCommand} app preview-list --ref abc123 --id orders --environment_id test
|
|
200201
|
+
${preferredCommand} app preview-cleanup --ref abc123 --id orders --environment_id test --preview_id <preview-id>
|
|
199326
200202
|
${preferredCommand} app logs --ref abc123 --id orders --environment_id test
|
|
199327
200203
|
${preferredCommand} app rollback --ref abc123 --id orders --environment_id test --release_id <old-sha256> --configuration_id <uuid> --activation_id <new-uuid> --expected_activation_id <current-uuid>
|
|
199328
200204
|
${preferredCommand} app check --root . --strict
|