@supacloud/admin 0.11.0 → 0.12.1
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 +5 -1
- package/dist/index.js +230 -11
- package/dist/sshcrypto-8m50vnmb.node +0 -0
- package/package.json +1 -1
- package/dist/sshcrypto-vd2k5hq9.node +0 -0
package/README.md
CHANGED
|
@@ -80,6 +80,7 @@ npx @supacloud/admin project create --name my-app --domain example.com \
|
|
|
80
80
|
--env_file /secure/path/.env.project-credentials.test --environment test
|
|
81
81
|
npx @supacloud/admin project list
|
|
82
82
|
npx @supacloud/admin project services --ref abc123
|
|
83
|
+
npx @supacloud/admin project runtime_snapshot --ref abc123
|
|
83
84
|
npx @supacloud/admin project service_control --ref abc123 --service gotrue --service_action stop
|
|
84
85
|
```
|
|
85
86
|
|
|
@@ -88,7 +89,9 @@ npx @supacloud/admin project service_control --ref abc123 --service gotrue --ser
|
|
|
88
89
|
Each component reports `status` as `ok`, `unknown`, or `error`; a failed probe
|
|
89
90
|
never substitutes a guessed version. Binary evidence is bound to the active
|
|
90
91
|
systemd `ExecStart`, and Web Console evidence comes from its component marker
|
|
91
|
-
plus an explicit `tree_sha256` digest.
|
|
92
|
+
plus an explicit `tree_sha256` digest. An `unknown` component remains a valid
|
|
93
|
+
inventory result, while any `error` component makes the CLI exit non-zero after
|
|
94
|
+
printing the structured report.
|
|
92
95
|
|
|
93
96
|
## Verified platform upgrades
|
|
94
97
|
|
|
@@ -167,6 +170,7 @@ Project commands owned by this CLI:
|
|
|
167
170
|
- `project restart`
|
|
168
171
|
- `project update_settings`
|
|
169
172
|
- `project services` — read-only project service inventory
|
|
173
|
+
- `project runtime_snapshot` — strict read-only runtime revision and PostgREST attestation snapshot
|
|
170
174
|
- `project service_control` — constrained project service lifecycle control
|
|
171
175
|
|
|
172
176
|
`project create` never prints project credentials. Pass an absolute
|
package/dist/index.js
CHANGED
|
@@ -4759,7 +4759,7 @@ var require_utils = __commonJS((exports, module) => {
|
|
|
4759
4759
|
|
|
4760
4760
|
// node_modules/ssh2/lib/protocol/crypto/build/Release/sshcrypto.node
|
|
4761
4761
|
var require_sshcrypto = __commonJS((exports, module) => {
|
|
4762
|
-
module.exports = __require("./sshcrypto-
|
|
4762
|
+
module.exports = __require("./sshcrypto-8m50vnmb.node");
|
|
4763
4763
|
});
|
|
4764
4764
|
|
|
4765
4765
|
// node_modules/ssh2/lib/protocol/crypto/poly1305.js
|
|
@@ -25788,7 +25788,17 @@ function resolveSupaCloudContext(env = process.env, cwd = process.cwd(), selecti
|
|
|
25788
25788
|
// src/shared/execution-policy.ts
|
|
25789
25789
|
var ACTION_POLICY = {
|
|
25790
25790
|
project: {
|
|
25791
|
-
read: [
|
|
25791
|
+
read: [
|
|
25792
|
+
"list",
|
|
25793
|
+
"get",
|
|
25794
|
+
"settings",
|
|
25795
|
+
"api_keys",
|
|
25796
|
+
"health",
|
|
25797
|
+
"logs",
|
|
25798
|
+
"tasks",
|
|
25799
|
+
"services",
|
|
25800
|
+
"runtime_snapshot"
|
|
25801
|
+
],
|
|
25792
25802
|
write: ["create", "delete", "pause", "restore", "restart", "update_settings"]
|
|
25793
25803
|
},
|
|
25794
25804
|
platform: {
|
|
@@ -26023,6 +26033,14 @@ function transportFailure(error) {
|
|
|
26023
26033
|
transportError: true
|
|
26024
26034
|
};
|
|
26025
26035
|
}
|
|
26036
|
+
function responseBodyFailure(status) {
|
|
26037
|
+
return {
|
|
26038
|
+
ok: false,
|
|
26039
|
+
status,
|
|
26040
|
+
data: { error: "Invalid Response", code: "INVALID_RESPONSE" },
|
|
26041
|
+
responseError: true
|
|
26042
|
+
};
|
|
26043
|
+
}
|
|
26026
26044
|
function validatedPostTimeout(options) {
|
|
26027
26045
|
const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT;
|
|
26028
26046
|
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > MAX_POST_TIMEOUT_MS) {
|
|
@@ -26039,6 +26057,15 @@ function validatedGetResponseLimit(options) {
|
|
|
26039
26057
|
}
|
|
26040
26058
|
return maxBytes;
|
|
26041
26059
|
}
|
|
26060
|
+
function validatedStrictJsonLimit(options) {
|
|
26061
|
+
const maxBytes = options.maxJsonBytes;
|
|
26062
|
+
if (maxBytes === undefined)
|
|
26063
|
+
return;
|
|
26064
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
|
|
26065
|
+
throw new RangeError("HTTP JSON response byte limit must be a positive safe integer");
|
|
26066
|
+
}
|
|
26067
|
+
return maxBytes;
|
|
26068
|
+
}
|
|
26042
26069
|
function responseExceedsDeclaredLimit(response, maxBytes) {
|
|
26043
26070
|
const contentLength = response.headers.get("content-length");
|
|
26044
26071
|
return contentLength !== null && /^\d+$/u.test(contentLength) && Number(contentLength) > maxBytes;
|
|
@@ -26091,6 +26118,13 @@ async function boundedResponseJson(response, maxBytes) {
|
|
|
26091
26118
|
throw error;
|
|
26092
26119
|
}
|
|
26093
26120
|
}
|
|
26121
|
+
async function strictBoundedResponseJson(response, maxBytes) {
|
|
26122
|
+
const responseBytes = await boundedResponseBytes(response, maxBytes);
|
|
26123
|
+
if (responseBytes === null)
|
|
26124
|
+
throw new Error("HTTP JSON response exceeded its byte limit");
|
|
26125
|
+
const responseText = new TextDecoder("utf-8", { fatal: true }).decode(responseBytes);
|
|
26126
|
+
return JSON.parse(responseText);
|
|
26127
|
+
}
|
|
26094
26128
|
async function fetchWithTimeout(url, options, timeoutMs = DEFAULT_TIMEOUT) {
|
|
26095
26129
|
const controller = new AbortController;
|
|
26096
26130
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -26142,16 +26176,33 @@ class HttpTransport {
|
|
|
26142
26176
|
}
|
|
26143
26177
|
async get(path, options = {}) {
|
|
26144
26178
|
const maxResponseBytes = validatedGetResponseLimit(options);
|
|
26179
|
+
const maxJsonBytes = validatedStrictJsonLimit(options);
|
|
26180
|
+
if (maxResponseBytes !== undefined && maxJsonBytes !== undefined) {
|
|
26181
|
+
throw new RangeError("HTTP response limit options are mutually exclusive");
|
|
26182
|
+
}
|
|
26183
|
+
let response;
|
|
26145
26184
|
try {
|
|
26146
|
-
|
|
26185
|
+
response = await fetchWithRetry(`${this.baseUrl}${path}`, {
|
|
26147
26186
|
method: "GET",
|
|
26148
26187
|
headers: this.headers()
|
|
26149
26188
|
});
|
|
26150
|
-
const data = maxResponseBytes === undefined ? await res.json().catch(() => null) : await boundedResponseJson(res, maxResponseBytes);
|
|
26151
|
-
return { ok: res.ok, status: res.status, data };
|
|
26152
26189
|
} catch (error) {
|
|
26153
26190
|
return transportFailure(error);
|
|
26154
26191
|
}
|
|
26192
|
+
if (maxJsonBytes !== undefined) {
|
|
26193
|
+
try {
|
|
26194
|
+
const data2 = await strictBoundedResponseJson(response, maxJsonBytes);
|
|
26195
|
+
return { ok: response.ok, status: response.status, data: data2 };
|
|
26196
|
+
} catch {
|
|
26197
|
+
return responseBodyFailure(response.status);
|
|
26198
|
+
}
|
|
26199
|
+
}
|
|
26200
|
+
if (maxResponseBytes !== undefined) {
|
|
26201
|
+
const data2 = await boundedResponseJson(response, maxResponseBytes);
|
|
26202
|
+
return { ok: response.ok, status: response.status, data: data2 };
|
|
26203
|
+
}
|
|
26204
|
+
const data = await response.json().catch(() => null);
|
|
26205
|
+
return { ok: response.ok, status: response.status, data };
|
|
26155
26206
|
}
|
|
26156
26207
|
async post(path, body, options) {
|
|
26157
26208
|
const timeoutMs = validatedPostTimeout(options);
|
|
@@ -28673,6 +28724,13 @@ async function platformVersions(ssh) {
|
|
|
28673
28724
|
}
|
|
28674
28725
|
};
|
|
28675
28726
|
}
|
|
28727
|
+
function platformVersionsToolResult(report) {
|
|
28728
|
+
const isError = Object.values(report.components).some((component) => component.status === "error");
|
|
28729
|
+
return {
|
|
28730
|
+
content: [{ type: "text", text: JSON.stringify(report, null, 2) }],
|
|
28731
|
+
...isError ? { isError: true } : {}
|
|
28732
|
+
};
|
|
28733
|
+
}
|
|
28676
28734
|
function registerSshTools(server, ssh) {
|
|
28677
28735
|
server.tool("ssh", `Server management via SSH. Available before & after SupaCloud installation.
|
|
28678
28736
|
Actions: ping, setup, install, upgrade, versions, diagnose, exec, troubleshoot, container_logs, tenant_manage, tenant_list, tenant_inspect, tenant_diagnose, tenant_migrate`, {
|
|
@@ -28859,8 +28917,7 @@ ${upgradeExecution.stdout.slice(-300)}${edgeBoundary}`;
|
|
|
28859
28917
|
break;
|
|
28860
28918
|
}
|
|
28861
28919
|
case "versions": {
|
|
28862
|
-
|
|
28863
|
-
break;
|
|
28920
|
+
return platformVersionsToolResult(await platformVersions(ssh));
|
|
28864
28921
|
}
|
|
28865
28922
|
case "diagnose": {
|
|
28866
28923
|
const cmds = [
|
|
@@ -30003,6 +30060,152 @@ function projectGetRead(response, expectedRef) {
|
|
|
30003
30060
|
const project = projectDetails(response.data, expectedRef);
|
|
30004
30061
|
return project ? successfulResult(project) : failedResult("Invalid project response");
|
|
30005
30062
|
}
|
|
30063
|
+
// src/shared/tools/project-runtime-snapshot.ts
|
|
30064
|
+
var RUNTIME_SNAPSHOT_SCHEMA = "supacloud.runtime-snapshot.v1";
|
|
30065
|
+
var ATTESTED_REVISION_PATTERN = /^hmac-sha256:[a-f0-9]{64}$/;
|
|
30066
|
+
var SAFE_PROJECT_REF2 = /^[a-z0-9-]{1,20}$/;
|
|
30067
|
+
var SNAPSHOT_KEYS = ["schema", "project_ref", "captured_at", "secrets", "postgrest"];
|
|
30068
|
+
var SECRETS_KEYS = [
|
|
30069
|
+
"desired_revision",
|
|
30070
|
+
"loaded_revision",
|
|
30071
|
+
"load_state",
|
|
30072
|
+
"load_source",
|
|
30073
|
+
"matches_desired",
|
|
30074
|
+
"loaded_at"
|
|
30075
|
+
];
|
|
30076
|
+
var POSTGREST_KEYS = [
|
|
30077
|
+
"desired_revision",
|
|
30078
|
+
"loaded_revision",
|
|
30079
|
+
"attestation_state",
|
|
30080
|
+
"matches_desired",
|
|
30081
|
+
"desired",
|
|
30082
|
+
"actual",
|
|
30083
|
+
"health",
|
|
30084
|
+
"port",
|
|
30085
|
+
"unit",
|
|
30086
|
+
"loaded_at"
|
|
30087
|
+
];
|
|
30088
|
+
var SECRET_LOAD_STATES = ["current", "stale", "not_loaded", "unverified", "unreachable"];
|
|
30089
|
+
var SECRET_LOAD_SOURCES = ["management_api", "stale_cache", "file_fallback"];
|
|
30090
|
+
var POSTGREST_ATTESTATION_STATES = [
|
|
30091
|
+
"loaded",
|
|
30092
|
+
"stale",
|
|
30093
|
+
"drifted",
|
|
30094
|
+
"unverified_legacy",
|
|
30095
|
+
"stopped",
|
|
30096
|
+
"unreachable"
|
|
30097
|
+
];
|
|
30098
|
+
var POSTGREST_DESIRED_STATES = ["running", "stopped"];
|
|
30099
|
+
var POSTGREST_ACTUAL_STATES = ["running", "stopped", "starting", "error"];
|
|
30100
|
+
var POSTGREST_HEALTH_STATES = ["healthy", "unhealthy", "unknown"];
|
|
30101
|
+
function isRecord2(candidate) {
|
|
30102
|
+
return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate);
|
|
30103
|
+
}
|
|
30104
|
+
function hasExactKeys(candidate, expectedKeys) {
|
|
30105
|
+
const actualKeys = Object.keys(candidate).sort();
|
|
30106
|
+
const sortedExpectedKeys = [...expectedKeys].sort();
|
|
30107
|
+
return actualKeys.length === sortedExpectedKeys.length && actualKeys.every((key, index) => key === sortedExpectedKeys[index]);
|
|
30108
|
+
}
|
|
30109
|
+
function isEnumMember(candidate, members) {
|
|
30110
|
+
return typeof candidate === "string" && members.includes(candidate);
|
|
30111
|
+
}
|
|
30112
|
+
function isIsoTimestampOrNull(candidate) {
|
|
30113
|
+
if (candidate === null)
|
|
30114
|
+
return true;
|
|
30115
|
+
if (typeof candidate !== "string")
|
|
30116
|
+
return false;
|
|
30117
|
+
const timestamp = Date.parse(candidate);
|
|
30118
|
+
return Number.isFinite(timestamp) && new Date(timestamp).toISOString() === candidate;
|
|
30119
|
+
}
|
|
30120
|
+
function isRevisionOrNull(candidate) {
|
|
30121
|
+
return candidate === null || typeof candidate === "string" && ATTESTED_REVISION_PATTERN.test(candidate);
|
|
30122
|
+
}
|
|
30123
|
+
function isBooleanOrNull(candidate) {
|
|
30124
|
+
return candidate === null || typeof candidate === "boolean";
|
|
30125
|
+
}
|
|
30126
|
+
function isUnloadedSecretsState(candidate) {
|
|
30127
|
+
return candidate.loaded_revision === null && candidate.load_source === null && candidate.matches_desired === null && candidate.loaded_at === null;
|
|
30128
|
+
}
|
|
30129
|
+
function isLoadedSecretsState(candidate) {
|
|
30130
|
+
if (candidate.load_state === "current") {
|
|
30131
|
+
return candidate.loaded_revision === candidate.desired_revision && candidate.load_source === "management_api" && candidate.matches_desired === true && candidate.loaded_at !== null;
|
|
30132
|
+
}
|
|
30133
|
+
return candidate.load_state === "stale" && candidate.loaded_revision !== null && candidate.loaded_revision !== candidate.desired_revision && candidate.load_source === "management_api" && candidate.matches_desired === false && candidate.loaded_at !== null;
|
|
30134
|
+
}
|
|
30135
|
+
function isUnverifiedSecretsState(candidate) {
|
|
30136
|
+
if (candidate.load_state !== "unverified" || candidate.matches_desired !== null || candidate.loaded_at === null)
|
|
30137
|
+
return false;
|
|
30138
|
+
if (candidate.loaded_revision === candidate.desired_revision) {
|
|
30139
|
+
return candidate.load_source === "management_api";
|
|
30140
|
+
}
|
|
30141
|
+
return candidate.loaded_revision === null && candidate.load_source !== null;
|
|
30142
|
+
}
|
|
30143
|
+
function hasValidSecretsState(candidate) {
|
|
30144
|
+
if (candidate.load_state === "not_loaded" || candidate.load_state === "unreachable") {
|
|
30145
|
+
return isUnloadedSecretsState(candidate);
|
|
30146
|
+
}
|
|
30147
|
+
return isLoadedSecretsState(candidate) || isUnverifiedSecretsState(candidate);
|
|
30148
|
+
}
|
|
30149
|
+
function isRuntimeSecretsSnapshot(payload) {
|
|
30150
|
+
if (!isRecord2(payload) || !hasExactKeys(payload, SECRETS_KEYS))
|
|
30151
|
+
return false;
|
|
30152
|
+
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))
|
|
30153
|
+
return false;
|
|
30154
|
+
return hasValidSecretsState(payload);
|
|
30155
|
+
}
|
|
30156
|
+
function hasConsistentRevisionMatch(candidate) {
|
|
30157
|
+
if (candidate.loaded_revision === null)
|
|
30158
|
+
return candidate.matches_desired === null;
|
|
30159
|
+
return candidate.matches_desired === (candidate.loaded_revision === candidate.desired_revision);
|
|
30160
|
+
}
|
|
30161
|
+
function hasActivePostgrestProjection(candidate) {
|
|
30162
|
+
return candidate.actual === "running" && candidate.health === "healthy" || candidate.actual === "error" && candidate.health === "unhealthy";
|
|
30163
|
+
}
|
|
30164
|
+
function hasValidPostgrestState(candidate) {
|
|
30165
|
+
if (!hasConsistentRevisionMatch(candidate))
|
|
30166
|
+
return false;
|
|
30167
|
+
if (candidate.attestation_state === "loaded") {
|
|
30168
|
+
return candidate.matches_desired === true && candidate.actual === "running" && candidate.health === "healthy" && candidate.loaded_at !== null;
|
|
30169
|
+
}
|
|
30170
|
+
if (candidate.attestation_state === "stale") {
|
|
30171
|
+
return candidate.loaded_revision !== null && candidate.matches_desired === false && candidate.loaded_at !== null && hasActivePostgrestProjection(candidate);
|
|
30172
|
+
}
|
|
30173
|
+
if (candidate.attestation_state === "unverified_legacy")
|
|
30174
|
+
return candidate.loaded_revision === null;
|
|
30175
|
+
if (candidate.attestation_state === "stopped") {
|
|
30176
|
+
return candidate.loaded_revision === null && candidate.actual === "stopped" && candidate.health === "unknown" && candidate.loaded_at === null;
|
|
30177
|
+
}
|
|
30178
|
+
if (candidate.attestation_state === "unreachable")
|
|
30179
|
+
return candidate.loaded_revision === null;
|
|
30180
|
+
return candidate.attestation_state === "drifted";
|
|
30181
|
+
}
|
|
30182
|
+
function isPostgrestRuntimeSnapshot(payload, projectRef) {
|
|
30183
|
+
if (!isRecord2(payload) || !hasExactKeys(payload, POSTGREST_KEYS))
|
|
30184
|
+
return false;
|
|
30185
|
+
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))
|
|
30186
|
+
return false;
|
|
30187
|
+
return hasValidPostgrestState(payload);
|
|
30188
|
+
}
|
|
30189
|
+
function sanitizedSnapshot(snapshot) {
|
|
30190
|
+
return {
|
|
30191
|
+
schema: snapshot.schema,
|
|
30192
|
+
project_ref: snapshot.project_ref,
|
|
30193
|
+
captured_at: snapshot.captured_at,
|
|
30194
|
+
secrets: { ...snapshot.secrets },
|
|
30195
|
+
postgrest: { ...snapshot.postgrest }
|
|
30196
|
+
};
|
|
30197
|
+
}
|
|
30198
|
+
function hasCausalLoadTimestamps(snapshot) {
|
|
30199
|
+
const capturedAt = Date.parse(snapshot.captured_at);
|
|
30200
|
+
return [snapshot.secrets.loaded_at, snapshot.postgrest.loaded_at].every((loadedAt) => loadedAt === null || Date.parse(loadedAt) <= capturedAt);
|
|
30201
|
+
}
|
|
30202
|
+
function parseProjectRuntimeSnapshot(payload, requestedProjectRef2) {
|
|
30203
|
+
if (!SAFE_PROJECT_REF2.test(requestedProjectRef2) || !isRecord2(payload) || !hasExactKeys(payload, SNAPSHOT_KEYS) || payload.schema !== RUNTIME_SNAPSHOT_SCHEMA || payload.project_ref !== requestedProjectRef2 || !isIsoTimestampOrNull(payload.captured_at) || payload.captured_at === null || !isRuntimeSecretsSnapshot(payload.secrets) || !isPostgrestRuntimeSnapshot(payload.postgrest, requestedProjectRef2))
|
|
30204
|
+
return null;
|
|
30205
|
+
const snapshot = payload;
|
|
30206
|
+
return hasCausalLoadTimestamps(snapshot) ? sanitizedSnapshot(snapshot) : null;
|
|
30207
|
+
}
|
|
30208
|
+
|
|
30006
30209
|
// src/shared/tools/project-cli-tools.ts
|
|
30007
30210
|
var PROJECT_SERVICE_NAMES = [
|
|
30008
30211
|
"postgrest",
|
|
@@ -30034,9 +30237,10 @@ var STUDIO_PROJECT_SERVICE_STATUSES = [
|
|
|
30034
30237
|
];
|
|
30035
30238
|
var AUTH_RUNTIME_MANAGED_BY_OWNER = "AUTH_RUNTIME_MANAGED_BY_OWNER";
|
|
30036
30239
|
var AUTH_SERVICE_HOST_SUFFIX = "-auth";
|
|
30037
|
-
var
|
|
30240
|
+
var SAFE_PROJECT_REF3 = /^[a-z0-9-]{1,20}$/;
|
|
30038
30241
|
var SAFE_AUTHORITY_PROJECT_REF = /^[A-Za-z0-9_-]{1,20}$/;
|
|
30039
30242
|
var MAX_SERVICE_CONTROL_MESSAGE_LENGTH = 256;
|
|
30243
|
+
var MAX_RUNTIME_SNAPSHOT_BYTES = 64 * 1024;
|
|
30040
30244
|
var RELEASE_CONTROL_RESPONSE_SCHEMA = "supacloud.cli.release-control.v1";
|
|
30041
30245
|
var PROJECT_CREATE_OPERATION = "project.create";
|
|
30042
30246
|
var PROJECT_ENVIRONMENTS = ["test", "production"];
|
|
@@ -30250,7 +30454,7 @@ function projectServiceStatusOutput(status) {
|
|
|
30250
30454
|
function projectServicesResponse(projectRef, response) {
|
|
30251
30455
|
if (!response.ok)
|
|
30252
30456
|
return failedProjectServiceHttpResponse(response);
|
|
30253
|
-
if (!
|
|
30457
|
+
if (!SAFE_PROJECT_REF3.test(projectRef) || !Array.isArray(response.data) || response.data.length !== 5) {
|
|
30254
30458
|
return failedProjectServiceResponse("Project service inventory response is invalid");
|
|
30255
30459
|
}
|
|
30256
30460
|
if (!response.data.every((service) => isProjectServiceStatus(service, projectRef))) {
|
|
@@ -30263,6 +30467,15 @@ function projectServicesResponse(projectRef, response) {
|
|
|
30263
30467
|
const services = response.data.map(projectServiceStatusOutput);
|
|
30264
30468
|
return projectToolResponse(JSON.stringify({ project_ref: projectRef, services }, null, 2));
|
|
30265
30469
|
}
|
|
30470
|
+
function projectRuntimeSnapshotResponse(projectRef, response) {
|
|
30471
|
+
if (response.responseError) {
|
|
30472
|
+
return failedProjectServiceResponse("Project runtime snapshot response is invalid");
|
|
30473
|
+
}
|
|
30474
|
+
if (!response.ok)
|
|
30475
|
+
return failedProjectServiceHttpResponse(response);
|
|
30476
|
+
const snapshot = parseProjectRuntimeSnapshot(response.data, projectRef);
|
|
30477
|
+
return snapshot ? projectToolResponse(JSON.stringify(snapshot, null, 2)) : failedProjectServiceResponse("Project runtime snapshot response is invalid");
|
|
30478
|
+
}
|
|
30266
30479
|
function supportsProjectServiceAction(service, action) {
|
|
30267
30480
|
return SUPPORTED_PROJECT_SERVICE_ACTIONS[service].includes(action);
|
|
30268
30481
|
}
|
|
@@ -30336,7 +30549,7 @@ function resolveRef(refFromArgs, defaultRef) {
|
|
|
30336
30549
|
}
|
|
30337
30550
|
function registerAdminProjectCliTools(server, http, options = {}) {
|
|
30338
30551
|
const fileOperations = options.projectEnvFileOperations;
|
|
30339
|
-
server.tool("project", "Platform-level project lifecycle management. Actions: list, create, get, delete, pause, restore, restart, settings, update_settings, api_keys, health, logs, tasks, services, service_control", {
|
|
30552
|
+
server.tool("project", "Platform-level project lifecycle management. Actions: list, create, get, delete, pause, restore, restart, settings, update_settings, api_keys, health, logs, tasks, services, runtime_snapshot, service_control", {
|
|
30340
30553
|
action: withDescription(stringEnum([
|
|
30341
30554
|
"list",
|
|
30342
30555
|
"create",
|
|
@@ -30352,6 +30565,7 @@ function registerAdminProjectCliTools(server, http, options = {}) {
|
|
|
30352
30565
|
"logs",
|
|
30353
30566
|
"tasks",
|
|
30354
30567
|
"services",
|
|
30568
|
+
"runtime_snapshot",
|
|
30355
30569
|
"service_control"
|
|
30356
30570
|
]), "Action to perform"),
|
|
30357
30571
|
ref: optional(Type.String(), "[*] Project ref (required for most actions except 'list' and 'create')"),
|
|
@@ -30485,6 +30699,10 @@ function registerAdminProjectCliTools(server, http, options = {}) {
|
|
|
30485
30699
|
const resolvedRef = resolveRef(ref);
|
|
30486
30700
|
return projectServicesResponse(resolvedRef, await http.get(`/v1/projects/${encodeURIComponent(resolvedRef)}/services`));
|
|
30487
30701
|
}
|
|
30702
|
+
case "runtime_snapshot": {
|
|
30703
|
+
const resolvedRef = resolveRef(ref);
|
|
30704
|
+
return projectRuntimeSnapshotResponse(resolvedRef, await http.get(`/v1/projects/${encodeURIComponent(resolvedRef)}/runtime-snapshot`, { maxJsonBytes: MAX_RUNTIME_SNAPSHOT_BYTES }));
|
|
30705
|
+
}
|
|
30488
30706
|
case "service_control": {
|
|
30489
30707
|
const resolvedRef = resolveRef(ref);
|
|
30490
30708
|
if (!service)
|
|
@@ -30837,7 +31055,7 @@ Actions: routes, upsert_route, update_route, delete_route, config, get_certifica
|
|
|
30837
31055
|
// package.json
|
|
30838
31056
|
var package_default = {
|
|
30839
31057
|
name: "@supacloud/admin",
|
|
30840
|
-
version: "0.
|
|
31058
|
+
version: "0.12.1",
|
|
30841
31059
|
description: "Platform administration CLI for SupaCloud operators",
|
|
30842
31060
|
type: "module",
|
|
30843
31061
|
main: "./dist/index.js",
|
|
@@ -30969,6 +31187,7 @@ EXAMPLES
|
|
|
30969
31187
|
supacloud-admin project create --name my-app --domain example.com --env_file /secure/path/.env.project-credentials.test --environment test
|
|
30970
31188
|
supacloud-admin project list
|
|
30971
31189
|
supacloud-admin project services --ref abc123
|
|
31190
|
+
supacloud-admin project runtime_snapshot --ref abc123
|
|
30972
31191
|
supacloud-admin project service_control --ref abc123 --service gotrue --service_action stop
|
|
30973
31192
|
supacloud-admin platform metrics
|
|
30974
31193
|
supacloud-admin gateway routes --ref abc123
|
|
Binary file
|
package/package.json
CHANGED
|
Binary file
|