@supacloud/admin 0.14.3 → 0.15.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/dist/index.js +153 -9
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -25791,6 +25791,8 @@ var ACTION_POLICY = {
|
|
|
25791
25791
|
read: [
|
|
25792
25792
|
"list",
|
|
25793
25793
|
"get",
|
|
25794
|
+
"endpoints",
|
|
25795
|
+
"list_endpoints",
|
|
25794
25796
|
"settings",
|
|
25795
25797
|
"api_keys",
|
|
25796
25798
|
"health",
|
|
@@ -25900,7 +25902,7 @@ function requestedProjectRef(moduleName, action, args) {
|
|
|
25900
25902
|
if (moduleName === "ssh")
|
|
25901
25903
|
return sshRequestedProjectRef(action, args);
|
|
25902
25904
|
if (moduleName === "project") {
|
|
25903
|
-
return ["list", "create"].includes(action) ? null : stringArgument(args, "ref");
|
|
25905
|
+
return ["list", "list_endpoints", "create"].includes(action) ? null : stringArgument(args, "ref");
|
|
25904
25906
|
}
|
|
25905
25907
|
if (moduleName === "platform") {
|
|
25906
25908
|
return PLATFORM_PROJECT_REF_ACTIONS.has(action) ? stringArgument(args, "ref") : null;
|
|
@@ -30011,7 +30013,7 @@ async function cleanedCredentialState(cleanup, sanitized) {
|
|
|
30011
30013
|
return sanitized ? "absent" : "unknown";
|
|
30012
30014
|
}
|
|
30013
30015
|
async function removeFailedEnvFile(cleanup) {
|
|
30014
|
-
const { directoryHandle,
|
|
30016
|
+
const { directoryHandle, openFile } = cleanup;
|
|
30015
30017
|
if (!openFile) {
|
|
30016
30018
|
try {
|
|
30017
30019
|
await directoryHandle.close();
|
|
@@ -30427,6 +30429,133 @@ function projectGetRead(response, expectedRef) {
|
|
|
30427
30429
|
const project = projectDetails(response.data, expectedRef);
|
|
30428
30430
|
return project ? successfulResult(project) : failedResult("Invalid project response");
|
|
30429
30431
|
}
|
|
30432
|
+
// ../cli/src/shared/tools/project-endpoint-read.ts
|
|
30433
|
+
var PROJECT_ENDPOINT_RESPONSE_MAX_BYTES = 256 * 1024;
|
|
30434
|
+
var PROJECT_ENDPOINT_LIST_RESPONSE_MAX_BYTES = 1024 * 1024;
|
|
30435
|
+
var PROJECT_REF_PATTERN2 = /^[a-z0-9-]{1,20}$/;
|
|
30436
|
+
var PROJECT_ENDPOINTS_SCHEMA = "supacloud.project-endpoints.v1";
|
|
30437
|
+
var PROJECT_ENDPOINT_SOURCES = new Set([
|
|
30438
|
+
"explicit_api_domain",
|
|
30439
|
+
"explicit_auth_domain",
|
|
30440
|
+
"explicit_studio_domain",
|
|
30441
|
+
"custom_domain",
|
|
30442
|
+
"derived_api_domain",
|
|
30443
|
+
"generated"
|
|
30444
|
+
]);
|
|
30445
|
+
var ROOT_KEYS = new Set(["schema", "project_ref", "endpoints"]);
|
|
30446
|
+
var ENDPOINTS_KEYS = new Set(["api", "auth", "studio"]);
|
|
30447
|
+
var ENDPOINT_KEYS = new Set(["origin", "host", "scheme", "source", "aliases"]);
|
|
30448
|
+
var MAX_PROJECTS2 = 1e4;
|
|
30449
|
+
var MAX_ALIASES = 64;
|
|
30450
|
+
function plainRecord2(candidate) {
|
|
30451
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate))
|
|
30452
|
+
return null;
|
|
30453
|
+
const prototype = Object.getPrototypeOf(candidate);
|
|
30454
|
+
return prototype === Object.prototype || prototype === null ? candidate : null;
|
|
30455
|
+
}
|
|
30456
|
+
function hasOnlyKeys2(record, allowedKeys) {
|
|
30457
|
+
return Object.keys(record).every((key) => allowedKeys.has(key));
|
|
30458
|
+
}
|
|
30459
|
+
function boundedText2(candidate, maxLength) {
|
|
30460
|
+
return typeof candidate === "string" && candidate.length > 0 && candidate.length <= maxLength && !/[\u0000-\u001f\u007f]/u.test(candidate) ? candidate : null;
|
|
30461
|
+
}
|
|
30462
|
+
function canonicalHost(candidate, scheme) {
|
|
30463
|
+
const host = boundedText2(candidate, 255);
|
|
30464
|
+
if (!host)
|
|
30465
|
+
return null;
|
|
30466
|
+
try {
|
|
30467
|
+
const parsed = new URL(`${scheme}://${host}`);
|
|
30468
|
+
return parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash || parsed.host !== host ? null : host;
|
|
30469
|
+
} catch {
|
|
30470
|
+
return null;
|
|
30471
|
+
}
|
|
30472
|
+
}
|
|
30473
|
+
function projectEndpoint2(candidate) {
|
|
30474
|
+
const endpoint = plainRecord2(candidate);
|
|
30475
|
+
if (!endpoint || !hasOnlyKeys2(endpoint, ENDPOINT_KEYS))
|
|
30476
|
+
return null;
|
|
30477
|
+
const scheme = endpoint.scheme === "http" || endpoint.scheme === "https" ? endpoint.scheme : null;
|
|
30478
|
+
const origin = boundedText2(endpoint.origin, 2048);
|
|
30479
|
+
const source = boundedText2(endpoint.source, 64);
|
|
30480
|
+
if (!scheme || !origin || !source || !PROJECT_ENDPOINT_SOURCES.has(source))
|
|
30481
|
+
return null;
|
|
30482
|
+
let parsedOrigin;
|
|
30483
|
+
try {
|
|
30484
|
+
parsedOrigin = new URL(origin);
|
|
30485
|
+
} catch {
|
|
30486
|
+
return null;
|
|
30487
|
+
}
|
|
30488
|
+
if (parsedOrigin.protocol !== `${scheme}:` || parsedOrigin.origin !== origin || parsedOrigin.username || parsedOrigin.password || parsedOrigin.pathname !== "/" || parsedOrigin.search || parsedOrigin.hash)
|
|
30489
|
+
return null;
|
|
30490
|
+
const host = canonicalHost(endpoint.host, scheme);
|
|
30491
|
+
if (!host || host !== parsedOrigin.host || !Array.isArray(endpoint.aliases) || endpoint.aliases.length > MAX_ALIASES)
|
|
30492
|
+
return null;
|
|
30493
|
+
const aliases = [];
|
|
30494
|
+
const seenAliases = new Set;
|
|
30495
|
+
for (const aliasCandidate of endpoint.aliases) {
|
|
30496
|
+
const alias = canonicalHost(aliasCandidate, scheme);
|
|
30497
|
+
if (!alias || alias === host || seenAliases.has(alias))
|
|
30498
|
+
return null;
|
|
30499
|
+
seenAliases.add(alias);
|
|
30500
|
+
aliases.push(alias);
|
|
30501
|
+
}
|
|
30502
|
+
return { origin, host, scheme, source, aliases };
|
|
30503
|
+
}
|
|
30504
|
+
function projectEndpointProjection(candidate) {
|
|
30505
|
+
const projection = plainRecord2(candidate);
|
|
30506
|
+
if (!projection || !hasOnlyKeys2(projection, ROOT_KEYS) || projection.schema !== PROJECT_ENDPOINTS_SCHEMA || typeof projection.project_ref !== "string" || !PROJECT_REF_PATTERN2.test(projection.project_ref))
|
|
30507
|
+
return null;
|
|
30508
|
+
const endpoints = plainRecord2(projection.endpoints);
|
|
30509
|
+
if (!endpoints || !hasOnlyKeys2(endpoints, ENDPOINTS_KEYS))
|
|
30510
|
+
return null;
|
|
30511
|
+
const api = projectEndpoint2(endpoints.api);
|
|
30512
|
+
const auth = projectEndpoint2(endpoints.auth);
|
|
30513
|
+
const studio = projectEndpoint2(endpoints.studio);
|
|
30514
|
+
return api && auth && studio ? {
|
|
30515
|
+
schema: PROJECT_ENDPOINTS_SCHEMA,
|
|
30516
|
+
project_ref: projection.project_ref,
|
|
30517
|
+
endpoints: { api, auth, studio }
|
|
30518
|
+
} : null;
|
|
30519
|
+
}
|
|
30520
|
+
function validHttpStatus2(status) {
|
|
30521
|
+
return Number.isSafeInteger(status) && status >= 100 && status <= 599;
|
|
30522
|
+
}
|
|
30523
|
+
function successfulResponse2(response) {
|
|
30524
|
+
return response.ok === true && validHttpStatus2(response.status) && response.status >= 200 && response.status <= 299;
|
|
30525
|
+
}
|
|
30526
|
+
function failedResult2(message) {
|
|
30527
|
+
return { text: `❌ ${message}`, isError: true };
|
|
30528
|
+
}
|
|
30529
|
+
function failedHttpResult2(label, status) {
|
|
30530
|
+
return failedResult2(validHttpStatus2(status) ? `${label} request failed (${status})` : `${label} request failed`);
|
|
30531
|
+
}
|
|
30532
|
+
function successfulResult2(payload) {
|
|
30533
|
+
return { text: JSON.stringify(payload, null, 2), isError: false };
|
|
30534
|
+
}
|
|
30535
|
+
function projectEndpointRead(response, expectedRef) {
|
|
30536
|
+
if (!successfulResponse2(response))
|
|
30537
|
+
return failedHttpResult2("Project endpoints", response.status);
|
|
30538
|
+
const projection = projectEndpointProjection(response.data);
|
|
30539
|
+
return projection && projection.project_ref === expectedRef ? successfulResult2(projection) : failedResult2("Invalid project endpoint response");
|
|
30540
|
+
}
|
|
30541
|
+
function projectEndpointListRead(response) {
|
|
30542
|
+
if (!successfulResponse2(response))
|
|
30543
|
+
return failedHttpResult2("Project endpoint list", response.status);
|
|
30544
|
+
if (!Array.isArray(response.data) || response.data.length > MAX_PROJECTS2) {
|
|
30545
|
+
return failedResult2("Invalid project endpoint list response");
|
|
30546
|
+
}
|
|
30547
|
+
const projections = [];
|
|
30548
|
+
const refs = new Set;
|
|
30549
|
+
for (const candidate of response.data) {
|
|
30550
|
+
const projection = projectEndpointProjection(candidate);
|
|
30551
|
+
if (!projection || refs.has(projection.project_ref)) {
|
|
30552
|
+
return failedResult2("Invalid project endpoint list response");
|
|
30553
|
+
}
|
|
30554
|
+
refs.add(projection.project_ref);
|
|
30555
|
+
projections.push(projection);
|
|
30556
|
+
}
|
|
30557
|
+
return successfulResult2(projections);
|
|
30558
|
+
}
|
|
30430
30559
|
// src/shared/tools/project-runtime-snapshot.ts
|
|
30431
30560
|
var RUNTIME_SNAPSHOT_SCHEMA = "supacloud.runtime-snapshot.v1";
|
|
30432
30561
|
var ATTESTED_REVISION_PATTERN = /^hmac-sha256:[a-f0-9]{64}$/;
|
|
@@ -30914,13 +31043,18 @@ function resolveRef(refFromArgs, defaultRef) {
|
|
|
30914
31043
|
throw new Error("'ref' is required for this action");
|
|
30915
31044
|
return ref;
|
|
30916
31045
|
}
|
|
31046
|
+
function projectEndpointProjectionPath(ref) {
|
|
31047
|
+
return `/v1/projects/${encodeURIComponent(ref)}/endpoint/projection`;
|
|
31048
|
+
}
|
|
30917
31049
|
function registerAdminProjectCliTools(server, http, options = {}) {
|
|
30918
31050
|
const fileOperations = options.projectEnvFileOperations;
|
|
30919
|
-
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", {
|
|
31051
|
+
server.tool("project", "Platform-level project lifecycle management. Actions: list, list_endpoints, create, get, endpoints, delete, pause, restore, restart, settings, update_settings, api_keys, health, logs, tasks, services, runtime_snapshot, service_control", {
|
|
30920
31052
|
action: withDescription(stringEnum([
|
|
30921
31053
|
"list",
|
|
31054
|
+
"list_endpoints",
|
|
30922
31055
|
"create",
|
|
30923
31056
|
"get",
|
|
31057
|
+
"endpoints",
|
|
30924
31058
|
"delete",
|
|
30925
31059
|
"pause",
|
|
30926
31060
|
"restore",
|
|
@@ -30935,7 +31069,7 @@ function registerAdminProjectCliTools(server, http, options = {}) {
|
|
|
30935
31069
|
"runtime_snapshot",
|
|
30936
31070
|
"service_control"
|
|
30937
31071
|
]), "Action to perform"),
|
|
30938
|
-
ref: optional(Type.String(), "[*] Project ref (required for most actions except 'list' and 'create')"),
|
|
31072
|
+
ref: optional(Type.String(), "[*] Project ref (required for most actions except 'list', 'list_endpoints', and 'create')"),
|
|
30939
31073
|
name: optional(Type.String(), "[create] Project name"),
|
|
30940
31074
|
region: optional(Type.String(), "[create] Region (default: local)"),
|
|
30941
31075
|
organization_id: optional(Type.String(), "[create] Organization ID"),
|
|
@@ -30972,6 +31106,10 @@ function registerAdminProjectCliTools(server, http, options = {}) {
|
|
|
30972
31106
|
return projectReadResponse(projectListRead(await http.get("/v1/projects", {
|
|
30973
31107
|
maxResponseBytes: PROJECT_READ_RESPONSE_MAX_BYTES
|
|
30974
31108
|
})));
|
|
31109
|
+
case "list_endpoints":
|
|
31110
|
+
return projectReadResponse(projectEndpointListRead(await http.get("/v1/projects/endpoints", {
|
|
31111
|
+
maxResponseBytes: PROJECT_ENDPOINT_LIST_RESPONSE_MAX_BYTES
|
|
31112
|
+
})));
|
|
30975
31113
|
case "create": {
|
|
30976
31114
|
if (!name)
|
|
30977
31115
|
throw new Error("'name' is required for create");
|
|
@@ -31015,6 +31153,12 @@ function registerAdminProjectCliTools(server, http, options = {}) {
|
|
|
31015
31153
|
maxResponseBytes: PROJECT_READ_RESPONSE_MAX_BYTES
|
|
31016
31154
|
}), resolvedRef));
|
|
31017
31155
|
}
|
|
31156
|
+
case "endpoints": {
|
|
31157
|
+
const resolvedRef = resolveRef(ref);
|
|
31158
|
+
return projectReadResponse(projectEndpointRead(await http.get(projectEndpointProjectionPath(resolvedRef), {
|
|
31159
|
+
maxResponseBytes: PROJECT_ENDPOINT_RESPONSE_MAX_BYTES
|
|
31160
|
+
}), resolvedRef));
|
|
31161
|
+
}
|
|
31018
31162
|
case "delete": {
|
|
31019
31163
|
const resolvedRef = resolveRef(ref);
|
|
31020
31164
|
text = simple(await http.delete(`/v1/projects/${resolvedRef}`), `Project ${resolvedRef} deleted`);
|
|
@@ -31425,7 +31569,7 @@ import { createHash as createHash3 } from "node:crypto";
|
|
|
31425
31569
|
import { constants as fsConstants2 } from "node:fs";
|
|
31426
31570
|
import { open as open2 } from "node:fs/promises";
|
|
31427
31571
|
import { resolve as resolve3 } from "node:path";
|
|
31428
|
-
var
|
|
31572
|
+
var PROJECT_REF_PATTERN3 = /^[A-Za-z0-9_-]{1,20}$/u;
|
|
31429
31573
|
var DEPLOYMENT_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/u;
|
|
31430
31574
|
var RELEASE_ID_PATTERN = /^[0-9a-f]{64}$/u;
|
|
31431
31575
|
var MUTATION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
|
|
@@ -31468,7 +31612,7 @@ function releaseRecord(candidate) {
|
|
|
31468
31612
|
"created_at",
|
|
31469
31613
|
"kind"
|
|
31470
31614
|
];
|
|
31471
|
-
if (!exactKeys(record, keys) || record.schema !== "supacloud.frontend-release.v1" || typeof record.project_ref !== "string" || !
|
|
31615
|
+
if (!exactKeys(record, keys) || record.schema !== "supacloud.frontend-release.v1" || typeof record.project_ref !== "string" || !PROJECT_REF_PATTERN3.test(record.project_ref) || typeof record.deployment_id !== "string" || !DEPLOYMENT_ID_PATTERN.test(record.deployment_id) || typeof record.release_id !== "string" || !RELEASE_ID_PATTERN.test(record.release_id) || record.sha256 !== record.release_id || typeof record.tree_sha256 !== "string" || !RELEASE_ID_PATTERN.test(record.tree_sha256) || !Number.isSafeInteger(record.size_bytes) || Number(record.size_bytes) < 1 || !Number.isSafeInteger(record.file_count) || Number(record.file_count) < 1 || !canonicalTimestamp3(record.created_at) || record.kind !== "prebuilt_static")
|
|
31472
31616
|
return null;
|
|
31473
31617
|
return {
|
|
31474
31618
|
project_ref: record.project_ref,
|
|
@@ -31514,7 +31658,7 @@ function releaseInventory(candidate, expected) {
|
|
|
31514
31658
|
"releases",
|
|
31515
31659
|
"next_cursor"
|
|
31516
31660
|
];
|
|
31517
|
-
if (!exactKeys(record, keys) || typeof record.project_ref !== "string" || !
|
|
31661
|
+
if (!exactKeys(record, keys) || typeof record.project_ref !== "string" || !PROJECT_REF_PATTERN3.test(record.project_ref) || typeof record.deployment_id !== "string" || !DEPLOYMENT_ID_PATTERN.test(record.deployment_id) || !Array.isArray(record.releases))
|
|
31518
31662
|
return null;
|
|
31519
31663
|
const activeReleaseId = nullableIdentity(record.active_release_id, RELEASE_ID_PATTERN);
|
|
31520
31664
|
const activeActivationId = nullableIdentity(record.active_activation_id, MUTATION_ID_PATTERN);
|
|
@@ -31535,7 +31679,7 @@ function releaseInventory(candidate, expected) {
|
|
|
31535
31679
|
};
|
|
31536
31680
|
}
|
|
31537
31681
|
function releaseEndpoint(projectRef, deploymentId) {
|
|
31538
|
-
if (!
|
|
31682
|
+
if (!PROJECT_REF_PATTERN3.test(projectRef))
|
|
31539
31683
|
throw new Error("'ref' is invalid for frontend releases");
|
|
31540
31684
|
if (!DEPLOYMENT_ID_PATTERN.test(deploymentId))
|
|
31541
31685
|
throw new Error("'id' is invalid for frontend releases");
|
|
@@ -31896,7 +32040,7 @@ Actions: list_releases, get_release, upload_release, activate_release`, {
|
|
|
31896
32040
|
// package.json
|
|
31897
32041
|
var package_default = {
|
|
31898
32042
|
name: "@supacloud/admin",
|
|
31899
|
-
version: "0.
|
|
32043
|
+
version: "0.15.1",
|
|
31900
32044
|
description: "Platform administration CLI for SupaCloud operators",
|
|
31901
32045
|
type: "module",
|
|
31902
32046
|
main: "./dist/index.js",
|