@supacloud/admin 0.15.1 → 0.15.3
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 +39 -2
- package/dist/index.js +138 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -124,8 +124,8 @@ external Edge Runtime as a single rollback-capable transaction:
|
|
|
124
124
|
|
|
125
125
|
```bash
|
|
126
126
|
npx @supacloud/admin ssh upgrade \
|
|
127
|
-
--version 0.
|
|
128
|
-
--edge_runtime_version 0.
|
|
127
|
+
--version 0.61.4 \
|
|
128
|
+
--edge_runtime_version 0.18.2 \
|
|
129
129
|
--artifact_transport local \
|
|
130
130
|
--github_proxy direct
|
|
131
131
|
```
|
|
@@ -150,6 +150,43 @@ enabled state. Component upgrades require persisted `EDGE_RUNTIME_MODE=external`
|
|
|
150
150
|
embedded mode is rejected before release artifacts or services are changed.
|
|
151
151
|
Local, remote, and direct server upgrades share one nonblocking host-wide lock.
|
|
152
152
|
|
|
153
|
+
Local artifact upgrades require Management 0.61.4 or newer. After stopping the
|
|
154
|
+
old Management service and before writing runtime secrets or running
|
|
155
|
+
`--init-db`, the target binary verifies that `DATABASE_URL` identifies the
|
|
156
|
+
Management control-plane database, rejects a registered tenant database, and
|
|
157
|
+
creates a verified custom-format PostgreSQL archive. Backups and their private
|
|
158
|
+
receipts are stored under
|
|
159
|
+
`/var/lib/supacloud/backups/control-plane-upgrades/<backup-id>/`; the five most
|
|
160
|
+
recent trusted backups are retained.
|
|
161
|
+
|
|
162
|
+
The inspection transaction remains open while `pg_dump` imports its exported
|
|
163
|
+
snapshot, so a different PostgreSQL server cannot satisfy the dump step. The
|
|
164
|
+
root process reserves the private archive first; the `postgres` identity receives
|
|
165
|
+
only that inherited output descriptor and never gains path access to the
|
|
166
|
+
root-only backup directory.
|
|
167
|
+
|
|
168
|
+
The inspection transaction also keeps its exported snapshot alive until staged
|
|
169
|
+
`--init-db` finishes. The child receives the database fingerprint and snapshot
|
|
170
|
+
only in its process environment, imports that live snapshot, verifies the
|
|
171
|
+
PostgreSQL cluster and database identity, and performs all initialization writes
|
|
172
|
+
inside that same transaction. This keeps the writes on one PostgreSQL backend
|
|
173
|
+
even when `DATABASE_URL` points through a transaction-pooling proxy. A copied
|
|
174
|
+
data directory, promoted standby, restored disk snapshot, or other node cannot
|
|
175
|
+
import the exporter-owned snapshot even when its static fingerprint matches.
|
|
176
|
+
The guard is not persisted or printed. A guard rejection restores the previous
|
|
177
|
+
runtime environment but leaves Management stopped for explicit reconciliation
|
|
178
|
+
instead of reconnecting it to an unverified target.
|
|
179
|
+
|
|
180
|
+
A committed transaction emits exactly one redacted
|
|
181
|
+
`supacloud.control-plane-upgrade-safety.v1` receipt. It contains only the backup
|
|
182
|
+
identifier and directory, byte count, SHA-256 digest, migration candidate
|
|
183
|
+
counts, checkpoint state, and completion time. Admin validates the exact
|
|
184
|
+
receipt schema before deleting the transient
|
|
185
|
+
status and log records. A missing or malformed receipt leaves those records in
|
|
186
|
+
place and requires reconciliation. Restoring a control-plane backup is a
|
|
187
|
+
separate destructive operation and requires an explicit, independently reviewed
|
|
188
|
+
recovery decision; the upgrade command never restores one automatically.
|
|
189
|
+
|
|
153
190
|
Admin observes the remote transaction for up to 30 minutes. Reaching that
|
|
154
191
|
deadline stops only local observation; it does not stop, clean up, or mark the
|
|
155
192
|
remote transaction as failed. The CLI reports the unit, stage, status, log, and
|
package/dist/index.js
CHANGED
|
@@ -26367,7 +26367,7 @@ function quoteShell(shellText) {
|
|
|
26367
26367
|
// src/shared/releases/local-upgrade-bundle.ts
|
|
26368
26368
|
import { spawn } from "node:child_process";
|
|
26369
26369
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
26370
|
-
import { accessSync, chmodSync as chmodSync2, constants as fsConstants, createWriteStream, lstatSync, mkdirSync, mkdtempSync as mkdtempSync2, readFileSync as readFileSync3, readdirSync, rmSync as rmSync2, statSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
26370
|
+
import { accessSync, chmodSync as chmodSync2, constants as fsConstants, createWriteStream, lstatSync, mkdirSync, mkdtempSync as mkdtempSync2, readFileSync as readFileSync3, readdirSync, renameSync, rmSync as rmSync2, statSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
26371
26371
|
import { get } from "node:https";
|
|
26372
26372
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
26373
26373
|
import { basename, delimiter, join } from "node:path";
|
|
@@ -26381,6 +26381,14 @@ var RELEASE_CHECKSUMS_NAME = "SHA256SUMS";
|
|
|
26381
26381
|
var RELEASE_REPOSITORY = "vibeunion/supacloud";
|
|
26382
26382
|
var RELEASE_SOURCE_REF = "refs/heads/main";
|
|
26383
26383
|
var RELEASE_SIGNER_WORKFLOW = `${RELEASE_REPOSITORY}/.github/workflows/release-please.yml`;
|
|
26384
|
+
var LEGACY_EDGE_RUNTIME_RELEASE_IDENTITY = {
|
|
26385
|
+
repository: "zuohuadong/supacloud",
|
|
26386
|
+
workflow: "zuohuadong/supacloud/.github/workflows/release-please.yml",
|
|
26387
|
+
component: "edge-runtime",
|
|
26388
|
+
version: "0.18.2",
|
|
26389
|
+
tag: "edge-runtime-v0.18.2",
|
|
26390
|
+
sourceCommit: "c6a3a87cf4e41e0f3dafadb018dd0c7d5b99b7d9"
|
|
26391
|
+
};
|
|
26384
26392
|
var MEBIBYTE = 1024 * 1024;
|
|
26385
26393
|
var RELEASE_BUNDLE_SIZE_LIMITS = {
|
|
26386
26394
|
manifest: MEBIBYTE,
|
|
@@ -26487,6 +26495,16 @@ function parseArtifacts(candidate, component) {
|
|
|
26487
26495
|
throw new Error(`Release manifest artifact ${oversized.name} exceeds its size limit`);
|
|
26488
26496
|
return artifacts;
|
|
26489
26497
|
}
|
|
26498
|
+
function parseIdentity(manifest, source, release) {
|
|
26499
|
+
const current = manifest.repository === RELEASE_REPOSITORY && manifest.workflow === RELEASE_SIGNER_WORKFLOW;
|
|
26500
|
+
const legacyEdgeRuntime = manifest.repository === LEGACY_EDGE_RUNTIME_RELEASE_IDENTITY.repository && manifest.workflow === LEGACY_EDGE_RUNTIME_RELEASE_IDENTITY.workflow && release.component === LEGACY_EDGE_RUNTIME_RELEASE_IDENTITY.component && release.version === LEGACY_EDGE_RUNTIME_RELEASE_IDENTITY.version && release.tag === LEGACY_EDGE_RUNTIME_RELEASE_IDENTITY.tag && source.commit === LEGACY_EDGE_RUNTIME_RELEASE_IDENTITY.sourceCommit;
|
|
26501
|
+
if (!current && !legacyEdgeRuntime)
|
|
26502
|
+
throw new Error("Release manifest identity is invalid");
|
|
26503
|
+
return {
|
|
26504
|
+
repository: manifest.repository,
|
|
26505
|
+
workflow: manifest.workflow
|
|
26506
|
+
};
|
|
26507
|
+
}
|
|
26490
26508
|
function parseReleaseManifest(text, expected) {
|
|
26491
26509
|
let candidate;
|
|
26492
26510
|
try {
|
|
@@ -26496,15 +26514,16 @@ function parseReleaseManifest(text, expected) {
|
|
|
26496
26514
|
}
|
|
26497
26515
|
const manifest = manifestObject(candidate, "Release manifest");
|
|
26498
26516
|
assertExactKeys(manifest, ["schemaVersion", "repository", "source", "workflow", "release", "artifacts"], "Release manifest");
|
|
26499
|
-
if (manifest.schemaVersion !== 1
|
|
26517
|
+
if (manifest.schemaVersion !== 1)
|
|
26500
26518
|
throw new Error("Release manifest identity is invalid");
|
|
26501
|
-
|
|
26519
|
+
const source = parseSource(manifest.source);
|
|
26520
|
+
const release = parseRelease(manifest.release, expected);
|
|
26521
|
+
const identity = parseIdentity(manifest, source, release);
|
|
26502
26522
|
return {
|
|
26503
26523
|
schemaVersion: 1,
|
|
26504
|
-
|
|
26505
|
-
source
|
|
26506
|
-
|
|
26507
|
-
release: parseRelease(manifest.release, expected),
|
|
26524
|
+
...identity,
|
|
26525
|
+
source,
|
|
26526
|
+
release,
|
|
26508
26527
|
artifacts: parseArtifacts(manifest.artifacts, expected.component)
|
|
26509
26528
|
};
|
|
26510
26529
|
}
|
|
@@ -26994,11 +27013,11 @@ function githubAttestationVerificationArguments(request) {
|
|
|
26994
27013
|
"--bundle",
|
|
26995
27014
|
request.bundlePath,
|
|
26996
27015
|
"--repo",
|
|
26997
|
-
|
|
27016
|
+
request.manifest.repository,
|
|
26998
27017
|
"--signer-workflow",
|
|
26999
|
-
|
|
27018
|
+
request.manifest.workflow,
|
|
27000
27019
|
"--source-ref",
|
|
27001
|
-
|
|
27020
|
+
request.manifest.source.ref,
|
|
27002
27021
|
"--source-digest",
|
|
27003
27022
|
request.manifest.source.commit,
|
|
27004
27023
|
"--deny-self-hosted-runners",
|
|
@@ -27041,13 +27060,27 @@ async function downloadReleaseMetadata(component, version, directory) {
|
|
|
27041
27060
|
rmSync2(metadataPath, { force: true });
|
|
27042
27061
|
}
|
|
27043
27062
|
}
|
|
27044
|
-
|
|
27063
|
+
function manifestAttestationDownloadUrl(release, manifest, manifestDigest) {
|
|
27064
|
+
if (manifest.repository === RELEASE_REPOSITORY) {
|
|
27065
|
+
return `${ATTESTATIONS_API}/sha256:${manifestDigest}`;
|
|
27066
|
+
}
|
|
27067
|
+
if (manifest.repository === LEGACY_EDGE_RUNTIME_RELEASE_IDENTITY.repository) {
|
|
27068
|
+
return releaseAssetUrl(release, RELEASE_ATTESTATION_NAME);
|
|
27069
|
+
}
|
|
27070
|
+
throw new Error("Release manifest repository is not supported");
|
|
27071
|
+
}
|
|
27072
|
+
async function downloadManifestAttestation(request) {
|
|
27073
|
+
const { release, manifest, manifestPath, destination } = request;
|
|
27045
27074
|
const responsePath = `${destination}.response`;
|
|
27046
27075
|
try {
|
|
27047
|
-
await downloadDirect(
|
|
27048
|
-
|
|
27049
|
-
|
|
27050
|
-
|
|
27076
|
+
await downloadDirect(manifestAttestationDownloadUrl(release, manifest, sha256File(manifestPath)), responsePath, RELEASE_BUNDLE_SIZE_LIMITS.attestation);
|
|
27077
|
+
if (manifest.repository === RELEASE_REPOSITORY) {
|
|
27078
|
+
const bundles = serializeAttestationBundles(parseJsonFile(responsePath, "GitHub attestation response"));
|
|
27079
|
+
writeFileSync2(destination, bundles, { mode: 384, flag: "wx" });
|
|
27080
|
+
chmodSync2(destination, 384);
|
|
27081
|
+
} else {
|
|
27082
|
+
renameSync(responsePath, destination);
|
|
27083
|
+
}
|
|
27051
27084
|
} finally {
|
|
27052
27085
|
rmSync2(responsePath, { force: true });
|
|
27053
27086
|
}
|
|
@@ -27058,7 +27091,7 @@ async function downloadComponent(request) {
|
|
|
27058
27091
|
const attestationPath = directChildPath(request.destination, RELEASE_ATTESTATION_NAME);
|
|
27059
27092
|
await downloadDirect(releaseAssetUrl(release, RELEASE_MANIFEST_NAME), manifestPath, RELEASE_BUNDLE_SIZE_LIMITS.manifest);
|
|
27060
27093
|
const manifest = parseReleaseManifest(readFileSync3(manifestPath, "utf8"), request);
|
|
27061
|
-
await downloadManifestAttestation(manifestPath, attestationPath);
|
|
27094
|
+
await downloadManifestAttestation({ release, manifest, manifestPath, destination: attestationPath });
|
|
27062
27095
|
await verifyManifestAttestation({
|
|
27063
27096
|
artifactPath: manifestPath,
|
|
27064
27097
|
bundlePath: attestationPath,
|
|
@@ -27192,6 +27225,9 @@ var REMOTE_RUN_ROOT = "/var/lib/supacloud/upgrade-runs";
|
|
|
27192
27225
|
var REMOTE_LOG_ROOT = "/var/log/supacloud";
|
|
27193
27226
|
var REMOTE_UPLOAD_ROOT = "/var/tmp";
|
|
27194
27227
|
var REMOTE_COMMAND_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
27228
|
+
var CONTROL_PLANE_BACKUP_ROOT = "/var/lib/supacloud/backups/control-plane-upgrades";
|
|
27229
|
+
var CONTROL_PLANE_SAFETY_PREFIX = "SUPACLOUD_CONTROL_PLANE_UPGRADE_SAFETY=";
|
|
27230
|
+
var MINIMUM_CONTROL_PLANE_SAFETY_VERSION = [0, 61, 4];
|
|
27195
27231
|
var POLL_INTERVAL_MS = 2000;
|
|
27196
27232
|
var STATE_READ_ATTEMPTS = 3;
|
|
27197
27233
|
var REMOTE_STATE_READ_TIMEOUT_MS = 15000;
|
|
@@ -27199,6 +27235,76 @@ var UPGRADE_OBSERVATION_TIMEOUT_MS = 30 * 60000;
|
|
|
27199
27235
|
|
|
27200
27236
|
class RemoteUpgradeReconciliationError extends AggregateError {
|
|
27201
27237
|
}
|
|
27238
|
+
function exactObjectKeys(candidate, expected) {
|
|
27239
|
+
const actual = Object.keys(candidate).sort();
|
|
27240
|
+
return actual.length === expected.length && actual.every((key, index) => key === [...expected].sort()[index]);
|
|
27241
|
+
}
|
|
27242
|
+
function nonNegativeIntegerRecord(candidate, expectedKeys) {
|
|
27243
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate))
|
|
27244
|
+
return false;
|
|
27245
|
+
const record = candidate;
|
|
27246
|
+
return exactObjectKeys(record, expectedKeys) && Object.values(record).every((value) => Number.isSafeInteger(value) && Number(value) >= 0);
|
|
27247
|
+
}
|
|
27248
|
+
function canonicalTimestamp(candidate) {
|
|
27249
|
+
if (typeof candidate !== "string")
|
|
27250
|
+
return false;
|
|
27251
|
+
const timestamp = new Date(candidate);
|
|
27252
|
+
return Number.isFinite(timestamp.valueOf()) && timestamp.toISOString() === candidate;
|
|
27253
|
+
}
|
|
27254
|
+
function parseControlPlaneSafetyEvidence(log) {
|
|
27255
|
+
const receiptLines = log.split(/\r?\n/).filter((line) => line.startsWith(CONTROL_PLANE_SAFETY_PREFIX));
|
|
27256
|
+
if (receiptLines.length !== 1)
|
|
27257
|
+
throw new Error("Remote upgrade did not emit one control-plane safety receipt");
|
|
27258
|
+
let candidate;
|
|
27259
|
+
try {
|
|
27260
|
+
candidate = JSON.parse(receiptLines[0].slice(CONTROL_PLANE_SAFETY_PREFIX.length));
|
|
27261
|
+
} catch {
|
|
27262
|
+
throw new Error("Remote control-plane safety receipt is not valid JSON");
|
|
27263
|
+
}
|
|
27264
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
|
|
27265
|
+
throw new Error("Remote control-plane safety receipt is invalid");
|
|
27266
|
+
}
|
|
27267
|
+
const receipt = candidate;
|
|
27268
|
+
const expectedKeys = [
|
|
27269
|
+
"backup_id",
|
|
27270
|
+
"backup_directory",
|
|
27271
|
+
"bytes",
|
|
27272
|
+
"candidate_counts",
|
|
27273
|
+
"completed_at",
|
|
27274
|
+
"current_key_checkpoint_present",
|
|
27275
|
+
"schema",
|
|
27276
|
+
"sha256"
|
|
27277
|
+
];
|
|
27278
|
+
const candidateKeys = [
|
|
27279
|
+
"deprecated_webhook_secrets",
|
|
27280
|
+
"legacy_deployment_history_rows",
|
|
27281
|
+
"legacy_project_config_rows",
|
|
27282
|
+
"opaque_key_backfill_projects",
|
|
27283
|
+
"stored_secret_values"
|
|
27284
|
+
];
|
|
27285
|
+
const validBackupId = typeof receipt.backup_id === "string" && /^control-plane-\d{8}T\d{6}Z-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(receipt.backup_id);
|
|
27286
|
+
const validDigest = typeof receipt.sha256 === "string" && /^[0-9a-f]{64}$/.test(receipt.sha256);
|
|
27287
|
+
if (!exactObjectKeys(receipt, expectedKeys) || receipt.schema !== "supacloud.control-plane-upgrade-safety.v1" || !validBackupId || receipt.backup_directory !== `${CONTROL_PLANE_BACKUP_ROOT}/${receipt.backup_id}` || !Number.isSafeInteger(receipt.bytes) || Number(receipt.bytes) <= 0 || !canonicalTimestamp(receipt.completed_at) || typeof receipt.current_key_checkpoint_present !== "boolean" || !validDigest || !nonNegativeIntegerRecord(receipt.candidate_counts, candidateKeys)) {
|
|
27288
|
+
throw new Error("Remote control-plane safety receipt is invalid");
|
|
27289
|
+
}
|
|
27290
|
+
return receipt;
|
|
27291
|
+
}
|
|
27292
|
+
function assertControlPlaneSafetyVersion(version) {
|
|
27293
|
+
const match = version.match(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/);
|
|
27294
|
+
if (!match)
|
|
27295
|
+
throw new Error("Management version must be an exact stable version");
|
|
27296
|
+
const requested = match.slice(1).map(Number);
|
|
27297
|
+
if (!requested.every(Number.isSafeInteger)) {
|
|
27298
|
+
throw new Error("Management version must be an exact stable version");
|
|
27299
|
+
}
|
|
27300
|
+
for (let index = 0;index < requested.length; index += 1) {
|
|
27301
|
+
if (requested[index] > MINIMUM_CONTROL_PLANE_SAFETY_VERSION[index])
|
|
27302
|
+
return;
|
|
27303
|
+
if (requested[index] < MINIMUM_CONTROL_PLANE_SAFETY_VERSION[index]) {
|
|
27304
|
+
throw new Error("Local artifact upgrades require Management 0.61.4 or newer with control-plane backup safety");
|
|
27305
|
+
}
|
|
27306
|
+
}
|
|
27307
|
+
}
|
|
27202
27308
|
function quoteShell2(shellText) {
|
|
27203
27309
|
return `'${shellText.split("'").join("'\\''")}'`;
|
|
27204
27310
|
}
|
|
@@ -27254,7 +27360,7 @@ function buildRemotePreflightScript() {
|
|
|
27254
27360
|
"export PATH",
|
|
27255
27361
|
trustedInstalledGithubFunction(),
|
|
27256
27362
|
"test -d /run/systemd/system || { echo 'systemd is not the active init system' >&2; exit 1; }",
|
|
27257
|
-
'for tool in systemctl systemd-run sha256sum stat realpath tar file find sort awk grep tail timeout flock install mktemp; do command -v "$tool" >/dev/null 2>&1 || { echo "Required local-upgrade tool is missing: $tool" >&2; exit 127; }; done',
|
|
27363
|
+
'for tool in systemctl systemd-run sha256sum stat realpath tar file find sort awk grep tail timeout flock install mktemp setpriv id pg_dump pg_restore; do command -v "$tool" >/dev/null 2>&1 || { echo "Required local-upgrade tool is missing: $tool" >&2; exit 127; }; done',
|
|
27258
27364
|
"tail -n 0 -- /dev/null >/dev/null 2>&1 || { echo 'A tail implementation with -n and -- support is required' >&2; exit 1; }",
|
|
27259
27365
|
"systemd-run --help | grep -Eq -- '(^|[[:space:]])--collect([=[:space:]]|$)' || { echo 'systemd-run --collect is required' >&2; exit 1; }",
|
|
27260
27366
|
"test -d /run/lock || { echo '/run/lock is unavailable' >&2; exit 1; }",
|
|
@@ -27839,12 +27945,19 @@ async function completedUpgradeOutput(ssh, paths) {
|
|
|
27839
27945
|
} catch (error) {
|
|
27840
27946
|
throw remoteReconciliationFailure("Upgrade succeeded but its retained log could not be read", [error], paths);
|
|
27841
27947
|
}
|
|
27948
|
+
let safetyEvidence;
|
|
27949
|
+
try {
|
|
27950
|
+
safetyEvidence = parseControlPlaneSafetyEvidence(log);
|
|
27951
|
+
} catch (error) {
|
|
27952
|
+
throw remoteReconciliationFailure("Upgrade succeeded but control-plane safety evidence is invalid", [error], paths);
|
|
27953
|
+
}
|
|
27842
27954
|
try {
|
|
27843
27955
|
await cleanupRemoteRecords(ssh, paths);
|
|
27844
27956
|
} catch (error) {
|
|
27845
27957
|
throw remoteReconciliationFailure("Upgrade succeeded but remote evidence cleanup could not be confirmed", [error], paths);
|
|
27846
27958
|
}
|
|
27847
27959
|
return `✅ Upgrade done
|
|
27960
|
+
${JSON.stringify(safetyEvidence)}
|
|
27848
27961
|
${log.slice(-1500)}`;
|
|
27849
27962
|
}
|
|
27850
27963
|
async function throwRemoteUpgradeFailure(ssh, paths, status) {
|
|
@@ -27908,6 +28021,7 @@ async function awaitRemoteUpgrade(ssh, paths) {
|
|
|
27908
28021
|
}
|
|
27909
28022
|
}
|
|
27910
28023
|
async function executeLocalUpgradeTransfer(ssh, request) {
|
|
28024
|
+
assertControlPlaneSafetyVersion(request.managementVersion);
|
|
27911
28025
|
const runId = randomUUID3();
|
|
27912
28026
|
const paths = buildRemoteUpgradePaths(runId);
|
|
27913
28027
|
const preflight = await remoteUpgradePreflight(ssh);
|
|
@@ -29367,7 +29481,7 @@ function isPlainRecord(candidate) {
|
|
|
29367
29481
|
const prototype = Object.getPrototypeOf(candidate);
|
|
29368
29482
|
return prototype === Object.prototype || prototype === null;
|
|
29369
29483
|
}
|
|
29370
|
-
function
|
|
29484
|
+
function canonicalTimestamp2(candidate) {
|
|
29371
29485
|
if (typeof candidate !== "string")
|
|
29372
29486
|
return false;
|
|
29373
29487
|
const timestamp = new Date(candidate);
|
|
@@ -29384,7 +29498,7 @@ function validBackupDatabase(candidate) {
|
|
|
29384
29498
|
return typeof candidate.database === "string" && SAFE_DATABASE.test(candidate.database);
|
|
29385
29499
|
}
|
|
29386
29500
|
function validBackupTimestamps(candidate) {
|
|
29387
|
-
return
|
|
29501
|
+
return canonicalTimestamp2(candidate.created_at) && canonicalTimestamp2(candidate.completed_at) && new Date(candidate.completed_at).valueOf() >= new Date(candidate.created_at).valueOf();
|
|
29388
29502
|
}
|
|
29389
29503
|
function validBackupEvidence(candidate) {
|
|
29390
29504
|
return Number.isSafeInteger(candidate.bytes) && Number(candidate.bytes) > 0 && typeof candidate.sha256 === "string" && SHA256.test(candidate.sha256);
|
|
@@ -30273,7 +30387,7 @@ function matchingText(candidate, maxLength, pattern) {
|
|
|
30273
30387
|
const candidateText = boundedText(candidate, maxLength);
|
|
30274
30388
|
return candidateText && pattern.test(candidateText) ? candidateText : null;
|
|
30275
30389
|
}
|
|
30276
|
-
function
|
|
30390
|
+
function canonicalTimestamp3(candidate) {
|
|
30277
30391
|
const timestamp = boundedText(candidate, 64);
|
|
30278
30392
|
if (!timestamp)
|
|
30279
30393
|
return null;
|
|
@@ -30288,7 +30402,7 @@ function projectedSummary(project) {
|
|
|
30288
30402
|
organization_slug: matchingText(project.organization_slug, 128, SAFE_IDENTIFIER_PATTERN),
|
|
30289
30403
|
name: boundedText(project.name, 100),
|
|
30290
30404
|
region: matchingText(project.region, 64, REGION_PATTERN),
|
|
30291
|
-
created_at:
|
|
30405
|
+
created_at: canonicalTimestamp3(project.created_at),
|
|
30292
30406
|
status: matchingText(project.status, 64, STATUS_PATTERN)
|
|
30293
30407
|
};
|
|
30294
30408
|
return Object.values(summary).every((field) => field !== null) ? summary : null;
|
|
@@ -31612,7 +31726,7 @@ function releaseRecord(candidate) {
|
|
|
31612
31726
|
"created_at",
|
|
31613
31727
|
"kind"
|
|
31614
31728
|
];
|
|
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 || !
|
|
31729
|
+
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 || !canonicalTimestamp4(record.created_at) || record.kind !== "prebuilt_static")
|
|
31616
31730
|
return null;
|
|
31617
31731
|
return {
|
|
31618
31732
|
project_ref: record.project_ref,
|
|
@@ -31635,7 +31749,7 @@ function releaseEnvelope(candidate, expected) {
|
|
|
31635
31749
|
return null;
|
|
31636
31750
|
return release;
|
|
31637
31751
|
}
|
|
31638
|
-
function
|
|
31752
|
+
function canonicalTimestamp4(candidate) {
|
|
31639
31753
|
if (typeof candidate !== "string" || !TIMESTAMP_PATTERN.test(candidate))
|
|
31640
31754
|
return false;
|
|
31641
31755
|
const milliseconds = Date.parse(candidate);
|
|
@@ -32040,7 +32154,7 @@ Actions: list_releases, get_release, upload_release, activate_release`, {
|
|
|
32040
32154
|
// package.json
|
|
32041
32155
|
var package_default = {
|
|
32042
32156
|
name: "@supacloud/admin",
|
|
32043
|
-
version: "0.15.
|
|
32157
|
+
version: "0.15.3",
|
|
32044
32158
|
description: "Platform administration CLI for SupaCloud operators",
|
|
32045
32159
|
type: "module",
|
|
32046
32160
|
main: "./dist/index.js",
|