@supacloud/cli 0.81.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 +819 -296
- package/dist/shared/mutation-protocol.d.ts +1 -0
- package/dist/shared/tools/app-tools.d.ts +13 -7
- package/dist/shared/tools/application-schemas.d.ts +62 -0
- package/dist/shared/tools/application-tools.d.ts +65 -1
- package/dist/shared/tools/application-workflow-schemas.d.ts +236 -0
- package/package.json +1 -1
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,21 +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_release_transfer_plan", "get_promotion_plan", "get_rollback_snapshot", "get_history", "get_deploy_plan", "
|
|
179097
|
-
write: [
|
|
179098
|
-
"upload_release",
|
|
179099
|
-
"put_configuration",
|
|
179100
|
-
"transfer_release",
|
|
179101
|
-
"activate_release",
|
|
179102
|
-
"deploy_release",
|
|
179103
|
-
"rollback_release",
|
|
179104
|
-
"reconcile_activation",
|
|
179105
|
-
"retire_activation",
|
|
179106
|
-
"create_preview",
|
|
179107
|
-
"list_previews",
|
|
179108
|
-
"get_preview",
|
|
179109
|
-
"cleanup_preview"
|
|
179110
|
-
]
|
|
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"]
|
|
179111
179108
|
},
|
|
179112
179109
|
project: {
|
|
179113
179110
|
read: ["get", "endpoints", "health", "logs", "api_keys", "settings", "tasks", "task_detail", "task_stats", "dlq", "background_settings"],
|
|
@@ -179202,8 +179199,8 @@ var ACTION_POLICY = {
|
|
|
179202
179199
|
ai: { local: ["show_skill", "install_skill"] },
|
|
179203
179200
|
app: {
|
|
179204
179201
|
local: ["init", "generate", "dev", "watch", "verify-plan", "compile", "check", "graph", "explain", "export-tools", "context", "doctor", "fix", "plan", "build"],
|
|
179205
|
-
read: ["status", "logs", "transfer-plan", "promote-plan", "rollback-plan", "history", "deploy-plan", "diff", "preview-plan"],
|
|
179206
|
-
write: ["upload", "configure", "transfer", "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"]
|
|
179207
179204
|
},
|
|
179208
179205
|
db: {
|
|
179209
179206
|
local: ["context", "lint", "explain", "diff", "role_sql"],
|
|
@@ -187550,6 +187547,374 @@ var ApplicationActivationResultSchema2 = ApplicationActivationResultSchema;
|
|
|
187550
187547
|
var ApplicationActivationRetirementResultSchema2 = ApplicationActivationRetirementResultSchema;
|
|
187551
187548
|
var ApplicationRollbackSnapshotSchema2 = ApplicationRollbackSnapshotSchema;
|
|
187552
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
|
+
}
|
|
187553
187918
|
|
|
187554
187919
|
// src/shared/tools/application-release-transfer.ts
|
|
187555
187920
|
async function applicationReleaseTransfer(http, input, project) {
|
|
@@ -187635,9 +188000,9 @@ var APPLICATION_PREVIEW_FIELDS = {
|
|
|
187635
188000
|
timeout_seconds: optional(waitSecondsSchema, "[create_preview/get_preview] Observation budget with wait, from 1 to 3600 seconds (default 300)")
|
|
187636
188001
|
};
|
|
187637
188002
|
var strict = { additionalProperties: false };
|
|
187638
|
-
var
|
|
188003
|
+
var phase2 = stringEnum(["pending", "ready", "failed", "cleaned"]);
|
|
187639
188004
|
var check5 = String2({ pattern: "^[a-z][a-z0-9_]{0,63}$" });
|
|
187640
|
-
var
|
|
188005
|
+
var checks2 = _Array_(check5, { maxItems: 64, uniqueItems: true });
|
|
187641
188006
|
var revision = Union([ApplicationConfigurationIdSchema2, Null()]);
|
|
187642
188007
|
var receiptSchema = _Object_({
|
|
187643
188008
|
schema: Literal("supacloud.application-preview.v1"),
|
|
@@ -187649,14 +188014,14 @@ var receiptSchema = _Object_({
|
|
|
187649
188014
|
expires_at: Union([timestampSchema, Null()]),
|
|
187650
188015
|
status: stringEnum(["planned", "provisioning", "ready", "failed", "cleaned"]),
|
|
187651
188016
|
resources: _Object_({
|
|
187652
|
-
build_artifact: _Object_({ status:
|
|
187653
|
-
database_branch: _Object_({ status:
|
|
187654
|
-
queue_namespace: _Object_({ status:
|
|
187655
|
-
storage_namespace: _Object_({ status:
|
|
187656
|
-
test_secret: _Object_({ status:
|
|
187657
|
-
configuration_revision: _Object_({ status:
|
|
187658
|
-
application_activation: _Object_({ status:
|
|
187659
|
-
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)
|
|
187660
188025
|
}, strict),
|
|
187661
188026
|
cleanup: _Object_({
|
|
187662
188027
|
required: Boolean2(),
|
|
@@ -187694,14 +188059,14 @@ var actionFields = {
|
|
|
187694
188059
|
get_preview: ["preview_id", "wait", "timeout_seconds"],
|
|
187695
188060
|
cleanup_preview: ["preview_id"]
|
|
187696
188061
|
};
|
|
187697
|
-
function
|
|
188062
|
+
function timestamp3(value) {
|
|
187698
188063
|
return Number.isFinite(Date.parse(value)) && new Date(value).toISOString() === value;
|
|
187699
188064
|
}
|
|
187700
188065
|
function receipt(value, identity) {
|
|
187701
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) {
|
|
187702
188067
|
throw new Error("Invalid preview receipt");
|
|
187703
188068
|
}
|
|
187704
|
-
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)) {
|
|
187705
188070
|
throw new Error("Invalid preview expiry");
|
|
187706
188071
|
}
|
|
187707
188072
|
if (value.status === "ready") {
|
|
@@ -187925,7 +188290,11 @@ async function applicationPreviewAction(http, args, project) {
|
|
|
187925
188290
|
throw new Error;
|
|
187926
188291
|
}
|
|
187927
188292
|
if (preview.status === "failed" || action === "cleanup_preview" && preview.status !== "cleaned") {
|
|
187928
|
-
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
|
+
});
|
|
187929
188298
|
}
|
|
187930
188299
|
if (timeoutSeconds !== undefined)
|
|
187931
188300
|
return await waitForPreview(http, path, operation, identity, preview, timeoutSeconds, startedAt);
|
|
@@ -187950,12 +188319,20 @@ var APPLICATION_TOOL_SCHEMA = {
|
|
|
187950
188319
|
"get_release_transfer_plan",
|
|
187951
188320
|
"transfer_release",
|
|
187952
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",
|
|
187953
188330
|
"activate_release",
|
|
188331
|
+
"reconcile_activation",
|
|
188332
|
+
"retire_activation",
|
|
187954
188333
|
"rollback_release",
|
|
187955
188334
|
"get_rollback_snapshot",
|
|
187956
188335
|
"get_history",
|
|
187957
|
-
"reconcile_activation",
|
|
187958
|
-
"retire_activation",
|
|
187959
188336
|
...APPLICATION_PREVIEW_ACTIONS,
|
|
187960
188337
|
"logs"
|
|
187961
188338
|
]), "Action"),
|
|
@@ -187971,6 +188348,9 @@ var APPLICATION_TOOL_SCHEMA = {
|
|
|
187971
188348
|
source_ref: optional(String2({ pattern: "^[A-Za-z0-9_-]{1,20}$" }), "[get_release_transfer_plan/transfer_release] Source project ref"),
|
|
187972
188349
|
source_release_id: optional(ApplicationReleaseIdSchema2, "[get_release_transfer_plan/transfer_release] Source immutable release ID"),
|
|
187973
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"),
|
|
187974
188354
|
cursor: optional(Union([ApplicationReleaseIdSchema2, ApplicationActivationHistoryCursorSchema2]), "[list_releases/get_history] Returned page cursor"),
|
|
187975
188355
|
limit: optional(Integer({ minimum: 1, maximum: 100 }), "[list_releases/get_history] Page size, default 50/20"),
|
|
187976
188356
|
offset: optional(Integer({ minimum: 0, maximum: 1e6 }), "[logs] Result offset"),
|
|
@@ -187991,6 +188371,12 @@ var inventorySchema = _Object_({
|
|
|
187991
188371
|
releases: _Array_(ApplicationReleaseRecordSchema2, { maxItems: 100 }),
|
|
187992
188372
|
next_cursor: Union([ApplicationReleaseIdSchema2, Null()])
|
|
187993
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
|
+
}
|
|
187994
188380
|
function text(args, field) {
|
|
187995
188381
|
const value = args[field];
|
|
187996
188382
|
if (typeof value !== "string" || !value.trim())
|
|
@@ -188383,6 +188769,166 @@ function registerApplicationTools(server2, http) {
|
|
|
188383
188769
|
return releaseControlFailure(operation, "INVALID_RESPONSE", result.status);
|
|
188384
188770
|
}
|
|
188385
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
|
+
}
|
|
188386
188932
|
if (action === "logs") {
|
|
188387
188933
|
const environmentId = text(args, "environment_id");
|
|
188388
188934
|
if (!Check2(ApplicationIdSchema2, environmentId))
|
|
@@ -188559,7 +189105,7 @@ function matchingText(candidate, maxLength, pattern) {
|
|
|
188559
189105
|
const candidateText = boundedText2(candidate, maxLength);
|
|
188560
189106
|
return candidateText && pattern.test(candidateText) ? candidateText : null;
|
|
188561
189107
|
}
|
|
188562
|
-
function
|
|
189108
|
+
function canonicalTimestamp3(candidate) {
|
|
188563
189109
|
const timestamp = boundedText2(candidate, 64);
|
|
188564
189110
|
if (!timestamp)
|
|
188565
189111
|
return null;
|
|
@@ -188574,7 +189120,7 @@ function projectedSummary(project) {
|
|
|
188574
189120
|
organization_slug: matchingText(project.organization_slug, 128, SAFE_IDENTIFIER_PATTERN),
|
|
188575
189121
|
name: boundedText2(project.name, 100),
|
|
188576
189122
|
region: matchingText(project.region, 64, REGION_PATTERN),
|
|
188577
|
-
created_at:
|
|
189123
|
+
created_at: canonicalTimestamp3(project.created_at),
|
|
188578
189124
|
status: matchingText(project.status, 64, STATUS_PATTERN)
|
|
188579
189125
|
};
|
|
188580
189126
|
return Object.values(summary).every((field) => field !== null) ? summary : null;
|
|
@@ -194568,6 +195114,13 @@ var REMOTE_APP_ACTIONS = {
|
|
|
194568
195114
|
upload: "upload_release",
|
|
194569
195115
|
"transfer-plan": "get_release_transfer_plan",
|
|
194570
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",
|
|
194571
195124
|
transfer: "transfer_release",
|
|
194572
195125
|
configure: "put_configuration",
|
|
194573
195126
|
deploy: "deploy_release",
|
|
@@ -194580,36 +195133,35 @@ var REMOTE_APP_ACTIONS = {
|
|
|
194580
195133
|
reconcile: "reconcile_activation",
|
|
194581
195134
|
retire: "retire_activation",
|
|
194582
195135
|
logs: "logs",
|
|
194583
|
-
"preview-plan": "get_preview_plan",
|
|
194584
195136
|
preview: "create_preview",
|
|
194585
|
-
previews: "list_previews"
|
|
194586
|
-
"preview-status": "get_preview",
|
|
194587
|
-
"preview-cleanup": "cleanup_preview"
|
|
195137
|
+
previews: "list_previews"
|
|
194588
195138
|
};
|
|
194589
195139
|
var REMOTE_APP_DESCRIPTIONS = {
|
|
194590
|
-
ref: "[upload/configure/transfer-plan/transfer/promote-plan/
|
|
194591
|
-
id: "[upload/configure/transfer-plan/transfer/promote-plan/
|
|
194592
|
-
environment_id: "[configure/promote-plan/
|
|
194593
|
-
configuration_id: "[promote-plan/deploy/deploy-plan/
|
|
194594
|
-
activation_id: "[deploy/rollback/reconcile/retire]
|
|
194595
|
-
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",
|
|
194596
195146
|
configuration_path: "[configure] Configuration write JSON including revision and expected revision",
|
|
194597
195147
|
manifest_path: "[upload] Local delivery.manifest.json",
|
|
194598
|
-
release_id: "[deploy/deploy-plan/diff/rollback/reconcile
|
|
194599
|
-
source_ref: "[transfer-plan/transfer/promote-plan] Source project ref",
|
|
194600
|
-
source_release_id: "[transfer-plan/transfer/promote-plan] Source immutable release ID",
|
|
194601
|
-
source_environment_id: "[promote-plan] Source environment ID",
|
|
194602
|
-
|
|
194603
|
-
|
|
194604
|
-
|
|
194605
|
-
|
|
194606
|
-
|
|
194607
|
-
|
|
194608
|
-
|
|
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",
|
|
194609
195161
|
cursor: "[history] Returned page cursor",
|
|
194610
195162
|
limit: "[history] Page size, default 20 (1-100)"
|
|
194611
195163
|
};
|
|
194612
|
-
var { action: _remoteAction, ...remoteFields } = APPLICATION_TOOL_SCHEMA;
|
|
195164
|
+
var { action: _remoteAction, plan: _remotePlan, ...remoteFields } = APPLICATION_TOOL_SCHEMA;
|
|
194613
195165
|
var REMOTE_APP_SCHEMA = Partial(_Object_(remoteFields)).properties;
|
|
194614
195166
|
for (const [name, schema] of Object.entries(REMOTE_APP_SCHEMA)) {
|
|
194615
195167
|
const description = REMOTE_APP_DESCRIPTIONS[name];
|
|
@@ -195599,6 +196151,167 @@ function formatRemoteDeployPlan(result) {
|
|
|
195599
196151
|
return result;
|
|
195600
196152
|
}
|
|
195601
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
|
+
}
|
|
195602
196315
|
async function runAppTool(request, options = {}) {
|
|
195603
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) {
|
|
195604
196317
|
throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "Do not combine hyphenated and underscored aliases of the same flag");
|
|
@@ -195615,16 +196328,22 @@ async function runAppTool(request, options = {}) {
|
|
|
195615
196328
|
if (request.database_url !== undefined && request.action !== "dev" && request.action !== "watch") {
|
|
195616
196329
|
throw new ScaffoldError("SCAFFOLD_OPTION_INVALID", "--database-url applies only to app dev --profile integration");
|
|
195617
196330
|
}
|
|
195618
|
-
if (request.json !== undefined &&
|
|
195619
|
-
throw new Error("--json applies only to app
|
|
195620
|
-
|
|
195621
|
-
if (request.json !== undefined && request.format === "text")
|
|
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")
|
|
195622
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);
|
|
196340
|
+
}
|
|
195623
196341
|
if (Object.hasOwn(REMOTE_APP_ACTIONS, request.action)) {
|
|
195624
196342
|
const delegate = options.getApplications?.();
|
|
195625
196343
|
if (!delegate)
|
|
195626
196344
|
return textResult("App remote actions require a Management API context.", true);
|
|
195627
|
-
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];
|
|
195628
196347
|
const { format, json, ...remoteRequest } = request;
|
|
195629
196348
|
const args = {
|
|
195630
196349
|
...remoteRequest,
|
|
@@ -195636,24 +196355,14 @@ async function runAppTool(request, options = {}) {
|
|
|
195636
196355
|
if (request.action === "history" && format !== "json" && json !== true) {
|
|
195637
196356
|
return historyText(result, request, args.ref);
|
|
195638
196357
|
}
|
|
195639
|
-
if ((request.action === "deploy-plan" || request.action === "diff") && format !== "json") {
|
|
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)) {
|
|
195640
196359
|
return formatRemoteDeployPlan(result);
|
|
195641
196360
|
}
|
|
195642
|
-
if (request.action === "promote-plan" &&
|
|
195643
|
-
|
|
195644
|
-
|
|
195645
|
-
return result;
|
|
195646
|
-
const plan = parseApplicationPromotionPlan(payload.plan);
|
|
195647
|
-
return textResult([
|
|
195648
|
-
`${plan.project_ref}/${plan.application_id}/${plan.environment_id}: ${plan.action}`,
|
|
195649
|
-
`Source: ${plan.source.project_ref}/${plan.source.environment_id}`,
|
|
195650
|
-
...plan.blockers.length ? [`Blocked: ${plan.blockers.join(", ")}`] : [],
|
|
195651
|
-
...plan.steps.length ? [`Next: ${plan.steps.join(" -> ")}`] : [],
|
|
195652
|
-
"Execution: not performed"
|
|
195653
|
-
].join(`
|
|
195654
|
-
`));
|
|
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);
|
|
195655
196364
|
}
|
|
195656
|
-
return (request.action === "transfer-plan" || request.action === "transfer") && format !== "json" && !json ? formatReleaseTransfer(result) : result;
|
|
196365
|
+
return (request.action === "transfer-plan" || request.action === "transfer") && format !== "json" && !json ? formatReleaseTransfer(result) : format !== "json" && !json ? formatWorkflowObservation(result, request.action) : result;
|
|
195657
196366
|
}
|
|
195658
196367
|
switch (request.action) {
|
|
195659
196368
|
case "verify-plan": {
|
|
@@ -195743,9 +196452,14 @@ function registerAppTools(server2, options = {}) {
|
|
|
195743
196452
|
"preview-plan",
|
|
195744
196453
|
"preview",
|
|
195745
196454
|
"previews",
|
|
196455
|
+
"preview-list",
|
|
196456
|
+
"preview-create",
|
|
195746
196457
|
"preview-status",
|
|
195747
|
-
"preview-cleanup"
|
|
195748
|
-
|
|
196458
|
+
"preview-cleanup",
|
|
196459
|
+
"promote",
|
|
196460
|
+
"promote-status",
|
|
196461
|
+
"promote-reconcile"
|
|
196462
|
+
]), "App action; promote obtains and verifies its plan before one mutation request"),
|
|
195749
196463
|
kind: optional(stringEnum(["module", "command", "query", "controller", "job", "contract", "resource"]), "[generate] Scaffold kind"),
|
|
195750
196464
|
template: optional(stringEnum(["minimal", "http", "command", "edge"]), "[init] Minimal application by default; explicit http/command/edge recipes"),
|
|
195751
196465
|
name: optional(String2(), "[init/generate] Project or object name"),
|
|
@@ -195803,7 +196517,7 @@ import { existsSync as existsSync13 } from "node:fs";
|
|
|
195803
196517
|
import { join as join17, resolve as resolve21 } from "node:path";
|
|
195804
196518
|
|
|
195805
196519
|
// src/shared/tools/db-workflow.ts
|
|
195806
|
-
import { createHash as
|
|
196520
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
195807
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";
|
|
195808
196522
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
195809
196523
|
import { dirname as dirname10, isAbsolute as isAbsolute7, join as join16, relative as relative9, resolve as resolve20 } from "node:path";
|
|
@@ -195869,7 +196583,7 @@ function parseDatabaseSourcesConfig(value) {
|
|
|
195869
196583
|
}
|
|
195870
196584
|
|
|
195871
196585
|
// src/shared/tools/db-workflow.ts
|
|
195872
|
-
var hash3 = (content) =>
|
|
196586
|
+
var hash3 = (content) => createHash7("sha256").update(content).digest("hex");
|
|
195873
196587
|
var result = (value, isError = false) => ({
|
|
195874
196588
|
isError,
|
|
195875
196589
|
content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
|
|
@@ -196558,7 +197272,7 @@ function parseHeaderEnvironment(input) {
|
|
|
196558
197272
|
}
|
|
196559
197273
|
}
|
|
196560
197274
|
var headerEnvironmentSchema = Optional(decodedSchema(Union([String2(), headerEnvironmentRecord]), headerEnvironmentRecord, parseHeaderEnvironment));
|
|
196561
|
-
function
|
|
197275
|
+
function objectRecord4(candidate) {
|
|
196562
197276
|
return candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
|
|
196563
197277
|
}
|
|
196564
197278
|
function boundedCronInteger(input, minimum, maximum) {
|
|
@@ -196603,7 +197317,7 @@ function readScheduleBodyFile(bodyPathInput) {
|
|
|
196603
197317
|
throw error;
|
|
196604
197318
|
throw new Error("Scheduled Function body file must contain exact JSON");
|
|
196605
197319
|
}
|
|
196606
|
-
const body =
|
|
197320
|
+
const body = objectRecord4(payload);
|
|
196607
197321
|
if (!body)
|
|
196608
197322
|
throw new Error("Scheduled Function body file must contain a JSON object");
|
|
196609
197323
|
return body;
|
|
@@ -196644,7 +197358,7 @@ function headerValueIsStable(name, value) {
|
|
|
196644
197358
|
function scheduleHeaders(mapping, environment) {
|
|
196645
197359
|
if (mapping === undefined)
|
|
196646
197360
|
return;
|
|
196647
|
-
const headerEnvironment =
|
|
197361
|
+
const headerEnvironment = objectRecord4(mapping);
|
|
196648
197362
|
if (!headerEnvironment)
|
|
196649
197363
|
throw new Error("'header_env' must be a JSON object");
|
|
196650
197364
|
const entries = Object.entries(headerEnvironment).map(([headerName, environmentName]) => resolvedHeaderEntry(headerName, environmentName, environment));
|
|
@@ -196685,8 +197399,8 @@ function safeSchedulePayload(schedule) {
|
|
|
196685
197399
|
return { body_empty: schedule.body_empty, header_names: headerNames };
|
|
196686
197400
|
}
|
|
196687
197401
|
function legacySchedulePayload(schedule) {
|
|
196688
|
-
const body =
|
|
196689
|
-
const headers =
|
|
197402
|
+
const body = objectRecord4(schedule.body);
|
|
197403
|
+
const headers = objectRecord4(schedule.headers);
|
|
196690
197404
|
if (!body || !headers)
|
|
196691
197405
|
return null;
|
|
196692
197406
|
const headerNames = safeHeaderNames(Object.keys(headers));
|
|
@@ -196703,7 +197417,7 @@ function safeHeaderNames(candidate) {
|
|
|
196703
197417
|
return valid && new Set(names).size === names.length ? names.sort() : null;
|
|
196704
197418
|
}
|
|
196705
197419
|
function safeSchedule(candidate) {
|
|
196706
|
-
const schedule =
|
|
197420
|
+
const schedule = objectRecord4(candidate);
|
|
196707
197421
|
if (!schedule || !validSafeSchedule(schedule))
|
|
196708
197422
|
return null;
|
|
196709
197423
|
const safePayload = safeSchedulePayload(schedule);
|
|
@@ -196749,7 +197463,7 @@ function listResponse(ref, response) {
|
|
|
196749
197463
|
const operation = "scheduled_functions.list";
|
|
196750
197464
|
if (!response.ok)
|
|
196751
197465
|
return scheduleFailure(operation, response);
|
|
196752
|
-
const payload =
|
|
197466
|
+
const payload = objectRecord4(response.data);
|
|
196753
197467
|
const rawSchedules = payload?.schedules;
|
|
196754
197468
|
const schedules = Array.isArray(rawSchedules) ? rawSchedules.map(safeSchedule) : null;
|
|
196755
197469
|
if (payload?.project_ref !== ref || !schedules || schedules.some((schedule) => !schedule)) {
|
|
@@ -196764,7 +197478,7 @@ function getResponse(ref, scheduleId, response) {
|
|
|
196764
197478
|
const operation = "scheduled_functions.get";
|
|
196765
197479
|
if (!response.ok)
|
|
196766
197480
|
return scheduleFailure(operation, response);
|
|
196767
|
-
const payload =
|
|
197481
|
+
const payload = objectRecord4(response.data);
|
|
196768
197482
|
const schedule = safeSchedule(payload?.schedule);
|
|
196769
197483
|
if (payload?.project_ref !== ref || !schedule || schedule.id !== scheduleId) {
|
|
196770
197484
|
return releaseControlFailure(operation, "INVALID_RESPONSE", null);
|
|
@@ -196777,7 +197491,7 @@ function mutationResponse(expectation, response) {
|
|
|
196777
197491
|
const operation = `scheduled_functions.${action}`;
|
|
196778
197492
|
if (!response.ok)
|
|
196779
197493
|
return releaseControlMutationFailure(operation, response);
|
|
196780
|
-
const payload =
|
|
197494
|
+
const payload = objectRecord4(response.data);
|
|
196781
197495
|
const schedule = safeSchedule(payload?.schedule);
|
|
196782
197496
|
const confirmsRequest = schedule && Object.entries(expectedFields).every(([field, expected]) => isDeepStrictEqual(schedule[field], expected));
|
|
196783
197497
|
const confirmsRevision = action === "create" || payload?.previous_updated_at === expectation.expectedUpdatedAt && schedule !== null && schedule.updated_at > expectation.expectedUpdatedAt;
|
|
@@ -196795,7 +197509,7 @@ function deleteResponse(ref, scheduleId, expectedUpdatedAt, response) {
|
|
|
196795
197509
|
const operation = "scheduled_functions.delete";
|
|
196796
197510
|
if (!response.ok)
|
|
196797
197511
|
return releaseControlMutationFailure(operation, response);
|
|
196798
|
-
const payload =
|
|
197512
|
+
const payload = objectRecord4(response.data);
|
|
196799
197513
|
if (payload?.deleted !== true || payload.project_ref !== ref || payload.schedule_id !== scheduleId || payload.deleted_updated_at !== expectedUpdatedAt) {
|
|
196800
197514
|
return releaseControlFailure(operation, "OUTCOME_UNKNOWN", response.status);
|
|
196801
197515
|
}
|
|
@@ -196935,204 +197649,6 @@ var SCHEDULE_TOOL_SCHEMA = {
|
|
|
196935
197649
|
header_env: withDescription(headerEnvironmentSchema, "[create/update] JSON map of HTTP header names to environment variable names")
|
|
196936
197650
|
};
|
|
196937
197651
|
|
|
196938
|
-
// src/shared/mutation-protocol.ts
|
|
196939
|
-
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}$/;
|
|
196940
|
-
var FINGERPRINT_PATTERN = /^[0-9a-f]{64}$/;
|
|
196941
|
-
var OPERATION_PATTERN = /^[a-z0-9][a-z0-9._:-]{0,127}$/;
|
|
196942
|
-
var RESOURCE_KEY_PATTERN = /^v1\/(?:[a-z0-9][a-z0-9._-]{0,63})\/([A-Za-z0-9_-]{2,171})$/;
|
|
196943
|
-
var RESOURCE_ID_CONTROL_PATTERN = /[\u0000-\u001f\u007f-\u009f]/u;
|
|
196944
|
-
var FAILURE_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
196945
|
-
var LEASE_OWNER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,254}$/;
|
|
196946
|
-
var TIMESTAMP_PATTERN2 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
196947
|
-
var MAX_STATUS_RESPONSE_BYTES = 196608;
|
|
196948
|
-
var MAX_RESOURCE_ID_BYTES = 128;
|
|
196949
|
-
var FATAL_UTF8_DECODER = new TextDecoder("utf-8", { fatal: true });
|
|
196950
|
-
var MUTATION_STATUSES = new Set([
|
|
196951
|
-
"pending",
|
|
196952
|
-
"running",
|
|
196953
|
-
"succeeded",
|
|
196954
|
-
"failed_retryable",
|
|
196955
|
-
"failed_terminal",
|
|
196956
|
-
"outcome_unknown"
|
|
196957
|
-
]);
|
|
196958
|
-
var MUTATION_RESPONSE_KEYS = ["project_ref", "mutation"];
|
|
196959
|
-
var MUTATION_KEYS = [
|
|
196960
|
-
"project_ref",
|
|
196961
|
-
"mutation_id",
|
|
196962
|
-
"operation",
|
|
196963
|
-
"resource_key",
|
|
196964
|
-
"request_fingerprint",
|
|
196965
|
-
"principal",
|
|
196966
|
-
"status",
|
|
196967
|
-
"checkpoint",
|
|
196968
|
-
"receipt",
|
|
196969
|
-
"response_status",
|
|
196970
|
-
"failure_code",
|
|
196971
|
-
"lease",
|
|
196972
|
-
"completed_at",
|
|
196973
|
-
"created_at",
|
|
196974
|
-
"updated_at"
|
|
196975
|
-
];
|
|
196976
|
-
var PRINCIPAL_KEYS = ["type", "id"];
|
|
196977
|
-
var LEASE_KEYS = ["owner", "expires_at", "fencing_epoch"];
|
|
196978
|
-
function isMutationId(candidate) {
|
|
196979
|
-
return typeof candidate === "string" && MUTATION_ID_PATTERN2.test(candidate);
|
|
196980
|
-
}
|
|
196981
|
-
function objectRecord4(candidate) {
|
|
196982
|
-
return candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
|
|
196983
|
-
}
|
|
196984
|
-
function exactRecord(candidate, keys) {
|
|
196985
|
-
const record = objectRecord4(candidate);
|
|
196986
|
-
if (!record || Object.keys(record).length !== keys.length)
|
|
196987
|
-
return null;
|
|
196988
|
-
return keys.every((key) => Object.hasOwn(record, key)) ? record : null;
|
|
196989
|
-
}
|
|
196990
|
-
function emptyProjection(candidate) {
|
|
196991
|
-
const record = objectRecord4(candidate);
|
|
196992
|
-
return record && Object.keys(record).length === 0 ? record : null;
|
|
196993
|
-
}
|
|
196994
|
-
function canonicalTimestamp3(candidate) {
|
|
196995
|
-
if (typeof candidate !== "string" || !TIMESTAMP_PATTERN2.test(candidate))
|
|
196996
|
-
return false;
|
|
196997
|
-
const milliseconds = Date.parse(candidate);
|
|
196998
|
-
return Number.isFinite(milliseconds) && new Date(milliseconds).toISOString() === candidate;
|
|
196999
|
-
}
|
|
197000
|
-
function nullableTimestamp(candidate) {
|
|
197001
|
-
return candidate === null || canonicalTimestamp3(candidate);
|
|
197002
|
-
}
|
|
197003
|
-
function safePrincipal(candidate) {
|
|
197004
|
-
const principal = exactRecord(candidate, PRINCIPAL_KEYS);
|
|
197005
|
-
if (!principal || !["master", "admin", "project"].includes(String(principal.type)))
|
|
197006
|
-
return null;
|
|
197007
|
-
if (typeof principal.id !== "string" || !principal.id || principal.id.length > 320 || principal.id.trim() !== principal.id || /[\u0000-\u001f\u007f]/u.test(principal.id))
|
|
197008
|
-
return null;
|
|
197009
|
-
return { type: principal.type, id: principal.id };
|
|
197010
|
-
}
|
|
197011
|
-
function safeLease(candidate) {
|
|
197012
|
-
const lease = exactRecord(candidate, LEASE_KEYS);
|
|
197013
|
-
if (!lease || lease.owner !== null && (typeof lease.owner !== "string" || !LEASE_OWNER_PATTERN.test(lease.owner)))
|
|
197014
|
-
return null;
|
|
197015
|
-
if (!nullableTimestamp(lease.expires_at) || !Number.isSafeInteger(lease.fencing_epoch) || Number(lease.fencing_epoch) < 0)
|
|
197016
|
-
return null;
|
|
197017
|
-
return {
|
|
197018
|
-
owner: lease.owner,
|
|
197019
|
-
expires_at: lease.expires_at,
|
|
197020
|
-
fencing_epoch: Number(lease.fencing_epoch)
|
|
197021
|
-
};
|
|
197022
|
-
}
|
|
197023
|
-
function safeResponseStatus(candidate) {
|
|
197024
|
-
if (candidate === null)
|
|
197025
|
-
return null;
|
|
197026
|
-
return Number.isInteger(candidate) && Number(candidate) >= 100 && Number(candidate) <= 599 ? Number(candidate) : undefined;
|
|
197027
|
-
}
|
|
197028
|
-
function validLeaseState(status, lease) {
|
|
197029
|
-
const running = status === "running";
|
|
197030
|
-
return running ? lease.owner !== null && lease.expires_at !== null && lease.fencing_epoch > 0 : lease.owner === null && lease.expires_at === null;
|
|
197031
|
-
}
|
|
197032
|
-
function validMutationLifecycle(mutation, receipt, responseStatus) {
|
|
197033
|
-
const terminal = ["succeeded", "failed_terminal", "outcome_unknown"].includes(String(mutation.status));
|
|
197034
|
-
if (mutation.completed_at !== null !== terminal)
|
|
197035
|
-
return false;
|
|
197036
|
-
if (mutation.status === "succeeded") {
|
|
197037
|
-
return receipt !== null && responseStatus !== null && responseStatus >= 200 && responseStatus < 300 && mutation.failure_code === null;
|
|
197038
|
-
}
|
|
197039
|
-
if (mutation.status === "failed_terminal") {
|
|
197040
|
-
return receipt !== null && typeof mutation.failure_code === "string";
|
|
197041
|
-
}
|
|
197042
|
-
if (mutation.status === "failed_retryable" || mutation.status === "outcome_unknown") {
|
|
197043
|
-
return receipt !== null && typeof mutation.failure_code === "string";
|
|
197044
|
-
}
|
|
197045
|
-
if (mutation.status === "pending") {
|
|
197046
|
-
return receipt === null && responseStatus === null && mutation.failure_code === null;
|
|
197047
|
-
}
|
|
197048
|
-
return true;
|
|
197049
|
-
}
|
|
197050
|
-
function canonicalMutationResourceKey(candidate) {
|
|
197051
|
-
if (typeof candidate !== "string")
|
|
197052
|
-
return false;
|
|
197053
|
-
const match = RESOURCE_KEY_PATTERN.exec(candidate);
|
|
197054
|
-
if (!match)
|
|
197055
|
-
return false;
|
|
197056
|
-
const encodedResourceId = match[1];
|
|
197057
|
-
const resourceIdBytes = Buffer.from(encodedResourceId, "base64url");
|
|
197058
|
-
if (resourceIdBytes.byteLength < 1 || resourceIdBytes.byteLength > MAX_RESOURCE_ID_BYTES || resourceIdBytes.toString("base64url") !== encodedResourceId)
|
|
197059
|
-
return false;
|
|
197060
|
-
let resourceId;
|
|
197061
|
-
try {
|
|
197062
|
-
resourceId = FATAL_UTF8_DECODER.decode(resourceIdBytes);
|
|
197063
|
-
} catch (decodeError) {
|
|
197064
|
-
if (decodeError instanceof TypeError)
|
|
197065
|
-
return false;
|
|
197066
|
-
throw decodeError;
|
|
197067
|
-
}
|
|
197068
|
-
return resourceId.trim() === resourceId && !RESOURCE_ID_CONTROL_PATTERN.test(resourceId) && Buffer.from(resourceId, "utf8").equals(resourceIdBytes);
|
|
197069
|
-
}
|
|
197070
|
-
function validMutationIdentity(mutation) {
|
|
197071
|
-
if (!isMutationId(mutation.mutation_id) || typeof mutation.project_ref !== "string")
|
|
197072
|
-
return false;
|
|
197073
|
-
if (typeof mutation.operation !== "string" || !OPERATION_PATTERN.test(mutation.operation))
|
|
197074
|
-
return false;
|
|
197075
|
-
if (mutation.resource_key !== null && !canonicalMutationResourceKey(mutation.resource_key))
|
|
197076
|
-
return false;
|
|
197077
|
-
return typeof mutation.request_fingerprint === "string" && FINGERPRINT_PATTERN.test(mutation.request_fingerprint);
|
|
197078
|
-
}
|
|
197079
|
-
function validMutationTerminalFields(mutation) {
|
|
197080
|
-
if (typeof mutation.status !== "string" || !MUTATION_STATUSES.has(mutation.status))
|
|
197081
|
-
return false;
|
|
197082
|
-
if (mutation.failure_code !== null && (typeof mutation.failure_code !== "string" || !FAILURE_CODE_PATTERN.test(mutation.failure_code)))
|
|
197083
|
-
return false;
|
|
197084
|
-
return nullableTimestamp(mutation.completed_at) && canonicalTimestamp3(mutation.created_at) && canonicalTimestamp3(mutation.updated_at);
|
|
197085
|
-
}
|
|
197086
|
-
function safeMutationStatus(candidate) {
|
|
197087
|
-
const mutation = exactRecord(candidate, MUTATION_KEYS);
|
|
197088
|
-
if (!mutation || !validMutationIdentity(mutation) || !validMutationTerminalFields(mutation))
|
|
197089
|
-
return null;
|
|
197090
|
-
const principal = safePrincipal(mutation.principal);
|
|
197091
|
-
const checkpoint = emptyProjection(mutation.checkpoint);
|
|
197092
|
-
const receipt = mutation.receipt === null ? null : emptyProjection(mutation.receipt);
|
|
197093
|
-
const responseStatus = safeResponseStatus(mutation.response_status);
|
|
197094
|
-
const lease = safeLease(mutation.lease);
|
|
197095
|
-
if (!principal || !checkpoint || mutation.receipt !== null && !receipt || responseStatus === undefined || !lease || !validLeaseState(String(mutation.status), lease) || !validMutationLifecycle(mutation, receipt, responseStatus))
|
|
197096
|
-
return null;
|
|
197097
|
-
return {
|
|
197098
|
-
project_ref: mutation.project_ref,
|
|
197099
|
-
mutation_id: mutation.mutation_id,
|
|
197100
|
-
operation: mutation.operation,
|
|
197101
|
-
resource_key: mutation.resource_key,
|
|
197102
|
-
request_fingerprint: mutation.request_fingerprint,
|
|
197103
|
-
principal,
|
|
197104
|
-
status: mutation.status,
|
|
197105
|
-
checkpoint,
|
|
197106
|
-
receipt,
|
|
197107
|
-
response_status: responseStatus,
|
|
197108
|
-
failure_code: mutation.failure_code,
|
|
197109
|
-
lease,
|
|
197110
|
-
completed_at: mutation.completed_at,
|
|
197111
|
-
created_at: mutation.created_at,
|
|
197112
|
-
updated_at: mutation.updated_at
|
|
197113
|
-
};
|
|
197114
|
-
}
|
|
197115
|
-
function mutationStatusPath(ref, mutationId) {
|
|
197116
|
-
if (!isMutationId(mutationId))
|
|
197117
|
-
throw new Error("'mutation_id' must be a UUIDv4");
|
|
197118
|
-
return `/v1/projects/${projectRefPathSegment(ref, "Mutations")}/mutations/${encodeURIComponent(mutationId)}`;
|
|
197119
|
-
}
|
|
197120
|
-
function mutationStatusResponse(ref, mutationId, payload) {
|
|
197121
|
-
const response = exactRecord(payload, MUTATION_RESPONSE_KEYS);
|
|
197122
|
-
const mutation = safeMutationStatus(response?.mutation);
|
|
197123
|
-
return response?.project_ref === ref && mutation?.project_ref === ref && mutation.mutation_id === mutationId ? mutation : null;
|
|
197124
|
-
}
|
|
197125
|
-
async function fetchMutationStatus(http, ref, mutationId) {
|
|
197126
|
-
const response = await http.get(mutationStatusPath(ref, mutationId), {
|
|
197127
|
-
maxResponseBytes: MAX_STATUS_RESPONSE_BYTES
|
|
197128
|
-
});
|
|
197129
|
-
if (!response.ok) {
|
|
197130
|
-
return { kind: "unavailable", httpStatus: response.transportError ? null : response.status };
|
|
197131
|
-
}
|
|
197132
|
-
const mutation = mutationStatusResponse(ref, mutationId, response.data);
|
|
197133
|
-
return mutation ? { kind: "available", mutation } : { kind: "invalid" };
|
|
197134
|
-
}
|
|
197135
|
-
|
|
197136
197652
|
// src/shared/tools/mutation-tools.ts
|
|
197137
197653
|
function requiredText3(args, name) {
|
|
197138
197654
|
const candidate = args[name];
|
|
@@ -197171,7 +197687,7 @@ var MUTATION_TOOL_SCHEMA = {
|
|
|
197171
197687
|
};
|
|
197172
197688
|
|
|
197173
197689
|
// src/shared/tools/release-tools.ts
|
|
197174
|
-
import { createHash as
|
|
197690
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
197175
197691
|
import { existsSync as existsSync14, mkdirSync as mkdirSync4, readdirSync as readdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "node:fs";
|
|
197176
197692
|
import { dirname as dirname11, join as join18, relative as relative10, resolve as resolve23 } from "node:path";
|
|
197177
197693
|
var SAFE_PROJECT_REF = /^[A-Za-z0-9_-]{1,64}$/;
|
|
@@ -197413,7 +197929,7 @@ function genericScopedReleaseScopeJson(scope) {
|
|
|
197413
197929
|
return JSON.stringify(canonicalValue(scope));
|
|
197414
197930
|
}
|
|
197415
197931
|
function genericScopedReleaseScopeSha256(scope) {
|
|
197416
|
-
return
|
|
197932
|
+
return createHash8("sha256").update(genericScopedReleaseScopeJson(scope)).digest("hex");
|
|
197417
197933
|
}
|
|
197418
197934
|
function runGitRevParse(cwd, args) {
|
|
197419
197935
|
const res = Bun.spawnSync(["git", ...args], { cwd });
|
|
@@ -197919,7 +198435,7 @@ function registerReleaseTools(server2, http, options = {}) {
|
|
|
197919
198435
|
|
|
197920
198436
|
// src/shared/tools/deploy-tools.ts
|
|
197921
198437
|
import { access, lstat as lstat5, mkdtemp as mkdtemp2, opendir, readFile as readFile7, rm as rm3, writeFile as writeFile7 } from "node:fs/promises";
|
|
197922
|
-
import { createHash as
|
|
198438
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
197923
198439
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
197924
198440
|
import { basename as basename10, dirname as dirname12, join as join19, relative as relative11, resolve as resolve24, sep as sep5 } from "node:path";
|
|
197925
198441
|
|
|
@@ -198854,7 +199370,7 @@ async function createFrontendArchive(outputDirectory) {
|
|
|
198854
199370
|
try {
|
|
198855
199371
|
const archiveBytes = zipSync(collected.files, { level: 6, mtime: FIXED_ZIP_MTIME });
|
|
198856
199372
|
await writeFile7(archivePath, archiveBytes);
|
|
198857
|
-
const sha256 =
|
|
199373
|
+
const sha256 = createHash9("sha256").update(archiveBytes).digest("hex");
|
|
198858
199374
|
return {
|
|
198859
199375
|
archivePath,
|
|
198860
199376
|
sha256,
|
|
@@ -199077,7 +199593,7 @@ function registerDeployTools(server2, http, options = {}) {
|
|
|
199077
199593
|
import { existsSync as existsSync15 } from "node:fs";
|
|
199078
199594
|
import { readFile as readFile8 } from "node:fs/promises";
|
|
199079
199595
|
import { readdir as readdir8 } from "node:fs/promises";
|
|
199080
|
-
import { createHash as
|
|
199596
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
199081
199597
|
import { join as join20, resolve as resolve25 } from "node:path";
|
|
199082
199598
|
import { compileProject as compileProject2 } from "@supacloud/compiler";
|
|
199083
199599
|
var SAFE_TOKEN = /^[A-Za-z0-9._:@/+,-]+$/;
|
|
@@ -199176,7 +199692,7 @@ async function sourceFingerprint(root) {
|
|
|
199176
199692
|
}
|
|
199177
199693
|
};
|
|
199178
199694
|
await visit(root);
|
|
199179
|
-
const hash =
|
|
199695
|
+
const hash = createHash10("sha256");
|
|
199180
199696
|
for (const file of files) {
|
|
199181
199697
|
hash.update(file.slice(root.length).replace(/\\/g, "/"));
|
|
199182
199698
|
hash.update(await readFile8(file));
|
|
@@ -199345,7 +199861,7 @@ function registerRemoteDevTools(server2, options = {}) {
|
|
|
199345
199861
|
// package.json
|
|
199346
199862
|
var package_default = {
|
|
199347
199863
|
name: "@supacloud/cli",
|
|
199348
|
-
version: "0.
|
|
199864
|
+
version: "0.82.0",
|
|
199349
199865
|
description: "Project-scoped CLI for SupaCloud users",
|
|
199350
199866
|
type: "module",
|
|
199351
199867
|
main: "./dist/index.js",
|
|
@@ -199670,12 +200186,19 @@ EXAMPLES
|
|
|
199670
200186
|
${preferredCommand} app diff --ref abc123 --id orders --environment_id test --release_id <sha256> --configuration_id <uuid>
|
|
199671
200187
|
${preferredCommand} app deploy --ref abc123 --id orders --environment_id test --release_id <sha256> --configuration_id <uuid>
|
|
199672
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>
|
|
199673
200190
|
${preferredCommand} --env production app promote-plan --id orders --environment_id production --source_ref staging --source_environment_id staging --source_release_id <sha256>
|
|
199674
200191
|
${preferredCommand} app preview-plan --ref abc123 --id orders --environment_id test --release_id <sha256> --branch_ref preview-orders
|
|
199675
200192
|
${preferredCommand} app preview --ref abc123 --id orders --environment_id test --release_id <sha256> --configuration_id <uuid> --ttl_seconds 3600 --wait
|
|
199676
200193
|
${preferredCommand} app previews --ref abc123 --id orders --environment_id test
|
|
199677
200194
|
${preferredCommand} app preview-status --ref abc123 --id orders --environment_id test --preview_id <uuid> --wait --timeout_seconds 300
|
|
199678
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>
|
|
199679
200202
|
${preferredCommand} app logs --ref abc123 --id orders --environment_id test
|
|
199680
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>
|
|
199681
200204
|
${preferredCommand} app check --root . --strict
|