@supacloud/admin 0.24.2 → 0.25.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 +10 -0
- package/dist/index.js +222 -66
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -70,6 +70,7 @@ production requires the exact project ref confirmation:
|
|
|
70
70
|
|
|
71
71
|
```bash
|
|
72
72
|
supacloud-admin frontend list_releases --ref abc123 --id web
|
|
73
|
+
supacloud-admin frontend get_active_release --ref abc123 --id web
|
|
73
74
|
supacloud-admin frontend get_release --ref abc123 --id web --release_id <sha256>
|
|
74
75
|
supacloud-admin frontend upload_release --ref abc123 --id web \
|
|
75
76
|
--zip_path /secure/site.zip --confirm-production abc123
|
|
@@ -77,6 +78,8 @@ supacloud-admin frontend activate_release --ref abc123 --id web \
|
|
|
77
78
|
--release_id <sha256> --expected_active_release_id absent \
|
|
78
79
|
--expected_activation_id absent --mutation_id <uuid-v4> \
|
|
79
80
|
--confirm-production abc123
|
|
81
|
+
supacloud-admin frontend rollback --ref abc123 --id web \
|
|
82
|
+
--release_id <retained-sha256> --confirm-production abc123
|
|
80
83
|
```
|
|
81
84
|
|
|
82
85
|
The CLI reads the archive through a no-follow file descriptor, verifies its
|
|
@@ -88,6 +91,13 @@ activation mutations require the Linux held-directory-FD implementation; on
|
|
|
88
91
|
other platforms, Management API returns HTTP 503 before it reads the upload
|
|
89
92
|
body, creates release directories, or writes a mutation journal.
|
|
90
93
|
|
|
94
|
+
The active snapshot automatically supports older HTTP 404 endpoints through
|
|
95
|
+
bounded history and exact artifact readback, never authorization or integrity
|
|
96
|
+
failures. `rollback` handles current CAS and mutation IDs automatically while
|
|
97
|
+
requiring an explicit retained target. It never retries a concurrent conflict,
|
|
98
|
+
restores a database or infers the previous version from sorted history.
|
|
99
|
+
Unknown results include the mutation ID for read-only reconciliation.
|
|
100
|
+
|
|
91
101
|
SSH host keys are fail-closed: setting `SUPACLOUD_HOST` and credentials is not
|
|
92
102
|
enough to enable SSH actions. Obtain the fingerprint through a trusted channel,
|
|
93
103
|
compare it out of band, then set it explicitly. For example, the discovery
|
package/dist/index.js
CHANGED
|
@@ -2855,23 +2855,23 @@ var require_nacl_fast = __commonJS(function(exports, module) {
|
|
|
2855
2855
|
randombytes = fn;
|
|
2856
2856
|
};
|
|
2857
2857
|
(function() {
|
|
2858
|
-
var
|
|
2859
|
-
if (
|
|
2858
|
+
var crypto2 = typeof self !== "undefined" ? self.crypto || self.msCrypto : null;
|
|
2859
|
+
if (crypto2 && crypto2.getRandomValues) {
|
|
2860
2860
|
var QUOTA = 65536;
|
|
2861
2861
|
nacl.setPRNG(function(x, n) {
|
|
2862
2862
|
var i, v = new Uint8Array(n);
|
|
2863
2863
|
for (i = 0;i < n; i += QUOTA) {
|
|
2864
|
-
|
|
2864
|
+
crypto2.getRandomValues(v.subarray(i, i + Math.min(n - i, QUOTA)));
|
|
2865
2865
|
}
|
|
2866
2866
|
for (i = 0;i < n; i++)
|
|
2867
2867
|
x[i] = v[i];
|
|
2868
2868
|
cleanup(v);
|
|
2869
2869
|
});
|
|
2870
2870
|
} else if (true) {
|
|
2871
|
-
|
|
2872
|
-
if (
|
|
2871
|
+
crypto2 = __require("crypto");
|
|
2872
|
+
if (crypto2 && crypto2.randomBytes) {
|
|
2873
2873
|
nacl.setPRNG(function(x, n) {
|
|
2874
|
-
var i, v =
|
|
2874
|
+
var i, v = crypto2.randomBytes(n);
|
|
2875
2875
|
for (i = 0;i < n; i++)
|
|
2876
2876
|
x[i] = v[i];
|
|
2877
2877
|
cleanup(v);
|
|
@@ -4135,14 +4135,14 @@ var require_bcrypt_pbkdf = __commonJS(function(exports, module) {
|
|
|
4135
4135
|
|
|
4136
4136
|
// node_modules/ssh2/lib/protocol/constants.js
|
|
4137
4137
|
var require_constants = __commonJS(function(exports, module) {
|
|
4138
|
-
var
|
|
4138
|
+
var crypto2 = __require("crypto");
|
|
4139
4139
|
var cpuInfo;
|
|
4140
4140
|
try {
|
|
4141
4141
|
cpuInfo = __require("cpu-features")();
|
|
4142
4142
|
} catch {}
|
|
4143
4143
|
var { bindingAvailable, CIPHER_INFO, MAC_INFO } = require_crypto();
|
|
4144
4144
|
var eddsaSupported = (() => {
|
|
4145
|
-
if (typeof
|
|
4145
|
+
if (typeof crypto2.sign === "function" && typeof crypto2.verify === "function") {
|
|
4146
4146
|
const key = `-----BEGIN PRIVATE KEY-----\r
|
|
4147
4147
|
MC4CAQAwBQYDK2VwBCIEIHKj+sVa9WcD` + `/q2DJUJaf43Kptc8xYuUQA4bOFj9vC8T\r
|
|
4148
4148
|
-----END PRIVATE KEY-----`;
|
|
@@ -4150,14 +4150,14 @@ MC4CAQAwBQYDK2VwBCIEIHKj+sVa9WcD` + `/q2DJUJaf43Kptc8xYuUQA4bOFj9vC8T\r
|
|
|
4150
4150
|
let sig;
|
|
4151
4151
|
let verified;
|
|
4152
4152
|
try {
|
|
4153
|
-
sig =
|
|
4154
|
-
verified =
|
|
4153
|
+
sig = crypto2.sign(null, data, key);
|
|
4154
|
+
verified = crypto2.verify(null, data, key, sig);
|
|
4155
4155
|
} catch {}
|
|
4156
4156
|
return Buffer.isBuffer(sig) && sig.length === 64 && verified === true;
|
|
4157
4157
|
}
|
|
4158
4158
|
return false;
|
|
4159
4159
|
})();
|
|
4160
|
-
var curve25519Supported = typeof
|
|
4160
|
+
var curve25519Supported = typeof crypto2.diffieHellman === "function" && typeof crypto2.generateKeyPairSync === "function" && typeof crypto2.createPublicKey === "function";
|
|
4161
4161
|
var DEFAULT_KEX = [
|
|
4162
4162
|
"ecdh-sha2-nistp256",
|
|
4163
4163
|
"ecdh-sha2-nistp384",
|
|
@@ -4192,7 +4192,7 @@ MC4CAQAwBQYDK2VwBCIEIHKj+sVa9WcD` + `/q2DJUJaf43Kptc8xYuUQA4bOFj9vC8T\r
|
|
|
4192
4192
|
"ssh-dss"
|
|
4193
4193
|
]);
|
|
4194
4194
|
var canUseCipher = (() => {
|
|
4195
|
-
const ciphers =
|
|
4195
|
+
const ciphers = crypto2.getCiphers();
|
|
4196
4196
|
return (name) => ciphers.includes(CIPHER_INFO[name].sslName);
|
|
4197
4197
|
})();
|
|
4198
4198
|
var DEFAULT_CIPHER = [
|
|
@@ -4227,7 +4227,7 @@ MC4CAQAwBQYDK2VwBCIEIHKj+sVa9WcD` + `/q2DJUJaf43Kptc8xYuUQA4bOFj9vC8T\r
|
|
|
4227
4227
|
"arcfour"
|
|
4228
4228
|
].filter(canUseCipher));
|
|
4229
4229
|
var canUseMAC = (() => {
|
|
4230
|
-
const hashes =
|
|
4230
|
+
const hashes = crypto2.getHashes();
|
|
4231
4231
|
return (name) => hashes.includes(MAC_INFO[name].sslName);
|
|
4232
4232
|
})();
|
|
4233
4233
|
var DEFAULT_MAC = [
|
|
@@ -19618,6 +19618,11 @@ class DecodeBuilder {
|
|
|
19618
19618
|
function Codec(type) {
|
|
19619
19619
|
return new DecodeBuilder(type);
|
|
19620
19620
|
}
|
|
19621
|
+
function Decode(type, callback) {
|
|
19622
|
+
return Codec(type).Decode(callback).Encode(() => {
|
|
19623
|
+
throw Error("Encode not implemented");
|
|
19624
|
+
});
|
|
19625
|
+
}
|
|
19621
19626
|
function IsCodec(value) {
|
|
19622
19627
|
return IsSchema(value) && HasPropertyKey(value, "~codec") && IsObject(value["~codec"]) && HasPropertyKey(value["~codec"], "encode") && HasPropertyKey(value["~codec"], "decode");
|
|
19623
19628
|
}
|
|
@@ -24838,7 +24843,22 @@ var Delete = _Object_({
|
|
|
24838
24843
|
});
|
|
24839
24844
|
var Edit = Union([Insert2, Update2, Delete]);
|
|
24840
24845
|
// ../cli/src/shared/schema.ts
|
|
24841
|
-
var
|
|
24846
|
+
var DECODE_ID = "~supacloudDecodeId";
|
|
24847
|
+
|
|
24848
|
+
class OutputDecodeError extends Error {
|
|
24849
|
+
decodeId;
|
|
24850
|
+
issues;
|
|
24851
|
+
constructor(decodeId, issues) {
|
|
24852
|
+
super(issues.map((issue) => `- ${issue.path}: ${issue.message}`).join(`
|
|
24853
|
+
`));
|
|
24854
|
+
this.decodeId = decodeId;
|
|
24855
|
+
this.issues = issues;
|
|
24856
|
+
}
|
|
24857
|
+
}
|
|
24858
|
+
function decodeIdOf(schema) {
|
|
24859
|
+
const value = Object.getOwnPropertyDescriptor(schema, DECODE_ID)?.value;
|
|
24860
|
+
return typeof value === "symbol" ? value : undefined;
|
|
24861
|
+
}
|
|
24842
24862
|
function schemaErrorPath(path) {
|
|
24843
24863
|
return path.replace(/^\//, "").replaceAll("/", ".") || "args";
|
|
24844
24864
|
}
|
|
@@ -24850,42 +24870,46 @@ function transformErrorLine(error) {
|
|
|
24850
24870
|
const path = typeof error === "object" && error !== null && "instancePath" in error && typeof error.instancePath === "string" ? schemaErrorPath(error.instancePath) : "args";
|
|
24851
24871
|
return `- ${path}: ${message}`;
|
|
24852
24872
|
}
|
|
24853
|
-
function
|
|
24854
|
-
const
|
|
24855
|
-
const issues = schemaErrorLines(objectSchema, input);
|
|
24873
|
+
function validateToolArguments(schema, input) {
|
|
24874
|
+
const issues = schemaErrorLines(_Object_(schema, { additionalProperties: false }), input);
|
|
24856
24875
|
if (issues.length > 0)
|
|
24857
24876
|
throw new Error(`Invalid arguments:
|
|
24858
24877
|
${issues.join(`
|
|
24859
24878
|
`)}`);
|
|
24879
|
+
}
|
|
24880
|
+
function isRecord(value) {
|
|
24881
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
24882
|
+
}
|
|
24883
|
+
function parseToolArguments(schema, input) {
|
|
24884
|
+
const objectSchema = _Object_(schema, { additionalProperties: false });
|
|
24885
|
+
validateToolArguments(schema, input);
|
|
24860
24886
|
let decoded;
|
|
24861
24887
|
try {
|
|
24862
24888
|
decoded = Decode10(objectSchema, input);
|
|
24863
24889
|
} catch (error) {
|
|
24890
|
+
if (error instanceof OutputDecodeError) {
|
|
24891
|
+
const entry = Object.entries(schema).find(([, property]) => decodeIdOf(property) === error.decodeId);
|
|
24892
|
+
const path = entry?.[0] ?? "args";
|
|
24893
|
+
const issues = error.issues.map((issue) => `- ${issue.path === "args" ? path : `${path}.${issue.path}`}: ${issue.message}`);
|
|
24894
|
+
throw new Error(`Invalid arguments:
|
|
24895
|
+
${issues.join(`
|
|
24896
|
+
`)}`);
|
|
24897
|
+
}
|
|
24864
24898
|
throw new Error(`Invalid arguments:
|
|
24865
24899
|
${transformErrorLine(error)}`);
|
|
24866
24900
|
}
|
|
24867
|
-
const
|
|
24868
|
-
|
|
24869
|
-
|
|
24870
|
-
if (outputSchema === undefined || decoded[name] === undefined)
|
|
24871
|
-
continue;
|
|
24872
|
-
for (const issue of Errors2(outputSchema, decoded[name])) {
|
|
24873
|
-
const path = schemaErrorPath(issue.instancePath);
|
|
24874
|
-
decodedIssues.push(`- ${path === "args" ? name : `${name}.${path}`}: ${issue.message}`);
|
|
24875
|
-
}
|
|
24901
|
+
const decodedFields = decoded;
|
|
24902
|
+
if (!isRecord(decodedFields)) {
|
|
24903
|
+
throw new Error("Invalid arguments: decoded value must be an object");
|
|
24876
24904
|
}
|
|
24877
|
-
if (decodedIssues.length > 0)
|
|
24878
|
-
throw new Error(`Invalid arguments:
|
|
24879
|
-
${decodedIssues.join(`
|
|
24880
|
-
`)}`);
|
|
24881
24905
|
return decoded;
|
|
24882
24906
|
}
|
|
24883
24907
|
function stringEnum(values, options) {
|
|
24884
|
-
|
|
24885
|
-
return Enum(enumRecord, options);
|
|
24908
|
+
return Enum([...values], options);
|
|
24886
24909
|
}
|
|
24887
24910
|
function mergeSchemaOptions(schema, options) {
|
|
24888
|
-
const result =
|
|
24911
|
+
const result = { ...schema };
|
|
24912
|
+
Object.setPrototypeOf(result, Object.getPrototypeOf(schema));
|
|
24889
24913
|
Object.defineProperties(result, Object.getOwnPropertyDescriptors(schema));
|
|
24890
24914
|
for (const [key, value] of Object.entries(options)) {
|
|
24891
24915
|
Object.defineProperty(result, key, { value, enumerable: true, writable: true, configurable: true });
|
|
@@ -24899,9 +24923,19 @@ function optional(schema, description) {
|
|
|
24899
24923
|
return Optional(description ? withDescription(schema, description) : schema);
|
|
24900
24924
|
}
|
|
24901
24925
|
function decodedSchema(inputSchema, outputSchema, decode, options) {
|
|
24902
|
-
const
|
|
24926
|
+
const decodeId = Symbol("decodedSchema");
|
|
24927
|
+
const transform = Decode(inputSchema, (input) => {
|
|
24928
|
+
const value = decode(input);
|
|
24929
|
+
const issues = [...Errors2(outputSchema, value)].map((issue) => ({
|
|
24930
|
+
path: schemaErrorPath(issue.instancePath),
|
|
24931
|
+
message: issue.message
|
|
24932
|
+
}));
|
|
24933
|
+
if (issues.length > 0)
|
|
24934
|
+
throw new OutputDecodeError(decodeId, issues);
|
|
24935
|
+
return Decode10(outputSchema, value);
|
|
24936
|
+
});
|
|
24903
24937
|
const result = options ? mergeSchemaOptions(transform, options) : transform;
|
|
24904
|
-
Object.defineProperty(result,
|
|
24938
|
+
Object.defineProperty(result, DECODE_ID, { value: decodeId, enumerable: false, configurable: true });
|
|
24905
24939
|
return result;
|
|
24906
24940
|
}
|
|
24907
24941
|
function schemaEnumValues(schema) {
|
|
@@ -25894,8 +25928,8 @@ var ACTION_POLICY = {
|
|
|
25894
25928
|
]
|
|
25895
25929
|
},
|
|
25896
25930
|
frontend: {
|
|
25897
|
-
read: ["list_releases", "get_release"],
|
|
25898
|
-
write: ["upload_release", "activate_release"]
|
|
25931
|
+
read: ["list_releases", "get_active_release", "get_release"],
|
|
25932
|
+
write: ["upload_release", "activate_release", "rollback"]
|
|
25899
25933
|
},
|
|
25900
25934
|
ssh: {
|
|
25901
25935
|
read: [
|
|
@@ -33709,13 +33743,13 @@ async function writeProjectEnvFile(prepared, credentials, operations = nodeProje
|
|
|
33709
33743
|
throw new ProjectCreateEnvError("ENV_FILE_WRITE_FAILED");
|
|
33710
33744
|
}
|
|
33711
33745
|
}
|
|
33712
|
-
function
|
|
33746
|
+
function isRecord2(candidate) {
|
|
33713
33747
|
return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate);
|
|
33714
33748
|
}
|
|
33715
33749
|
function decodedJwtPart(encodedPart) {
|
|
33716
33750
|
try {
|
|
33717
33751
|
const decodedPart = JSON.parse(Buffer.from(encodedPart, "base64url").toString("utf8"));
|
|
33718
|
-
return
|
|
33752
|
+
return isRecord2(decodedPart) ? decodedPart : null;
|
|
33719
33753
|
} catch {
|
|
33720
33754
|
return null;
|
|
33721
33755
|
}
|
|
@@ -33756,28 +33790,28 @@ function projectApiUrl(candidate, projectRef, expectedApiOrigin) {
|
|
|
33756
33790
|
return parsedUrl.origin;
|
|
33757
33791
|
}
|
|
33758
33792
|
function parseProjectCreateIdentity(responsePayload, expectedApiOrigin) {
|
|
33759
|
-
if (!
|
|
33793
|
+
if (!isRecord2(responsePayload) || typeof responsePayload.ref !== "string")
|
|
33760
33794
|
return null;
|
|
33761
|
-
if (!PROJECT_REF.test(responsePayload.ref) || !
|
|
33795
|
+
if (!PROJECT_REF.test(responsePayload.ref) || !isRecord2(responsePayload.api))
|
|
33762
33796
|
return null;
|
|
33763
33797
|
const apiUrl = projectApiUrl(responsePayload.api.url, responsePayload.ref, expectedApiOrigin);
|
|
33764
33798
|
return apiUrl ? { projectRef: responsePayload.ref, apiUrl } : null;
|
|
33765
33799
|
}
|
|
33766
33800
|
function parseProjectCreateWithoutCredentials(responsePayload, expectedApiOrigin) {
|
|
33767
33801
|
const identity = parseProjectCreateIdentity(responsePayload, expectedApiOrigin);
|
|
33768
|
-
if (!identity || !
|
|
33802
|
+
if (!identity || !isRecord2(responsePayload))
|
|
33769
33803
|
return null;
|
|
33770
33804
|
return Object.hasOwn(responsePayload, "credentials") ? null : identity;
|
|
33771
33805
|
}
|
|
33772
33806
|
function parseProjectCreateCredentials(responsePayload, expectedApiOrigin, expectedProjectName) {
|
|
33773
|
-
if (!expectedApiOrigin || !expectedProjectName || !
|
|
33807
|
+
if (!expectedApiOrigin || !expectedProjectName || !isRecord2(responsePayload))
|
|
33774
33808
|
return null;
|
|
33775
33809
|
if (responsePayload.name !== expectedProjectName)
|
|
33776
33810
|
return null;
|
|
33777
33811
|
const identity = parseProjectCreateIdentity(responsePayload, expectedApiOrigin);
|
|
33778
33812
|
if (!identity)
|
|
33779
33813
|
return null;
|
|
33780
|
-
if (!
|
|
33814
|
+
if (!isRecord2(responsePayload.credentials))
|
|
33781
33815
|
return null;
|
|
33782
33816
|
const serviceRoleKey = responsePayload.credentials.service_role_key;
|
|
33783
33817
|
return isServiceRoleKey(serviceRoleKey) ? { ...identity, serviceRoleKey } : null;
|
|
@@ -34171,7 +34205,7 @@ var POSTGREST_ATTESTATION_STATES = [
|
|
|
34171
34205
|
var POSTGREST_DESIRED_STATES = ["running", "stopped"];
|
|
34172
34206
|
var POSTGREST_ACTUAL_STATES = ["running", "stopped", "starting", "error"];
|
|
34173
34207
|
var POSTGREST_HEALTH_STATES = ["healthy", "unhealthy", "unknown"];
|
|
34174
|
-
function
|
|
34208
|
+
function isRecord3(candidate) {
|
|
34175
34209
|
return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate);
|
|
34176
34210
|
}
|
|
34177
34211
|
function hasExactKeys(candidate, expectedKeys) {
|
|
@@ -34220,7 +34254,7 @@ function hasValidSecretsState(candidate) {
|
|
|
34220
34254
|
return isLoadedSecretsState(candidate) || isUnverifiedSecretsState(candidate);
|
|
34221
34255
|
}
|
|
34222
34256
|
function isRuntimeSecretsSnapshot(payload) {
|
|
34223
|
-
if (!
|
|
34257
|
+
if (!isRecord3(payload) || !hasExactKeys(payload, SECRETS_KEYS))
|
|
34224
34258
|
return false;
|
|
34225
34259
|
if (!isRevisionOrNull(payload.loaded_revision) || typeof payload.desired_revision !== "string" || !ATTESTED_REVISION_PATTERN.test(payload.desired_revision) || !isEnumMember(payload.load_state, SECRET_LOAD_STATES) || !(payload.load_source === null || isEnumMember(payload.load_source, SECRET_LOAD_SOURCES)) || !isBooleanOrNull(payload.matches_desired) || !isIsoTimestampOrNull(payload.loaded_at))
|
|
34226
34260
|
return false;
|
|
@@ -34253,7 +34287,7 @@ function hasValidPostgrestState(candidate) {
|
|
|
34253
34287
|
return candidate.attestation_state === "drifted";
|
|
34254
34288
|
}
|
|
34255
34289
|
function isPostgrestRuntimeSnapshot(payload, projectRef) {
|
|
34256
|
-
if (!
|
|
34290
|
+
if (!isRecord3(payload) || !hasExactKeys(payload, POSTGREST_KEYS))
|
|
34257
34291
|
return false;
|
|
34258
34292
|
if (typeof payload.desired_revision !== "string" || !ATTESTED_REVISION_PATTERN.test(payload.desired_revision) || !isRevisionOrNull(payload.loaded_revision) || !isEnumMember(payload.attestation_state, POSTGREST_ATTESTATION_STATES) || !isBooleanOrNull(payload.matches_desired) || !isEnumMember(payload.desired, POSTGREST_DESIRED_STATES) || !isEnumMember(payload.actual, POSTGREST_ACTUAL_STATES) || !isEnumMember(payload.health, POSTGREST_HEALTH_STATES) || !Number.isSafeInteger(payload.port) || Number(payload.port) < 1 || Number(payload.port) > 65535 || payload.unit !== `supacloud-pgrst@${projectRef}` || !isIsoTimestampOrNull(payload.loaded_at))
|
|
34259
34293
|
return false;
|
|
@@ -34273,7 +34307,7 @@ function hasCausalLoadTimestamps(snapshot) {
|
|
|
34273
34307
|
return [snapshot.secrets.loaded_at, snapshot.postgrest.loaded_at].every((loadedAt) => loadedAt === null || Date.parse(loadedAt) <= capturedAt);
|
|
34274
34308
|
}
|
|
34275
34309
|
function parseProjectRuntimeSnapshot(payload, requestedProjectRef) {
|
|
34276
|
-
if (!SAFE_PROJECT_REF3.test(requestedProjectRef) || !
|
|
34310
|
+
if (!SAFE_PROJECT_REF3.test(requestedProjectRef) || !isRecord3(payload) || !hasExactKeys(payload, SNAPSHOT_KEYS) || payload.schema !== RUNTIME_SNAPSHOT_SCHEMA || payload.project_ref !== requestedProjectRef || !isIsoTimestampOrNull(payload.captured_at) || payload.captured_at === null || !isRuntimeSecretsSnapshot(payload.secrets) || !isPostgrestRuntimeSnapshot(payload.postgrest, requestedProjectRef))
|
|
34277
34311
|
return null;
|
|
34278
34312
|
const snapshot = payload;
|
|
34279
34313
|
return hasCausalLoadTimestamps(snapshot) ? sanitizedSnapshot(snapshot) : null;
|
|
@@ -34812,6 +34846,21 @@ function registerAdminProjectCliTools(server, http, options = {}) {
|
|
|
34812
34846
|
});
|
|
34813
34847
|
}
|
|
34814
34848
|
|
|
34849
|
+
// ../cli/src/shared/tool-server.ts
|
|
34850
|
+
var resultSchema = _Object_({
|
|
34851
|
+
content: _Array_(_Object_({ type: Literal("text"), text: String2() })),
|
|
34852
|
+
isError: Optional(Boolean2())
|
|
34853
|
+
});
|
|
34854
|
+
function registerTool(server, name, description, schema, callback) {
|
|
34855
|
+
server.tool(name, description, schema, async (input) => {
|
|
34856
|
+
const args = parseToolArguments(schema, input);
|
|
34857
|
+
const result = await callback(args);
|
|
34858
|
+
if (!Check2(resultSchema, result))
|
|
34859
|
+
throw new Error(`Invalid result from tool '${name}'`);
|
|
34860
|
+
return result;
|
|
34861
|
+
});
|
|
34862
|
+
}
|
|
34863
|
+
|
|
34815
34864
|
// ../cli/src/shared/tools/gateway-tools.ts
|
|
34816
34865
|
function parseCliStringArray(value) {
|
|
34817
34866
|
if (Array.isArray(value))
|
|
@@ -34866,7 +34915,7 @@ var ok2 = (res) => res.ok ? JSON.stringify(res.data, null, 2) : `❌ Failed (${r
|
|
|
34866
34915
|
var simple2 = (res, msg) => res.ok ? `✅ ${msg}` : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
|
|
34867
34916
|
function registerGatewayTools(server, http, options = {}) {
|
|
34868
34917
|
const { projectRef } = options;
|
|
34869
|
-
server
|
|
34918
|
+
registerTool(server, "gateway", `Gateway / Caddy 配置(通过 JSON Admin API 注入)。要求 admin 权限。
|
|
34870
34919
|
Actions: routes, upsert_route, update_route, delete_route, config, get_certificate, update_certificate, issue_certificate, deploy_certificate, rebuild, custom_hostname, set_custom_hostname, delete_custom_hostname, verify_custom_hostname`, {
|
|
34871
34920
|
action: withDescription(stringEnum([
|
|
34872
34921
|
"routes",
|
|
@@ -35267,12 +35316,15 @@ function releaseInventory(candidate, expected) {
|
|
|
35267
35316
|
next_cursor: nextCursor
|
|
35268
35317
|
};
|
|
35269
35318
|
}
|
|
35270
|
-
function
|
|
35319
|
+
function deploymentEndpoint(projectRef, deploymentId) {
|
|
35271
35320
|
if (!PROJECT_REF_PATTERN3.test(projectRef))
|
|
35272
35321
|
throw new Error("'ref' is invalid for frontend releases");
|
|
35273
35322
|
if (!DEPLOYMENT_ID_PATTERN.test(deploymentId))
|
|
35274
35323
|
throw new Error("'id' is invalid for frontend releases");
|
|
35275
|
-
return `/v1/projects/${encodeURIComponent(projectRef)}/frontend/deployments/${encodeURIComponent(deploymentId)}
|
|
35324
|
+
return `/v1/projects/${encodeURIComponent(projectRef)}/frontend/deployments/${encodeURIComponent(deploymentId)}`;
|
|
35325
|
+
}
|
|
35326
|
+
function releaseEndpoint(projectRef, deploymentId) {
|
|
35327
|
+
return `${deploymentEndpoint(projectRef, deploymentId)}/releases`;
|
|
35276
35328
|
}
|
|
35277
35329
|
function releasePath(projectRef, deploymentId, releaseId) {
|
|
35278
35330
|
if (!RELEASE_ID_PATTERN.test(releaseId))
|
|
@@ -35477,16 +35529,15 @@ function activationReceipt(candidate, input) {
|
|
|
35477
35529
|
return release;
|
|
35478
35530
|
}
|
|
35479
35531
|
async function activeReleaseReadback(http, identity) {
|
|
35480
|
-
const
|
|
35481
|
-
|
|
35482
|
-
|
|
35483
|
-
|
|
35484
|
-
|
|
35485
|
-
|
|
35486
|
-
|
|
35487
|
-
|
|
35488
|
-
|
|
35489
|
-
return { release: null, status: inventoryRead.status };
|
|
35532
|
+
const snapshot = await readActiveFrontendRelease(http, identity.projectRef, identity.deploymentId);
|
|
35533
|
+
if ("failure" in snapshot)
|
|
35534
|
+
return { release: null, status: snapshot.status };
|
|
35535
|
+
const inventory = snapshot.inventory;
|
|
35536
|
+
if (inventory.active_release_id !== identity.releaseId || inventory.active_activation_id !== identity.mutationId) {
|
|
35537
|
+
return { release: null, status: 200 };
|
|
35538
|
+
}
|
|
35539
|
+
if (snapshot.exactReadback) {
|
|
35540
|
+
return { release: inventory.releases[0] ?? null, status: 200 };
|
|
35490
35541
|
}
|
|
35491
35542
|
const releaseRead = await http.get(releasePath(identity.projectRef, identity.deploymentId, identity.releaseId), { maxJsonBytes: RESPONSE_MAX_BYTES });
|
|
35492
35543
|
const release = releaseRead.ok ? releaseEnvelope(releaseRead.data, {
|
|
@@ -35496,6 +35547,100 @@ async function activeReleaseReadback(http, identity) {
|
|
|
35496
35547
|
}) : null;
|
|
35497
35548
|
return { release, status: releaseRead.status };
|
|
35498
35549
|
}
|
|
35550
|
+
async function readActiveFrontendRelease(http, projectRef, deploymentId) {
|
|
35551
|
+
const response = await http.get(`${deploymentEndpoint(projectRef, deploymentId)}/active-release`, {
|
|
35552
|
+
maxJsonBytes: RESPONSE_MAX_BYTES
|
|
35553
|
+
});
|
|
35554
|
+
const inventory = response.ok && response.status === 200 ? releaseInventory(response.data, { projectRef, deploymentId }) : null;
|
|
35555
|
+
if (inventory) {
|
|
35556
|
+
if (inventory.next_cursor !== null || inventory.releases.length !== (inventory.active_release_id === null ? 0 : 1) || inventory.releases[0] && inventory.releases[0].release_id !== inventory.active_release_id) {
|
|
35557
|
+
return { failure: releaseReadFailure("frontend.get_active_release", response), status: response.status };
|
|
35558
|
+
}
|
|
35559
|
+
return { inventory, exactReadback: false };
|
|
35560
|
+
}
|
|
35561
|
+
if (response.ok || response.status !== 404) {
|
|
35562
|
+
return { failure: releaseReadFailure("frontend.get_active_release", response), status: response.status };
|
|
35563
|
+
}
|
|
35564
|
+
const historyResponse = await http.get(`${releaseEndpoint(projectRef, deploymentId)}?limit=1`, {
|
|
35565
|
+
maxJsonBytes: RESPONSE_MAX_BYTES
|
|
35566
|
+
});
|
|
35567
|
+
const history = historyResponse.ok && historyResponse.status === 200 ? releaseInventory(historyResponse.data, { projectRef, deploymentId }) : null;
|
|
35568
|
+
if (!history || history.releases.length > 1) {
|
|
35569
|
+
return {
|
|
35570
|
+
failure: releaseReadFailure("frontend.get_active_release", historyResponse),
|
|
35571
|
+
status: historyResponse.status
|
|
35572
|
+
};
|
|
35573
|
+
}
|
|
35574
|
+
if (history.active_release_id === null) {
|
|
35575
|
+
return { inventory: { ...history, releases: [], next_cursor: null }, exactReadback: false };
|
|
35576
|
+
}
|
|
35577
|
+
const exactResponse = await http.get(releasePath(projectRef, deploymentId, history.active_release_id), {
|
|
35578
|
+
maxJsonBytes: RESPONSE_MAX_BYTES
|
|
35579
|
+
});
|
|
35580
|
+
const release = exactResponse.ok && exactResponse.status === 200 ? releaseEnvelope(exactResponse.data, { projectRef, deploymentId, releaseId: history.active_release_id }) : null;
|
|
35581
|
+
if (!release) {
|
|
35582
|
+
return {
|
|
35583
|
+
failure: releaseReadFailure("frontend.get_active_release", exactResponse),
|
|
35584
|
+
status: exactResponse.status
|
|
35585
|
+
};
|
|
35586
|
+
}
|
|
35587
|
+
return { inventory: { ...history, releases: [release], next_cursor: null }, exactReadback: true };
|
|
35588
|
+
}
|
|
35589
|
+
async function getActiveFrontendRelease(http, projectRef, deploymentId) {
|
|
35590
|
+
const snapshot = await readActiveFrontendRelease(http, projectRef, deploymentId);
|
|
35591
|
+
return "failure" in snapshot ? snapshot.failure : toolResponse(snapshot.inventory);
|
|
35592
|
+
}
|
|
35593
|
+
async function rollbackFrontendRelease(http, projectRef, deploymentId, releaseId) {
|
|
35594
|
+
if (!RELEASE_ID_PATTERN.test(releaseId))
|
|
35595
|
+
throw new Error("'release_id' must be a SHA-256 digest");
|
|
35596
|
+
const authority = await readActiveFrontendRelease(http, projectRef, deploymentId);
|
|
35597
|
+
if ("failure" in authority)
|
|
35598
|
+
return authority.failure;
|
|
35599
|
+
const inventory = authority.inventory;
|
|
35600
|
+
if (inventory.active_release_id === releaseId) {
|
|
35601
|
+
return toolResponse({
|
|
35602
|
+
project_ref: projectRef,
|
|
35603
|
+
deployment_id: deploymentId,
|
|
35604
|
+
active_release_id: releaseId,
|
|
35605
|
+
activation_id: inventory.active_activation_id,
|
|
35606
|
+
unchanged: true
|
|
35607
|
+
});
|
|
35608
|
+
}
|
|
35609
|
+
if (inventory.active_release_id === null || inventory.active_activation_id === null) {
|
|
35610
|
+
return releaseFailure("frontend.rollback", "NO_ACTIVE_RELEASE", 409);
|
|
35611
|
+
}
|
|
35612
|
+
const target = await getFrontendRelease(http, projectRef, deploymentId, releaseId);
|
|
35613
|
+
if (target.isError)
|
|
35614
|
+
return target;
|
|
35615
|
+
const mutationId = crypto.randomUUID();
|
|
35616
|
+
const activation = await activateFrontendRelease(http, {
|
|
35617
|
+
projectRef,
|
|
35618
|
+
deploymentId,
|
|
35619
|
+
releaseId,
|
|
35620
|
+
expectedActiveReleaseId: inventory.active_release_id,
|
|
35621
|
+
expectedActivationId: inventory.active_activation_id,
|
|
35622
|
+
mutationId
|
|
35623
|
+
});
|
|
35624
|
+
if (activation.isError) {
|
|
35625
|
+
return {
|
|
35626
|
+
...toolResponse({
|
|
35627
|
+
...JSON.parse(activation.content[0].text),
|
|
35628
|
+
operation: "frontend.rollback",
|
|
35629
|
+
mutation_id: mutationId,
|
|
35630
|
+
target_release_id: releaseId,
|
|
35631
|
+
expected_active_release_id: inventory.active_release_id,
|
|
35632
|
+
expected_activation_id: inventory.active_activation_id
|
|
35633
|
+
}),
|
|
35634
|
+
isError: true
|
|
35635
|
+
};
|
|
35636
|
+
}
|
|
35637
|
+
return toolResponse({
|
|
35638
|
+
...JSON.parse(activation.content[0].text),
|
|
35639
|
+
rollback: true,
|
|
35640
|
+
previous_release_id: inventory.active_release_id,
|
|
35641
|
+
previous_activation_id: inventory.active_activation_id
|
|
35642
|
+
});
|
|
35643
|
+
}
|
|
35499
35644
|
async function activateFrontendRelease(http, input) {
|
|
35500
35645
|
const mutationId = input.mutationId;
|
|
35501
35646
|
if (!MUTATION_ID_PATTERN.test(mutationId))
|
|
@@ -35561,17 +35706,19 @@ async function activationReadback(http, input, mutationId, activationStatus) {
|
|
|
35561
35706
|
// src/shared/tools/frontend-tools.ts
|
|
35562
35707
|
function registerFrontendTools(server, http) {
|
|
35563
35708
|
server.tool("frontend", `Immutable prebuilt frontend release control.
|
|
35564
|
-
Actions: list_releases, get_release, upload_release, activate_release`, {
|
|
35709
|
+
Actions: list_releases, get_active_release, get_release, upload_release, activate_release, rollback`, {
|
|
35565
35710
|
action: withDescription(stringEnum([
|
|
35566
35711
|
"list_releases",
|
|
35712
|
+
"get_active_release",
|
|
35567
35713
|
"get_release",
|
|
35568
35714
|
"upload_release",
|
|
35569
|
-
"activate_release"
|
|
35715
|
+
"activate_release",
|
|
35716
|
+
"rollback"
|
|
35570
35717
|
]), "Action"),
|
|
35571
35718
|
ref: optional(String4(), "Project ref"),
|
|
35572
35719
|
id: optional(String4(), "Deployment ID"),
|
|
35573
35720
|
zip_path: optional(String4(), "[upload_release] Local ZIP file path"),
|
|
35574
|
-
release_id: optional(String4(), "[get_release/activate_release] SHA-256 release ID"),
|
|
35721
|
+
release_id: optional(String4(), "[get_release/activate_release/rollback] SHA-256 release ID"),
|
|
35575
35722
|
expected_active_release_id: optional(String4(), "[activate_release] Current release SHA-256 or absent"),
|
|
35576
35723
|
expected_activation_id: optional(String4(), "[activate_release] Current activation UUIDv4 or absent"),
|
|
35577
35724
|
mutation_id: optional(String4(), "[activate_release] Required retry-stable UUIDv4"),
|
|
@@ -35604,6 +35751,10 @@ Actions: list_releases, get_release, upload_release, activate_release`, {
|
|
|
35604
35751
|
need("id", id);
|
|
35605
35752
|
need("release_id", release_id);
|
|
35606
35753
|
return getFrontendRelease(http, ref, id, release_id);
|
|
35754
|
+
case "get_active_release":
|
|
35755
|
+
need("ref", ref);
|
|
35756
|
+
need("id", id);
|
|
35757
|
+
return getActiveFrontendRelease(http, ref, id);
|
|
35607
35758
|
case "upload_release":
|
|
35608
35759
|
need("ref", ref);
|
|
35609
35760
|
need("id", id);
|
|
@@ -35624,6 +35775,11 @@ Actions: list_releases, get_release, upload_release, activate_release`, {
|
|
|
35624
35775
|
expectedActivationId: expected_activation_id,
|
|
35625
35776
|
mutationId: mutation_id
|
|
35626
35777
|
});
|
|
35778
|
+
case "rollback":
|
|
35779
|
+
need("ref", ref);
|
|
35780
|
+
need("id", id);
|
|
35781
|
+
need("release_id", release_id);
|
|
35782
|
+
return rollbackFrontendRelease(http, ref, id, release_id);
|
|
35627
35783
|
default:
|
|
35628
35784
|
throw new Error("Unknown immutable frontend release action");
|
|
35629
35785
|
}
|
|
@@ -35632,7 +35788,7 @@ Actions: list_releases, get_release, upload_release, activate_release`, {
|
|
|
35632
35788
|
// package.json
|
|
35633
35789
|
var package_default = {
|
|
35634
35790
|
name: "@supacloud/admin",
|
|
35635
|
-
version: "0.
|
|
35791
|
+
version: "0.25.0",
|
|
35636
35792
|
description: "Platform administration CLI for SupaCloud operators",
|
|
35637
35793
|
type: "module",
|
|
35638
35794
|
main: "./dist/index.js",
|
|
@@ -35665,7 +35821,7 @@ var package_default = {
|
|
|
35665
35821
|
},
|
|
35666
35822
|
dependencies: {
|
|
35667
35823
|
typebox: "1.3.34",
|
|
35668
|
-
"@supacloud/cli": "0.
|
|
35824
|
+
"@supacloud/cli": "0.76.0",
|
|
35669
35825
|
ssh2: "^1.17.0"
|
|
35670
35826
|
},
|
|
35671
35827
|
devDependencies: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supacloud/admin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "Platform administration CLI for SupaCloud operators",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"typebox": "1.3.34",
|
|
36
|
-
"@supacloud/cli": "0.
|
|
36
|
+
"@supacloud/cli": "0.76.0",
|
|
37
37
|
"ssh2": "^1.17.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|