@sealant/mend 0.36.0-next.628 → 0.36.0-next.632
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/{chunk-OTYH35OK.js → chunk-OHOMU7JP.js} +4 -1
- package/dist/{dashboard-P73HKQOT.js → dashboard-34ZANJ24.js} +1 -1
- package/dist/main.js +392 -21
- package/man/mend-accounts.1 +1 -1
- package/man/mend-adopt.1 +1 -1
- package/man/mend-attach.1 +1 -1
- package/man/mend-codex.1 +1 -1
- package/man/mend-completions.1 +1 -1
- package/man/mend-connect-pi.1 +1 -1
- package/man/mend-connect.1 +1 -1
- package/man/mend-continue.1 +1 -1
- package/man/mend-doctor.1 +1 -1
- package/man/mend-dotfiles-repo.1 +1 -1
- package/man/mend-dotfiles-sync.1 +1 -1
- package/man/mend-dotfiles.1 +1 -1
- package/man/mend-env-cluster.1 +1 -1
- package/man/mend-env-load.1 +1 -1
- package/man/mend-env-show.1 +1 -1
- package/man/mend-folder-create.1 +1 -1
- package/man/mend-folder-list.1 +1 -1
- package/man/mend-folder-push.1 +1 -1
- package/man/mend-folder-rm.1 +1 -1
- package/man/mend-git-author.1 +1 -1
- package/man/mend-help.1 +1 -1
- package/man/mend-invite.1 +1 -1
- package/man/mend-keys-init.1 +1 -1
- package/man/mend-keys-mode.1 +1 -1
- package/man/mend-keys-share.1 +1 -1
- package/man/mend-keys-show.1 +1 -1
- package/man/mend-land.1 +1 -1
- package/man/mend-login.1 +1 -1
- package/man/mend-logout.1 +1 -1
- package/man/mend-man.1 +1 -1
- package/man/mend-members.1 +1 -1
- package/man/mend-memory-import.1 +1 -1
- package/man/mend-memory-rm.1 +1 -1
- package/man/mend-memory-show.1 +1 -1
- package/man/mend-memory.1 +1 -1
- package/man/mend-models.1 +1 -1
- package/man/mend-operator-exposure.1 +1 -1
- package/man/mend-operator-gate.1 +1 -1
- package/man/mend-operator-grant-owner.1 +1 -1
- package/man/mend-operator-org-create.1 +1 -1
- package/man/mend-operator-org-invite-owner.1 +1 -1
- package/man/mend-operator-org-list.1 +1 -1
- package/man/mend-operator-org-rename.1 +1 -1
- package/man/mend-operator-reset-link.1 +1 -1
- package/man/mend-pair.1 +1 -1
- package/man/mend-projects.1 +1 -1
- package/man/mend-pull.1 +1 -1
- package/man/mend-refresh.1 +1 -1
- package/man/mend-rejoin.1 +1 -1
- package/man/mend-resume.1 +1 -1
- package/man/mend-run.1 +1 -1
- package/man/mend-secrets-add.1 +1 -1
- package/man/mend-secrets-rm.1 +1 -1
- package/man/mend-secrets.1 +1 -1
- package/man/mend-server-logs.1 +1 -1
- package/man/mend-server-restart.1 +1 -1
- package/man/mend-server-setup.1 +1 -1
- package/man/mend-server-start.1 +1 -1
- package/man/mend-server-status.1 +1 -1
- package/man/mend-server-stop.1 +1 -1
- package/man/mend-server-upgrade.1 +9 -2
- package/man/mend-server.1 +1 -1
- package/man/mend-service-add.1 +1 -1
- package/man/mend-service-connect.1 +1 -1
- package/man/mend-service-init.1 +1 -1
- package/man/mend-service-list.1 +1 -1
- package/man/mend-service-logs.1 +1 -1
- package/man/mend-service-restart.1 +1 -1
- package/man/mend-service-run.1 +1 -1
- package/man/mend-service-stop.1 +1 -1
- package/man/mend-session-share.1 +1 -1
- package/man/mend-sessions.1 +1 -1
- package/man/mend-shell.1 +1 -1
- package/man/mend-skills-push.1 +1 -1
- package/man/mend-skills.1 +1 -1
- package/man/mend-snake.1 +1 -1
- package/man/mend-ssh-setup.1 +1 -1
- package/man/mend-ssh.1 +1 -1
- package/man/mend-stop.1 +1 -1
- package/man/mend-ui.1 +1 -1
- package/man/mend-uninstall.1 +1 -1
- package/man/mend-version.1 +1 -1
- package/man/mend-worktrees-rm.1 +1 -1
- package/man/mend-worktrees.1 +1 -1
- package/man/mend.1 +1 -1
- package/package.json +2 -2
|
@@ -27127,6 +27127,9 @@ var AuditAction = Schema_exports.Literals([
|
|
|
27127
27127
|
"session.shared_control_off",
|
|
27128
27128
|
// The owner ended a session's workspace with captures still pending: work was discarded.
|
|
27129
27129
|
"session.unsaved_discarded",
|
|
27130
|
+
// Mend stopped a session whose owner no longer has access to where it ran (mend#558): a removed
|
|
27131
|
+
// member, or a joiner who can no longer see the project. The session's owner is the actor.
|
|
27132
|
+
"session.stopped_no_access",
|
|
27130
27133
|
"member.password_reset_issued",
|
|
27131
27134
|
"organization.created",
|
|
27132
27135
|
"organization.renamed",
|
|
@@ -28436,7 +28439,7 @@ var ProjectClusterBindingsSnapshot = class extends Schema_exports.Class(
|
|
|
28436
28439
|
}) {
|
|
28437
28440
|
};
|
|
28438
28441
|
|
|
28439
|
-
// ../../node_modules/.pnpm/@sealant+api-contracts-next@0.39.0-next.
|
|
28442
|
+
// ../../node_modules/.pnpm/@sealant+api-contracts-next@0.39.0-next.695_effect@4.0.0-beta.93/node_modules/@sealant/api-contracts-next/dist/workspace-environment.js
|
|
28440
28443
|
var RESERVED_NAMES = new Map([
|
|
28441
28444
|
// Identity/process roots the daemon and shells own.
|
|
28442
28445
|
...[
|
package/dist/main.js
CHANGED
|
@@ -52,7 +52,7 @@ import {
|
|
|
52
52
|
trackBracketedPaste,
|
|
53
53
|
validatePiProfile,
|
|
54
54
|
validateSecretFilePath
|
|
55
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-OHOMU7JP.js";
|
|
56
56
|
|
|
57
57
|
// src/main.ts
|
|
58
58
|
import { spawn as spawn3, spawnSync as spawnSync6 } from "node:child_process";
|
|
@@ -2814,12 +2814,16 @@ var COMMANDS = [
|
|
|
2814
2814
|
name: "server upgrade",
|
|
2815
2815
|
section: "this machine",
|
|
2816
2816
|
summary: "upgrade to an explicit version with a database backup",
|
|
2817
|
-
synopsis: [
|
|
2817
|
+
synopsis: [
|
|
2818
|
+
"--version <target|latest> [--assets-dir <dir>] [--offline] [--from-preview] [--keep-backups <n>]"
|
|
2819
|
+
],
|
|
2818
2820
|
description: [
|
|
2819
2821
|
"Preflights release assets and the canonical image's version label before interrupting Mend. Downgrades are refused. latest is resolved only when explicitly requested; a same-version request does nothing.",
|
|
2820
2822
|
"The installed configuration stays: the bind, the origin, the ports, the edge host and the declared exposure and tenancy are carried into the new generation, which renders the same overlays beside the new compose.yaml. An upgrade never drops the edge or the posture; mend server setup is where they change.",
|
|
2821
2823
|
"Stops app writers, starts official Postgres if needed, and streams pg_dumpall into a private database backup before selecting and starting the target. Connections are interrupted. Workspace containers and data are retained, but active work can lose connectivity and need reconnection. Stop any external database writers before upgrading.",
|
|
2822
|
-
"Preflight or backup failure retains the old pin and attempts to recover the old app if it was running. Once target startup may have begun, Mend never automatically downgrades or restores the database. The target pin, old generation and backup remain. Inspect logs, fix the target, then use mend server start. Recovery records are under the installation's backups/upgrade-UUID directory."
|
|
2824
|
+
"Preflight or backup failure retains the old pin and attempts to recover the old app if it was running. Once target startup may have begun, Mend never automatically downgrades or restores the database. The target pin, old generation and backup remain. Inspect logs, fix the target, then use mend server start. Recovery records are under the installation's backups/upgrade-UUID directory.",
|
|
2825
|
+
"After the target answers health, its recovery record is marked completed and older completed backups are removed, keeping the newest two (this one included) in the order Mend wrote them; each removal is printed with the space it freed. A failed upgrade removes nothing. A backup this release recorded as pending, or whose dump is incomplete, is always kept. Each dump holds the whole database, so it can run to gigabytes.",
|
|
2826
|
+
'Backups written by releases before 0.36 carry no outcome: the first upgrade on 0.36 or later treats each one whose dump is whole as completed and keeps only the newest N, so copy any you want to keep out of backups/ first. Their removals are marked "from before 0.36, no recorded outcome".'
|
|
2823
2827
|
],
|
|
2824
2828
|
options: [
|
|
2825
2829
|
{
|
|
@@ -2837,6 +2841,10 @@ var COMMANDS = [
|
|
|
2837
2841
|
{
|
|
2838
2842
|
flag: "--from-preview",
|
|
2839
2843
|
text: "once, from a preview numbered X.Y.Z-preview.K to a next build X.Y.Z-next.N; refused unless the target carries every migration the server applied"
|
|
2844
|
+
},
|
|
2845
|
+
{
|
|
2846
|
+
flag: "--keep-backups <n>",
|
|
2847
|
+
text: "completed upgrade backups to keep after a healthy upgrade, this one included; 0 keeps all. Default: 2"
|
|
2840
2848
|
}
|
|
2841
2849
|
],
|
|
2842
2850
|
examples: [
|
|
@@ -5993,6 +6001,286 @@ var commitGeneration = (paths, files) => {
|
|
|
5993
6001
|
activateGeneration(paths, generation);
|
|
5994
6002
|
return generation;
|
|
5995
6003
|
};
|
|
6004
|
+
var renderRecovery = (record2) => `${JSON.stringify(record2, null, 2)}
|
|
6005
|
+
`;
|
|
6006
|
+
var BACKUP_NAME = /^upgrade-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
6007
|
+
var REMOVING_NAME = /^upgrade-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.removing$/;
|
|
6008
|
+
var RECOVERY_TEMPORARY = /^\.recovery-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
6009
|
+
var lstatOptional = (file) => {
|
|
6010
|
+
try {
|
|
6011
|
+
return fs14.lstatSync(file);
|
|
6012
|
+
} catch (cause) {
|
|
6013
|
+
if (hasCode(cause, "ENOENT")) return null;
|
|
6014
|
+
throw cause;
|
|
6015
|
+
}
|
|
6016
|
+
};
|
|
6017
|
+
var freedBy = (stat) => stat.nlink === 1 ? stat.size : 0;
|
|
6018
|
+
var GENERATION_NAME = /^gen-[0-9a-f-]{36}$/;
|
|
6019
|
+
var VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
6020
|
+
var generationVersion = (configDir, generation) => {
|
|
6021
|
+
if (!GENERATION_NAME.test(generation)) return void 0;
|
|
6022
|
+
try {
|
|
6023
|
+
const parsed = JSON.parse(
|
|
6024
|
+
fs14.readFileSync(path15.join(configDir, "generations", generation, "server.json"), "utf8")
|
|
6025
|
+
);
|
|
6026
|
+
return typeof parsed === "object" && parsed !== null && "serverVersion" in parsed && typeof parsed.serverVersion === "string" && VERSION.test(parsed.serverVersion) ? parsed.serverVersion : void 0;
|
|
6027
|
+
} catch {
|
|
6028
|
+
return void 0;
|
|
6029
|
+
}
|
|
6030
|
+
};
|
|
6031
|
+
var readRecord = (configDir, file, stat) => {
|
|
6032
|
+
let parsed;
|
|
6033
|
+
try {
|
|
6034
|
+
parsed = JSON.parse(fs14.readFileSync(file, "utf8"));
|
|
6035
|
+
} catch {
|
|
6036
|
+
return null;
|
|
6037
|
+
}
|
|
6038
|
+
if (typeof parsed !== "object" || parsed === null || !("previousGeneration" in parsed) || !("targetGeneration" in parsed) || typeof parsed.previousGeneration !== "string" || typeof parsed.targetGeneration !== "string")
|
|
6039
|
+
return null;
|
|
6040
|
+
const sequence = "sequence" in parsed && typeof parsed.sequence === "number" && Number.isSafeInteger(parsed.sequence) && parsed.sequence > 0 ? parsed.sequence : void 0;
|
|
6041
|
+
const stamped = "createdAt" in parsed && typeof parsed.createdAt === "string" ? Date.parse(parsed.createdAt) : Number.NaN;
|
|
6042
|
+
const targetGeneration = path15.basename(parsed.targetGeneration);
|
|
6043
|
+
return {
|
|
6044
|
+
previousGeneration: path15.basename(parsed.previousGeneration),
|
|
6045
|
+
targetGeneration,
|
|
6046
|
+
state: "state" in parsed ? parsed.state : void 0,
|
|
6047
|
+
sequence,
|
|
6048
|
+
recordsOutcome: "state" in parsed || "sequence" in parsed,
|
|
6049
|
+
targetVersion: generationVersion(configDir, targetGeneration),
|
|
6050
|
+
clock: Number.isFinite(stamped) ? stamped : stat.mtimeMs
|
|
6051
|
+
};
|
|
6052
|
+
};
|
|
6053
|
+
var DUMP_TRAILER = /-- PostgreSQL database cluster dump complete\r?\n(?:--\r?\n)?\s*$/;
|
|
6054
|
+
var dumpIsWhole = (file, size) => {
|
|
6055
|
+
const length = Math.min(size, 256);
|
|
6056
|
+
if (length === 0) return false;
|
|
6057
|
+
const buffer = Buffer.alloc(length);
|
|
6058
|
+
const fd = fs14.openSync(file, "r");
|
|
6059
|
+
try {
|
|
6060
|
+
fs14.readSync(fd, buffer, 0, length, size - length);
|
|
6061
|
+
} finally {
|
|
6062
|
+
fs14.closeSync(fd);
|
|
6063
|
+
}
|
|
6064
|
+
const tail = buffer.toString("utf8");
|
|
6065
|
+
const match = DUMP_TRAILER.exec(tail);
|
|
6066
|
+
if (match === null) return false;
|
|
6067
|
+
return match.index === 0 ? length === size : tail[match.index - 1] === "\n";
|
|
6068
|
+
};
|
|
6069
|
+
var readBackup = (configDir, directory) => {
|
|
6070
|
+
const stat = lstatOptional(directory);
|
|
6071
|
+
if (stat === null || !stat.isDirectory()) return { kind: "foreign" };
|
|
6072
|
+
if (REMOVING_NAME.test(path15.basename(directory))) return readRemoving(directory);
|
|
6073
|
+
if (!BACKUP_NAME.test(path15.basename(directory))) return { kind: "foreign" };
|
|
6074
|
+
const entries = fs14.readdirSync(directory);
|
|
6075
|
+
if (entries.length === 0) return { kind: "interrupted", bytes: 0 };
|
|
6076
|
+
const recoveryFile = path15.join(directory, "recovery.json");
|
|
6077
|
+
const recovery = lstatOptional(recoveryFile);
|
|
6078
|
+
if (recovery === null || !recovery.isFile()) return { kind: "foreign" };
|
|
6079
|
+
const record2 = readRecord(configDir, recoveryFile, recovery);
|
|
6080
|
+
if (record2 === null) return { kind: "foreign" };
|
|
6081
|
+
if (entries.includes("database.sql.partial"))
|
|
6082
|
+
return { kind: "held", reason: "unfinished", record: record2 };
|
|
6083
|
+
if (entries.some(
|
|
6084
|
+
(name) => name !== "database.sql" && name !== "recovery.json" && !RECOVERY_TEMPORARY.test(name)
|
|
6085
|
+
))
|
|
6086
|
+
return { kind: "foreign" };
|
|
6087
|
+
if (record2.recordsOutcome && (record2.sequence === void 0 || record2.state === void 0))
|
|
6088
|
+
return {
|
|
6089
|
+
kind: "held",
|
|
6090
|
+
reason: "unreadable",
|
|
6091
|
+
detail: "recovery.json has a state without a valid sequence"
|
|
6092
|
+
};
|
|
6093
|
+
const dump = lstatOptional(path15.join(directory, "database.sql"));
|
|
6094
|
+
if (dump !== null && !dump.isFile()) return { kind: "foreign" };
|
|
6095
|
+
if (dump === null)
|
|
6096
|
+
return record2.state === "completed" ? { kind: "interrupted", bytes: freedBy(recovery) } : { kind: "held", reason: "unfinished", record: record2 };
|
|
6097
|
+
if (!dumpIsWhole(path15.join(directory, "database.sql"), dump.size))
|
|
6098
|
+
return { kind: "held", reason: "unfinished", record: record2 };
|
|
6099
|
+
if (record2.recordsOutcome && record2.state !== "completed")
|
|
6100
|
+
return { kind: "held", reason: "pending", record: record2 };
|
|
6101
|
+
return {
|
|
6102
|
+
kind: "completed",
|
|
6103
|
+
record: record2,
|
|
6104
|
+
bytes: freedBy(dump) + freedBy(recovery),
|
|
6105
|
+
legacy: !record2.recordsOutcome
|
|
6106
|
+
};
|
|
6107
|
+
};
|
|
6108
|
+
var readRemoving = (directory) => {
|
|
6109
|
+
let bytes = 0;
|
|
6110
|
+
for (const name of fs14.readdirSync(directory)) {
|
|
6111
|
+
const file = lstatOptional(path15.join(directory, name));
|
|
6112
|
+
if (file === null || !file.isFile() || !(name === "database.sql" || name === "database.sql.partial" || name === "recovery.json" || RECOVERY_TEMPORARY.test(name)))
|
|
6113
|
+
return { kind: "foreign" };
|
|
6114
|
+
bytes += freedBy(file);
|
|
6115
|
+
}
|
|
6116
|
+
return { kind: "interrupted", bytes };
|
|
6117
|
+
};
|
|
6118
|
+
var nextSequence = (configDir, backups) => {
|
|
6119
|
+
let highest = 0;
|
|
6120
|
+
for (const name of fs14.readdirSync(backups)) {
|
|
6121
|
+
if (!BACKUP_NAME.test(name)) continue;
|
|
6122
|
+
try {
|
|
6123
|
+
const directory = path15.join(backups, name);
|
|
6124
|
+
const stat = lstatOptional(directory);
|
|
6125
|
+
if (stat === null || !stat.isDirectory()) continue;
|
|
6126
|
+
const file = path15.join(directory, "recovery.json");
|
|
6127
|
+
const recovery = lstatOptional(file);
|
|
6128
|
+
if (recovery === null || !recovery.isFile()) continue;
|
|
6129
|
+
highest = Math.max(highest, readRecord(configDir, file, recovery)?.sequence ?? 0);
|
|
6130
|
+
} catch {
|
|
6131
|
+
continue;
|
|
6132
|
+
}
|
|
6133
|
+
}
|
|
6134
|
+
return highest + 1;
|
|
6135
|
+
};
|
|
6136
|
+
var versionOrder = (compareVersions, a, b) => {
|
|
6137
|
+
if (a === void 0 || b === void 0) return 0;
|
|
6138
|
+
try {
|
|
6139
|
+
return compareVersions(a, b);
|
|
6140
|
+
} catch {
|
|
6141
|
+
return 0;
|
|
6142
|
+
}
|
|
6143
|
+
};
|
|
6144
|
+
var chainDepths = (records) => {
|
|
6145
|
+
const depths = /* @__PURE__ */ new Map();
|
|
6146
|
+
const depth = (record2, visiting) => {
|
|
6147
|
+
const known = depths.get(record2);
|
|
6148
|
+
if (known !== void 0) return known;
|
|
6149
|
+
const next = new Set(visiting).add(record2);
|
|
6150
|
+
const before = records.filter(
|
|
6151
|
+
(other) => !next.has(other) && other.targetGeneration === record2.previousGeneration && other.targetGeneration !== other.previousGeneration
|
|
6152
|
+
);
|
|
6153
|
+
const value = Math.max(-1, ...before.map((other) => depth(other, next))) + 1;
|
|
6154
|
+
depths.set(record2, value);
|
|
6155
|
+
return value;
|
|
6156
|
+
};
|
|
6157
|
+
return (record2) => depth(record2, /* @__PURE__ */ new Set());
|
|
6158
|
+
};
|
|
6159
|
+
var newestFirst = (records, compareVersions) => {
|
|
6160
|
+
const depth = chainDepths(records.filter((record2) => record2.sequence === void 0));
|
|
6161
|
+
return (a, b) => {
|
|
6162
|
+
if (a.sequence !== void 0 || b.sequence !== void 0) {
|
|
6163
|
+
if (a.sequence === void 0) return 1;
|
|
6164
|
+
if (b.sequence === void 0) return -1;
|
|
6165
|
+
if (a.sequence !== b.sequence) return b.sequence - a.sequence;
|
|
6166
|
+
} else {
|
|
6167
|
+
const byVersion = versionOrder(compareVersions, b.targetVersion, a.targetVersion);
|
|
6168
|
+
if (byVersion !== 0) return byVersion;
|
|
6169
|
+
const byChain = depth(b) - depth(a);
|
|
6170
|
+
if (byChain !== 0) return byChain;
|
|
6171
|
+
}
|
|
6172
|
+
return b.clock - a.clock;
|
|
6173
|
+
};
|
|
6174
|
+
};
|
|
6175
|
+
var writtenAfterOutcomes = (record2, records, current, compareVersions) => {
|
|
6176
|
+
const sequenced = records.filter((other) => other.sequence !== void 0);
|
|
6177
|
+
const unsequenced = records.filter((other) => other.sequence === void 0);
|
|
6178
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6179
|
+
const chainsFromSequenced = (candidate) => {
|
|
6180
|
+
if (seen.has(candidate)) return false;
|
|
6181
|
+
seen.add(candidate);
|
|
6182
|
+
if (sequenced.some((other) => other.targetGeneration === candidate.previousGeneration))
|
|
6183
|
+
return true;
|
|
6184
|
+
return unsequenced.some(
|
|
6185
|
+
(other) => other.targetGeneration === candidate.previousGeneration && chainsFromSequenced(other)
|
|
6186
|
+
);
|
|
6187
|
+
};
|
|
6188
|
+
return chainsFromSequenced(record2) || sequenced.some(
|
|
6189
|
+
(other) => other !== current && versionOrder(compareVersions, record2.targetVersion, other.targetVersion) > 0
|
|
6190
|
+
);
|
|
6191
|
+
};
|
|
6192
|
+
var removeBackup = (directory) => {
|
|
6193
|
+
const removing = REMOVING_NAME.test(path15.basename(directory)) ? directory : `${directory}.removing`;
|
|
6194
|
+
if (removing !== directory) fs14.renameSync(directory, removing);
|
|
6195
|
+
for (const name of fs14.readdirSync(removing))
|
|
6196
|
+
if (name === "database.sql" || name === "database.sql.partial" || name === "recovery.json" || RECOVERY_TEMPORARY.test(name))
|
|
6197
|
+
fs14.unlinkSync(path15.join(removing, name));
|
|
6198
|
+
fs14.rmdirSync(removing);
|
|
6199
|
+
};
|
|
6200
|
+
var entryOf = ({ directory, bytes, legacy }) => ({
|
|
6201
|
+
directory,
|
|
6202
|
+
bytes,
|
|
6203
|
+
legacy,
|
|
6204
|
+
interrupted: false
|
|
6205
|
+
});
|
|
6206
|
+
var failureOf = (cause) => cause instanceof Error ? cause.message : "unknown filesystem error";
|
|
6207
|
+
var pruneBackups = (configDir, keep, current, compareVersions) => {
|
|
6208
|
+
if (!Number.isSafeInteger(keep) || keep < 1)
|
|
6209
|
+
throw new ServerStoreError("Pruning keeps at least one upgrade backup.");
|
|
6210
|
+
const backups = path15.join(configDir, "backups");
|
|
6211
|
+
if (path15.dirname(current.directory) !== backups)
|
|
6212
|
+
throw new ServerStoreError("The current upgrade backup is outside this installation.");
|
|
6213
|
+
const currentRead = readBackup(configDir, current.directory);
|
|
6214
|
+
if (currentRead.kind !== "completed" || currentRead.record.sequence === void 0)
|
|
6215
|
+
throw new ServerStoreError("The current upgrade backup is not completed; nothing was pruned.");
|
|
6216
|
+
const completed = [];
|
|
6217
|
+
const interrupted = [];
|
|
6218
|
+
const held = [];
|
|
6219
|
+
const records = [currentRead.record];
|
|
6220
|
+
for (const name of fs14.readdirSync(backups).toSorted()) {
|
|
6221
|
+
const directory = path15.join(backups, name);
|
|
6222
|
+
if (directory === current.directory) continue;
|
|
6223
|
+
let read;
|
|
6224
|
+
try {
|
|
6225
|
+
read = readBackup(configDir, directory);
|
|
6226
|
+
} catch (cause) {
|
|
6227
|
+
if (BACKUP_NAME.test(name) || REMOVING_NAME.test(name))
|
|
6228
|
+
held.push({ directory, reason: "unreadable", detail: failureOf(cause) });
|
|
6229
|
+
continue;
|
|
6230
|
+
}
|
|
6231
|
+
if (read.kind === "held") {
|
|
6232
|
+
held.push({
|
|
6233
|
+
directory,
|
|
6234
|
+
reason: read.reason,
|
|
6235
|
+
...read.detail === void 0 ? {} : { detail: read.detail }
|
|
6236
|
+
});
|
|
6237
|
+
if (read.record !== void 0) records.push(read.record);
|
|
6238
|
+
}
|
|
6239
|
+
if (read.kind === "interrupted")
|
|
6240
|
+
interrupted.push({ directory, bytes: read.bytes, legacy: false, interrupted: true });
|
|
6241
|
+
if (read.kind === "completed") {
|
|
6242
|
+
completed.push({
|
|
6243
|
+
directory,
|
|
6244
|
+
bytes: read.bytes,
|
|
6245
|
+
legacy: read.legacy,
|
|
6246
|
+
interrupted: false,
|
|
6247
|
+
record: read.record
|
|
6248
|
+
});
|
|
6249
|
+
records.push(read.record);
|
|
6250
|
+
}
|
|
6251
|
+
}
|
|
6252
|
+
const candidates = completed.filter((entry) => {
|
|
6253
|
+
if (!entry.legacy || !writtenAfterOutcomes(entry.record, records, currentRead.record, compareVersions))
|
|
6254
|
+
return true;
|
|
6255
|
+
held.push({ directory: entry.directory, reason: "no-outcome" });
|
|
6256
|
+
return false;
|
|
6257
|
+
});
|
|
6258
|
+
const compare = newestFirst(records, compareVersions);
|
|
6259
|
+
candidates.sort((a, b) => compare(a.record, b.record));
|
|
6260
|
+
const kept = [
|
|
6261
|
+
{ directory: current.directory, bytes: currentRead.bytes, legacy: false, interrupted: false },
|
|
6262
|
+
...candidates.slice(0, keep - 1).map(entryOf)
|
|
6263
|
+
];
|
|
6264
|
+
const removed = [];
|
|
6265
|
+
const failed = [];
|
|
6266
|
+
for (const candidate of [...interrupted, ...candidates.slice(keep - 1).map(entryOf)]) {
|
|
6267
|
+
try {
|
|
6268
|
+
removeBackup(candidate.directory);
|
|
6269
|
+
removed.push(candidate);
|
|
6270
|
+
} catch (cause) {
|
|
6271
|
+
failed.push({ directory: candidate.directory, message: failureOf(cause) });
|
|
6272
|
+
}
|
|
6273
|
+
}
|
|
6274
|
+
let unsynced;
|
|
6275
|
+
if (removed.length > 0 || failed.length > 0) {
|
|
6276
|
+
try {
|
|
6277
|
+
syncDirectory(backups);
|
|
6278
|
+
} catch (cause) {
|
|
6279
|
+
unsynced = failureOf(cause);
|
|
6280
|
+
}
|
|
6281
|
+
}
|
|
6282
|
+
return { removed, kept, held, failed, ...unsynced === void 0 ? {} : { unsynced } };
|
|
6283
|
+
};
|
|
5996
6284
|
var createStore = (configDir, lock) => {
|
|
5997
6285
|
const paths = {
|
|
5998
6286
|
configDir,
|
|
@@ -6014,22 +6302,19 @@ var createStore = (configDir, lock) => {
|
|
|
6014
6302
|
const backups = path15.join(configDir, "backups");
|
|
6015
6303
|
fs14.mkdirSync(backups, { recursive: true, mode: 448 });
|
|
6016
6304
|
fs14.chmodSync(backups, 448);
|
|
6305
|
+
const sequence = nextSequence(configDir, backups);
|
|
6017
6306
|
const directory = path15.join(backups, `upgrade-${randomUUID2()}`);
|
|
6018
6307
|
fs14.mkdirSync(directory, { mode: 448 });
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
2
|
|
6030
|
-
)}
|
|
6031
|
-
`
|
|
6032
|
-
);
|
|
6308
|
+
const record2 = {
|
|
6309
|
+
previousGeneration: previous.directory,
|
|
6310
|
+
targetGeneration: target.directory,
|
|
6311
|
+
database: "database.sql",
|
|
6312
|
+
sequence,
|
|
6313
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6314
|
+
policy: "If target is active, migrations may have begun. Never downgrade or restore automatically."
|
|
6315
|
+
};
|
|
6316
|
+
const recoveryFile = path15.join(directory, "recovery.json");
|
|
6317
|
+
writeDurable(recoveryFile, renderRecovery({ ...record2, state: "pending" }));
|
|
6033
6318
|
syncDirectory(directory);
|
|
6034
6319
|
syncDirectory(backups);
|
|
6035
6320
|
syncDirectory(configDir);
|
|
@@ -6049,9 +6334,16 @@ var createStore = (configDir, lock) => {
|
|
|
6049
6334
|
}
|
|
6050
6335
|
fs14.renameSync(partialFile, path15.join(directory, "database.sql"));
|
|
6051
6336
|
syncDirectory(directory);
|
|
6337
|
+
}),
|
|
6338
|
+
markCompleted: () => whileOwned(() => {
|
|
6339
|
+
const temporary = path15.join(directory, `.recovery-${randomUUID2()}`);
|
|
6340
|
+
writeDurable(temporary, renderRecovery({ ...record2, state: "completed" }));
|
|
6341
|
+
fs14.renameSync(temporary, recoveryFile);
|
|
6342
|
+
syncDirectory(directory);
|
|
6052
6343
|
})
|
|
6053
6344
|
};
|
|
6054
|
-
})
|
|
6345
|
+
}),
|
|
6346
|
+
pruneBackups: (keep, current, compareVersions) => whileOwned(() => pruneBackups(configDir, keep, current, compareVersions))
|
|
6055
6347
|
};
|
|
6056
6348
|
};
|
|
6057
6349
|
var refuseFlatLayout = (configDir) => {
|
|
@@ -7406,9 +7698,26 @@ var startInstallation = async (runtime, installation, secrets) => {
|
|
|
7406
7698
|
await probeHealth(runtime, healthOrigin(installation.config), installation.config.serverVersion);
|
|
7407
7699
|
runtime.writeLine(reachableLine(installation.config));
|
|
7408
7700
|
};
|
|
7701
|
+
var DEFAULT_KEEP_BACKUPS = 2;
|
|
7409
7702
|
var parseUpgradeOptions = (args) => {
|
|
7410
7703
|
const fromPreview = args.includes("--from-preview");
|
|
7411
|
-
const rest =
|
|
7704
|
+
const rest = [];
|
|
7705
|
+
let keepBackups = DEFAULT_KEEP_BACKUPS;
|
|
7706
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
7707
|
+
const flag = args[index];
|
|
7708
|
+
if (flag === void 0 || flag === "--from-preview") continue;
|
|
7709
|
+
if (flag === "--keep-backups") {
|
|
7710
|
+
const value = args[index + 1];
|
|
7711
|
+
if (value === void 0 || !/^\d+$/.test(value) || !Number.isSafeInteger(Number(value)))
|
|
7712
|
+
throw setupError(
|
|
7713
|
+
"--keep-backups takes a whole number: how many upgrade backups to keep, 0 for all."
|
|
7714
|
+
);
|
|
7715
|
+
keepBackups = Number(value);
|
|
7716
|
+
index += 1;
|
|
7717
|
+
continue;
|
|
7718
|
+
}
|
|
7719
|
+
rest.push(flag);
|
|
7720
|
+
}
|
|
7412
7721
|
for (let index = 0; index < rest.length; index += 1) {
|
|
7413
7722
|
const flag = rest[index];
|
|
7414
7723
|
if (flag === "--offline") continue;
|
|
@@ -7421,7 +7730,63 @@ var parseUpgradeOptions = (args) => {
|
|
|
7421
7730
|
throw setupError(
|
|
7422
7731
|
"Upgrade requires --version TARGET. Use --version latest only to request the latest release explicitly."
|
|
7423
7732
|
);
|
|
7424
|
-
return { ...options, fromPreview };
|
|
7733
|
+
return { ...options, fromPreview, keepBackups };
|
|
7734
|
+
};
|
|
7735
|
+
var formatBytes3 = (bytes) => {
|
|
7736
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
7737
|
+
const units = ["KiB", "MiB", "GiB", "TiB"];
|
|
7738
|
+
let value = bytes / 1024;
|
|
7739
|
+
let unit = 0;
|
|
7740
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
7741
|
+
value /= 1024;
|
|
7742
|
+
unit += 1;
|
|
7743
|
+
}
|
|
7744
|
+
return `${value.toFixed(value < 10 ? 1 : 0)} ${units[unit]}`;
|
|
7745
|
+
};
|
|
7746
|
+
var heldReason = (entry) => {
|
|
7747
|
+
switch (entry.reason) {
|
|
7748
|
+
case "pending":
|
|
7749
|
+
return "pending: its upgrade never recorded a healthy target";
|
|
7750
|
+
case "unfinished":
|
|
7751
|
+
return "unfinished: no complete database dump";
|
|
7752
|
+
case "no-outcome":
|
|
7753
|
+
return "no recorded outcome: written after a 0.36 backup by a CLI that records none";
|
|
7754
|
+
case "unreadable":
|
|
7755
|
+
return `unreadable: ${entry.detail ?? "not read"}`;
|
|
7756
|
+
}
|
|
7757
|
+
};
|
|
7758
|
+
var pruneUpgradeBackups = (runtime, store, backup, keep) => {
|
|
7759
|
+
const marked = backup.markCompleted();
|
|
7760
|
+
if (marked._tag === "error") {
|
|
7761
|
+
runtime.writeLine(
|
|
7762
|
+
`Could not record the upgrade as completed in ${backup.directory}/recovery.json; no backups were removed. ${marked.error.message}`
|
|
7763
|
+
);
|
|
7764
|
+
return;
|
|
7765
|
+
}
|
|
7766
|
+
if (keep === 0) {
|
|
7767
|
+
runtime.writeLine("Upgrade backups \xB7 all kept (--keep-backups 0)");
|
|
7768
|
+
return;
|
|
7769
|
+
}
|
|
7770
|
+
const pruned = store.pruneBackups(keep, backup, upgradeOrder);
|
|
7771
|
+
if (pruned._tag === "error") {
|
|
7772
|
+
runtime.writeLine(`Upgrade backups \xB7 not pruned: ${pruned.error.message}`);
|
|
7773
|
+
return;
|
|
7774
|
+
}
|
|
7775
|
+
const { removed, kept, held, failed, unsynced } = pruned.value;
|
|
7776
|
+
for (const entry of removed)
|
|
7777
|
+
runtime.writeLine(
|
|
7778
|
+
`Removed upgrade backup ${entry.directory} \xB7 ${formatBytes3(entry.bytes)}${entry.interrupted ? " \xB7 finishing a removal a crash cut short" : entry.legacy ? " \xB7 from before 0.36, no recorded outcome" : ""}`
|
|
7779
|
+
);
|
|
7780
|
+
const freed = removed.reduce((total, entry) => total + entry.bytes, 0);
|
|
7781
|
+
runtime.writeLine(
|
|
7782
|
+
`Upgrade backups \xB7 removed ${removed.length}${removed.length > 0 ? ` \xB7 ${formatBytes3(freed)} freed` : ""} \xB7 kept ${kept.length} (--keep-backups ${keep})`
|
|
7783
|
+
);
|
|
7784
|
+
for (const entry of failed)
|
|
7785
|
+
runtime.writeLine(`Could not remove upgrade backup ${entry.directory}: ${entry.message}`);
|
|
7786
|
+
if (unsynced !== void 0)
|
|
7787
|
+
runtime.writeLine(`Upgrade backups \xB7 removals made but not fsynced: ${unsynced}`);
|
|
7788
|
+
for (const entry of held)
|
|
7789
|
+
runtime.writeLine(`Kept upgrade backup ${entry.directory} \xB7 ${heldReason(entry)}`);
|
|
7425
7790
|
};
|
|
7426
7791
|
var LEGACY_PREVIEW = /^(\d+\.\d+\.\d+)-preview\.(0|[1-9]\d*)$/;
|
|
7427
7792
|
var NEXT_BUILD = /^(\d+\.\d+\.\d+)-next\.(0|[1-9]\d*)(\.preview\.[1-9]\d*)?$/;
|
|
@@ -7430,6 +7795,11 @@ var isPreviewToNext = (from, to) => {
|
|
|
7430
7795
|
const next = NEXT_BUILD.exec(to);
|
|
7431
7796
|
return preview !== null && next !== null && preview[1] === next[1];
|
|
7432
7797
|
};
|
|
7798
|
+
var upgradeOrder = (a, b) => {
|
|
7799
|
+
if (isPreviewToNext(a, b)) return -1;
|
|
7800
|
+
if (isPreviewToNext(b, a)) return 1;
|
|
7801
|
+
return compareServerVersions(a, b);
|
|
7802
|
+
};
|
|
7433
7803
|
var drizzleMillis = (folder) => {
|
|
7434
7804
|
const at = (start, end) => Number(folder.slice(start, end));
|
|
7435
7805
|
return String(Date.UTC(at(0, 4), at(4, 6) - 1, at(6, 8), at(8, 10), at(10, 12), at(12, 14)));
|
|
@@ -7669,6 +8039,7 @@ var upgradeServer = async (args, runtime, store, existing) => {
|
|
|
7669
8039
|
);
|
|
7670
8040
|
}
|
|
7671
8041
|
runtime.writeLine(`Upgraded to ${version}. Retained database backup: ${backup.directory}`);
|
|
8042
|
+
pruneUpgradeBackups(runtime, store, backup, options.keepBackups);
|
|
7672
8043
|
};
|
|
7673
8044
|
var observeEdgeCertificate = async (runtime, installation, host) => {
|
|
7674
8045
|
const listed = await runtime.run(
|
|
@@ -12537,7 +12908,7 @@ var dashboard = async (config, options = {}) => {
|
|
|
12537
12908
|
);
|
|
12538
12909
|
process.exit(rerun.status ?? 0);
|
|
12539
12910
|
}
|
|
12540
|
-
const { runDashboard } = await import("./dashboard-
|
|
12911
|
+
const { runDashboard } = await import("./dashboard-34ZANJ24.js");
|
|
12541
12912
|
const agentShare = await startShareIfBridge(config);
|
|
12542
12913
|
const tunnels = options.noTunnel === true || serverIsLocal(config) ? null : createServiceTunnels({
|
|
12543
12914
|
listServices: () => fetchServices(config),
|
package/man/mend-accounts.1
CHANGED
package/man/mend-adopt.1
CHANGED
package/man/mend-attach.1
CHANGED
package/man/mend-codex.1
CHANGED
package/man/mend-completions.1
CHANGED
package/man/mend-connect-pi.1
CHANGED
package/man/mend-connect.1
CHANGED
package/man/mend-continue.1
CHANGED
package/man/mend-doctor.1
CHANGED
package/man/mend-dotfiles-repo.1
CHANGED
package/man/mend-dotfiles-sync.1
CHANGED
package/man/mend-dotfiles.1
CHANGED
package/man/mend-env-cluster.1
CHANGED
package/man/mend-env-load.1
CHANGED
package/man/mend-env-show.1
CHANGED
package/man/mend-folder-create.1
CHANGED
package/man/mend-folder-list.1
CHANGED
package/man/mend-folder-push.1
CHANGED
package/man/mend-folder-rm.1
CHANGED