@slock-ai/computer 0.0.35 → 0.0.37
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 +590 -1690
- package/dist/lib/index.js +29 -14
- package/package.json +6 -2
package/dist/index.js
CHANGED
|
@@ -16617,8 +16617,8 @@ var require_snapshot_recorder = __commonJS({
|
|
|
16617
16617
|
"../../node_modules/.pnpm/undici@7.24.8/node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
|
|
16618
16618
|
"use strict";
|
|
16619
16619
|
init_esm_shims();
|
|
16620
|
-
var { writeFile:
|
|
16621
|
-
var { dirname:
|
|
16620
|
+
var { writeFile: writeFile12, readFile: readFile15, mkdir: mkdir15 } = __require("fs/promises");
|
|
16621
|
+
var { dirname: dirname13, resolve: resolve2 } = __require("path");
|
|
16622
16622
|
var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("timers");
|
|
16623
16623
|
var { InvalidArgumentError: InvalidArgumentError2, UndiciError } = require_errors();
|
|
16624
16624
|
var { hashId, isUrlExcludedFactory, normalizeHeaders, createHeaderFilters } = require_snapshot_utils();
|
|
@@ -16819,7 +16819,7 @@ var require_snapshot_recorder = __commonJS({
|
|
|
16819
16819
|
throw new InvalidArgumentError2("Snapshot path is required");
|
|
16820
16820
|
}
|
|
16821
16821
|
try {
|
|
16822
|
-
const data = await
|
|
16822
|
+
const data = await readFile15(resolve2(path3), "utf8");
|
|
16823
16823
|
const parsed = JSON.parse(data);
|
|
16824
16824
|
if (Array.isArray(parsed)) {
|
|
16825
16825
|
this.#snapshots.clear();
|
|
@@ -16849,12 +16849,12 @@ var require_snapshot_recorder = __commonJS({
|
|
|
16849
16849
|
throw new InvalidArgumentError2("Snapshot path is required");
|
|
16850
16850
|
}
|
|
16851
16851
|
const resolvedPath = resolve2(path3);
|
|
16852
|
-
await
|
|
16852
|
+
await mkdir15(dirname13(resolvedPath), { recursive: true });
|
|
16853
16853
|
const data = Array.from(this.#snapshots.entries()).map(([hash, snapshot]) => ({
|
|
16854
16854
|
hash,
|
|
16855
16855
|
snapshot
|
|
16856
16856
|
}));
|
|
16857
|
-
await
|
|
16857
|
+
await writeFile12(resolvedPath, JSON.stringify(data, null, 2), { flush: true });
|
|
16858
16858
|
}
|
|
16859
16859
|
/**
|
|
16860
16860
|
* Clears all recorded snapshots
|
|
@@ -28469,8 +28469,8 @@ var require_graceful_fs = __commonJS({
|
|
|
28469
28469
|
fs2.createReadStream = createReadStream;
|
|
28470
28470
|
fs2.createWriteStream = createWriteStream;
|
|
28471
28471
|
var fs$readFile = fs2.readFile;
|
|
28472
|
-
fs2.readFile =
|
|
28473
|
-
function
|
|
28472
|
+
fs2.readFile = readFile15;
|
|
28473
|
+
function readFile15(path3, options, cb) {
|
|
28474
28474
|
if (typeof options === "function")
|
|
28475
28475
|
cb = options, options = null;
|
|
28476
28476
|
return go$readFile(path3, options, cb);
|
|
@@ -28486,8 +28486,8 @@ var require_graceful_fs = __commonJS({
|
|
|
28486
28486
|
}
|
|
28487
28487
|
}
|
|
28488
28488
|
var fs$writeFile = fs2.writeFile;
|
|
28489
|
-
fs2.writeFile =
|
|
28490
|
-
function
|
|
28489
|
+
fs2.writeFile = writeFile12;
|
|
28490
|
+
function writeFile12(path3, data, options, cb) {
|
|
28491
28491
|
if (typeof options === "function")
|
|
28492
28492
|
cb = options, options = null;
|
|
28493
28493
|
return go$writeFile(path3, data, options, cb);
|
|
@@ -28522,8 +28522,8 @@ var require_graceful_fs = __commonJS({
|
|
|
28522
28522
|
}
|
|
28523
28523
|
var fs$copyFile = fs2.copyFile;
|
|
28524
28524
|
if (fs$copyFile)
|
|
28525
|
-
fs2.copyFile =
|
|
28526
|
-
function
|
|
28525
|
+
fs2.copyFile = copyFile;
|
|
28526
|
+
function copyFile(src, dest, flags, cb) {
|
|
28527
28527
|
if (typeof flags === "function") {
|
|
28528
28528
|
cb = flags;
|
|
28529
28529
|
flags = 0;
|
|
@@ -28541,9 +28541,9 @@ var require_graceful_fs = __commonJS({
|
|
|
28541
28541
|
}
|
|
28542
28542
|
}
|
|
28543
28543
|
var fs$readdir = fs2.readdir;
|
|
28544
|
-
fs2.readdir =
|
|
28544
|
+
fs2.readdir = readdir4;
|
|
28545
28545
|
var noReaddirOptionVersions = /^v[0-5]\./;
|
|
28546
|
-
function
|
|
28546
|
+
function readdir4(path3, options, cb) {
|
|
28547
28547
|
if (typeof options === "function")
|
|
28548
28548
|
cb = options, options = null;
|
|
28549
28549
|
var go$readdir = noReaddirOptionVersions.test(process.version) ? function go$readdir2(path4, options2, cb2, startTime) {
|
|
@@ -29193,11 +29193,11 @@ var require_mtime_precision = __commonJS({
|
|
|
29193
29193
|
function probe(file, fs, callback) {
|
|
29194
29194
|
const cachedPrecision = fs[cacheSymbol];
|
|
29195
29195
|
if (cachedPrecision) {
|
|
29196
|
-
return fs.stat(file, (err,
|
|
29196
|
+
return fs.stat(file, (err, stat4) => {
|
|
29197
29197
|
if (err) {
|
|
29198
29198
|
return callback(err);
|
|
29199
29199
|
}
|
|
29200
|
-
callback(null,
|
|
29200
|
+
callback(null, stat4.mtime, cachedPrecision);
|
|
29201
29201
|
});
|
|
29202
29202
|
}
|
|
29203
29203
|
const mtime = new Date(Math.ceil(Date.now() / 1e3) * 1e3 + 5);
|
|
@@ -29205,13 +29205,13 @@ var require_mtime_precision = __commonJS({
|
|
|
29205
29205
|
if (err) {
|
|
29206
29206
|
return callback(err);
|
|
29207
29207
|
}
|
|
29208
|
-
fs.stat(file, (err2,
|
|
29208
|
+
fs.stat(file, (err2, stat4) => {
|
|
29209
29209
|
if (err2) {
|
|
29210
29210
|
return callback(err2);
|
|
29211
29211
|
}
|
|
29212
|
-
const precision =
|
|
29212
|
+
const precision = stat4.mtime.getTime() % 1e3 === 0 ? "s" : "ms";
|
|
29213
29213
|
Object.defineProperty(fs, cacheSymbol, { value: precision });
|
|
29214
|
-
callback(null,
|
|
29214
|
+
callback(null, stat4.mtime, precision);
|
|
29215
29215
|
});
|
|
29216
29216
|
});
|
|
29217
29217
|
}
|
|
@@ -29266,14 +29266,14 @@ var require_lockfile = __commonJS({
|
|
|
29266
29266
|
if (options.stale <= 0) {
|
|
29267
29267
|
return callback(Object.assign(new Error("Lock file is already being held"), { code: "ELOCKED", file }));
|
|
29268
29268
|
}
|
|
29269
|
-
options.fs.stat(lockfilePath, (err2,
|
|
29269
|
+
options.fs.stat(lockfilePath, (err2, stat4) => {
|
|
29270
29270
|
if (err2) {
|
|
29271
29271
|
if (err2.code === "ENOENT") {
|
|
29272
29272
|
return acquireLock(file, { ...options, stale: 0 }, callback);
|
|
29273
29273
|
}
|
|
29274
29274
|
return callback(err2);
|
|
29275
29275
|
}
|
|
29276
|
-
if (!isLockStale(
|
|
29276
|
+
if (!isLockStale(stat4, options)) {
|
|
29277
29277
|
return callback(Object.assign(new Error("Lock file is already being held"), { code: "ELOCKED", file }));
|
|
29278
29278
|
}
|
|
29279
29279
|
removeLock(file, options, (err3) => {
|
|
@@ -29285,8 +29285,8 @@ var require_lockfile = __commonJS({
|
|
|
29285
29285
|
});
|
|
29286
29286
|
});
|
|
29287
29287
|
}
|
|
29288
|
-
function isLockStale(
|
|
29289
|
-
return
|
|
29288
|
+
function isLockStale(stat4, options) {
|
|
29289
|
+
return stat4.mtime.getTime() < Date.now() - options.stale;
|
|
29290
29290
|
}
|
|
29291
29291
|
function removeLock(file, options, callback) {
|
|
29292
29292
|
options.fs.rmdir(getLockFile(file, options), (err) => {
|
|
@@ -29304,7 +29304,7 @@ var require_lockfile = __commonJS({
|
|
|
29304
29304
|
lock2.updateDelay = lock2.updateDelay || options.update;
|
|
29305
29305
|
lock2.updateTimeout = setTimeout(() => {
|
|
29306
29306
|
lock2.updateTimeout = null;
|
|
29307
|
-
options.fs.stat(lock2.lockfilePath, (err,
|
|
29307
|
+
options.fs.stat(lock2.lockfilePath, (err, stat4) => {
|
|
29308
29308
|
const isOverThreshold = lock2.lastUpdate + options.stale < Date.now();
|
|
29309
29309
|
if (err) {
|
|
29310
29310
|
if (err.code === "ENOENT" || isOverThreshold) {
|
|
@@ -29313,7 +29313,7 @@ var require_lockfile = __commonJS({
|
|
|
29313
29313
|
lock2.updateDelay = 1e3;
|
|
29314
29314
|
return updateLock(file, options);
|
|
29315
29315
|
}
|
|
29316
|
-
const isMtimeOurs = lock2.mtime.getTime() ===
|
|
29316
|
+
const isMtimeOurs = lock2.mtime.getTime() === stat4.mtime.getTime();
|
|
29317
29317
|
if (!isMtimeOurs) {
|
|
29318
29318
|
return setLockAsCompromised(
|
|
29319
29319
|
file,
|
|
@@ -29438,11 +29438,11 @@ var require_lockfile = __commonJS({
|
|
|
29438
29438
|
if (err) {
|
|
29439
29439
|
return callback(err);
|
|
29440
29440
|
}
|
|
29441
|
-
options.fs.stat(getLockFile(file2, options), (err2,
|
|
29441
|
+
options.fs.stat(getLockFile(file2, options), (err2, stat4) => {
|
|
29442
29442
|
if (err2) {
|
|
29443
29443
|
return err2.code === "ENOENT" ? callback(null, false) : callback(err2);
|
|
29444
29444
|
}
|
|
29445
|
-
return callback(null, !isLockStale(
|
|
29445
|
+
return callback(null, !isLockStale(stat4, options));
|
|
29446
29446
|
});
|
|
29447
29447
|
});
|
|
29448
29448
|
}
|
|
@@ -29570,6 +29570,7 @@ var require_proper_lockfile = __commonJS({
|
|
|
29570
29570
|
|
|
29571
29571
|
// src/index.ts
|
|
29572
29572
|
init_esm_shims();
|
|
29573
|
+
import { pathToFileURL } from "url";
|
|
29573
29574
|
|
|
29574
29575
|
// ../../node_modules/.pnpm/commander@12.1.0/node_modules/commander/esm.mjs
|
|
29575
29576
|
init_esm_shims();
|
|
@@ -30042,12 +30043,6 @@ function adoptionLogPath(slockHome) {
|
|
|
30042
30043
|
function channelPath(slockHome) {
|
|
30043
30044
|
return path2.join(computerDir(slockHome), "channel");
|
|
30044
30045
|
}
|
|
30045
|
-
function upgradeStagingDir(slockHome, version) {
|
|
30046
|
-
return path2.join(computerDir(slockHome), "upgrade-staging", version);
|
|
30047
|
-
}
|
|
30048
|
-
function upgradeSnapshotPath(slockHome) {
|
|
30049
|
-
return path2.join(computerDir(slockHome), "upgrade-snapshot.json");
|
|
30050
|
-
}
|
|
30051
30046
|
function upgradeLogPath(slockHome) {
|
|
30052
30047
|
return path2.join(computerDir(slockHome), "upgrade.log");
|
|
30053
30048
|
}
|
|
@@ -30510,8 +30505,8 @@ async function runAttach(opts) {
|
|
|
30510
30505
|
// src/setup.ts
|
|
30511
30506
|
init_esm_shims();
|
|
30512
30507
|
var import_undici3 = __toESM(require_undici(), 1);
|
|
30513
|
-
import { chmod as
|
|
30514
|
-
import { dirname as
|
|
30508
|
+
import { chmod as chmod5, mkdir as mkdir11, readFile as readFile10, rename as rename4, rm as rm3, writeFile as writeFile10 } from "fs/promises";
|
|
30509
|
+
import { dirname as dirname10 } from "path";
|
|
30515
30510
|
|
|
30516
30511
|
// src/lib/migration.ts
|
|
30517
30512
|
init_esm_shims();
|
|
@@ -31018,11 +31013,14 @@ async function appendAdoptionLog(slockHome, line) {
|
|
|
31018
31013
|
// src/service.ts
|
|
31019
31014
|
init_esm_shims();
|
|
31020
31015
|
import { spawn as spawn2 } from "child_process";
|
|
31016
|
+
import { createRequire as createRequire2 } from "module";
|
|
31021
31017
|
|
|
31022
31018
|
// src/version.ts
|
|
31023
31019
|
init_esm_shims();
|
|
31024
31020
|
import { createRequire } from "module";
|
|
31025
31021
|
function readComputerVersion(moduleUrl = import.meta.url) {
|
|
31022
|
+
const baked = process.env.SLOCK_COMPUTER_VERSION;
|
|
31023
|
+
if (typeof baked === "string" && baked.length > 0) return baked;
|
|
31026
31024
|
try {
|
|
31027
31025
|
const require2 = createRequire(moduleUrl);
|
|
31028
31026
|
const pkg = require2("../package.json");
|
|
@@ -31034,8 +31032,8 @@ function readComputerVersion(moduleUrl = import.meta.url) {
|
|
|
31034
31032
|
var COMPUTER_VERSION = readComputerVersion();
|
|
31035
31033
|
|
|
31036
31034
|
// src/service.ts
|
|
31037
|
-
import { mkdir as
|
|
31038
|
-
import { dirname as
|
|
31035
|
+
import { mkdir as mkdir10, readFile as readFile9, writeFile as writeFile9, open, rename as rename3 } from "fs/promises";
|
|
31036
|
+
import { dirname as dirname9, join as joinPath } from "path";
|
|
31039
31037
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
31040
31038
|
|
|
31041
31039
|
// src/cleanup.ts
|
|
@@ -31792,14 +31790,317 @@ async function detach(input, options = {}) {
|
|
|
31792
31790
|
};
|
|
31793
31791
|
}
|
|
31794
31792
|
|
|
31793
|
+
// src/upgradeSea.ts
|
|
31794
|
+
init_esm_shims();
|
|
31795
|
+
import { createHash as createHash3 } from "crypto";
|
|
31796
|
+
import { chmod as chmod4, mkdir as mkdir9, readFile as readFile8, rename as rename2, rm as rm2, writeFile as writeFile8 } from "fs/promises";
|
|
31797
|
+
import { join as join4 } from "path";
|
|
31798
|
+
|
|
31799
|
+
// src/channel.ts
|
|
31800
|
+
init_esm_shims();
|
|
31801
|
+
import { readFile as readFile7, writeFile as writeFile7, mkdir as mkdir8 } from "fs/promises";
|
|
31802
|
+
import { dirname as dirname8 } from "path";
|
|
31803
|
+
var DEFAULT_CHANNEL = "latest";
|
|
31804
|
+
var SEMVER_RE = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
|
|
31805
|
+
function parseChannel(raw) {
|
|
31806
|
+
const v = raw.trim();
|
|
31807
|
+
if (v === "latest" || v === "alpha") return v;
|
|
31808
|
+
if (v.startsWith("pinned:")) {
|
|
31809
|
+
const semver = v.slice("pinned:".length);
|
|
31810
|
+
if (SEMVER_RE.test(semver)) return `pinned:${semver}`;
|
|
31811
|
+
return null;
|
|
31812
|
+
}
|
|
31813
|
+
return null;
|
|
31814
|
+
}
|
|
31815
|
+
async function readChannel(slockHome) {
|
|
31816
|
+
try {
|
|
31817
|
+
const raw = await readFile7(channelPath(slockHome), "utf8");
|
|
31818
|
+
const parsed = parseChannel(raw);
|
|
31819
|
+
if (parsed !== null) return parsed;
|
|
31820
|
+
} catch {
|
|
31821
|
+
}
|
|
31822
|
+
return DEFAULT_CHANNEL;
|
|
31823
|
+
}
|
|
31824
|
+
async function writeChannel(slockHome, channel2) {
|
|
31825
|
+
const p = channelPath(slockHome);
|
|
31826
|
+
await mkdir8(dirname8(p), { recursive: true });
|
|
31827
|
+
await writeFile7(p, `${channel2}
|
|
31828
|
+
`, { mode: 384 });
|
|
31829
|
+
}
|
|
31830
|
+
async function runChannelShow(slockHome) {
|
|
31831
|
+
void computerDir;
|
|
31832
|
+
const channel2 = await readChannel(slockHome);
|
|
31833
|
+
info(channel2);
|
|
31834
|
+
}
|
|
31835
|
+
async function runChannelSet(slockHome, raw) {
|
|
31836
|
+
const parsed = parseChannel(raw);
|
|
31837
|
+
if (parsed === null) {
|
|
31838
|
+
fail(
|
|
31839
|
+
"CHANNEL_INVALID",
|
|
31840
|
+
`Invalid channel "${raw}". Accepted: \`latest\`, \`alpha\`, or \`pinned:<semver>\` (e.g. pinned:0.52.2).`
|
|
31841
|
+
);
|
|
31842
|
+
}
|
|
31843
|
+
await writeChannel(slockHome, parsed);
|
|
31844
|
+
info(`Channel set to ${parsed}.`);
|
|
31845
|
+
info(`Note: service reads channel at startup; restart \`slock-computer start\` to apply.`);
|
|
31846
|
+
}
|
|
31847
|
+
|
|
31848
|
+
// src/upgradeSea.ts
|
|
31849
|
+
var DEFAULT_UPGRADE_BASE_URL = "https://cdn.slock.ai/computer";
|
|
31850
|
+
var UPGRADE_BASE_URL_ENV = "SLOCK_COMPUTER_UPGRADE_BASE_URL";
|
|
31851
|
+
function resolveUpgradeBaseUrl(env = process.env) {
|
|
31852
|
+
const override = env[UPGRADE_BASE_URL_ENV];
|
|
31853
|
+
if (typeof override === "string" && override.trim().length > 0) {
|
|
31854
|
+
return override.trim().replace(/\/+$/, "");
|
|
31855
|
+
}
|
|
31856
|
+
return DEFAULT_UPGRADE_BASE_URL;
|
|
31857
|
+
}
|
|
31858
|
+
async function stageSeaPhase(stagingDir, version, platform, arch, deps = {}) {
|
|
31859
|
+
const fetchFn = deps.fetchFn ?? fetch;
|
|
31860
|
+
const baseUrl = deps.baseUrl ?? resolveUpgradeBaseUrl();
|
|
31861
|
+
const targetKey = `${platform}-${arch}`;
|
|
31862
|
+
const manifestUrl = `${baseUrl}/${version}/manifest.json`;
|
|
31863
|
+
const manifestRes = await fetchFn(manifestUrl);
|
|
31864
|
+
if (!manifestRes.ok) {
|
|
31865
|
+
throw new Error(`UPGRADE_MANIFEST_FETCH_FAILED: ${manifestRes.status} for ${manifestUrl}`);
|
|
31866
|
+
}
|
|
31867
|
+
const manifest = await manifestRes.json();
|
|
31868
|
+
const target = manifest.targets?.[targetKey];
|
|
31869
|
+
if (!target || typeof target.file !== "string" || typeof target.sha256 !== "string") {
|
|
31870
|
+
throw new Error(
|
|
31871
|
+
`UPGRADE_NO_BINARY_FOR_PLATFORM: manifest for ${version} has no usable binary for ${targetKey}`
|
|
31872
|
+
);
|
|
31873
|
+
}
|
|
31874
|
+
const binaryUrl = `${baseUrl}/${version}/${target.file}`;
|
|
31875
|
+
const binRes = await fetchFn(binaryUrl);
|
|
31876
|
+
if (!binRes.ok) {
|
|
31877
|
+
throw new Error(`UPGRADE_DOWNLOAD_FAILED: ${binRes.status} for ${binaryUrl}`);
|
|
31878
|
+
}
|
|
31879
|
+
const onProgress = deps.onProgress;
|
|
31880
|
+
const progressRequestId = deps.requestId ?? "";
|
|
31881
|
+
const emitDownloadProgress = (downloaded, total) => {
|
|
31882
|
+
if (!onProgress) return;
|
|
31883
|
+
if (total == null || total <= 0) {
|
|
31884
|
+
onProgress({ requestId: progressRequestId, phase: "downloading" });
|
|
31885
|
+
return;
|
|
31886
|
+
}
|
|
31887
|
+
onProgress({
|
|
31888
|
+
requestId: progressRequestId,
|
|
31889
|
+
phase: "downloading",
|
|
31890
|
+
percent: Math.min(100, Math.floor(downloaded / total * 100))
|
|
31891
|
+
});
|
|
31892
|
+
};
|
|
31893
|
+
let bytes;
|
|
31894
|
+
const body = binRes.body;
|
|
31895
|
+
if (onProgress && body) {
|
|
31896
|
+
const totalHeader = binRes.headers?.get?.("content-length");
|
|
31897
|
+
const total = totalHeader != null && /^\d+$/.test(totalHeader) ? Number(totalHeader) : null;
|
|
31898
|
+
const reader = body.getReader();
|
|
31899
|
+
const chunks = [];
|
|
31900
|
+
let downloaded = 0;
|
|
31901
|
+
const THROTTLE_MS = 200;
|
|
31902
|
+
let lastEmit = 0;
|
|
31903
|
+
try {
|
|
31904
|
+
for (; ; ) {
|
|
31905
|
+
const { done, value } = await reader.read();
|
|
31906
|
+
if (done) break;
|
|
31907
|
+
if (value) {
|
|
31908
|
+
chunks.push(value);
|
|
31909
|
+
downloaded += value.byteLength;
|
|
31910
|
+
const now = Date.now();
|
|
31911
|
+
if (now - lastEmit >= THROTTLE_MS) {
|
|
31912
|
+
lastEmit = now;
|
|
31913
|
+
emitDownloadProgress(downloaded, total);
|
|
31914
|
+
}
|
|
31915
|
+
}
|
|
31916
|
+
}
|
|
31917
|
+
} finally {
|
|
31918
|
+
await reader.cancel().catch(() => {
|
|
31919
|
+
});
|
|
31920
|
+
}
|
|
31921
|
+
emitDownloadProgress(downloaded, total);
|
|
31922
|
+
bytes = Buffer.concat(chunks);
|
|
31923
|
+
} else {
|
|
31924
|
+
bytes = Buffer.from(await binRes.arrayBuffer());
|
|
31925
|
+
}
|
|
31926
|
+
const actualSha = createHash3("sha256").update(bytes).digest("hex");
|
|
31927
|
+
if (actualSha !== target.sha256) {
|
|
31928
|
+
throw new Error(
|
|
31929
|
+
`UPGRADE_SHA_MISMATCH: ${targetKey} ${version} expected ${target.sha256} got ${actualSha}`
|
|
31930
|
+
);
|
|
31931
|
+
}
|
|
31932
|
+
await mkdir9(stagingDir, { recursive: true });
|
|
31933
|
+
const stagedBinaryPath = join4(stagingDir, target.file);
|
|
31934
|
+
await writeFile8(stagedBinaryPath, bytes, { mode: 493 });
|
|
31935
|
+
await chmod4(stagedBinaryPath, 493);
|
|
31936
|
+
return { stagedBinaryPath, version, sha256: actualSha };
|
|
31937
|
+
}
|
|
31938
|
+
async function swapSeaPhase(currentBinaryPath, stagedBinaryPath) {
|
|
31939
|
+
const prevBinaryPath = `${currentBinaryPath}.prev`;
|
|
31940
|
+
await rm2(prevBinaryPath, { force: true });
|
|
31941
|
+
await rename2(currentBinaryPath, prevBinaryPath);
|
|
31942
|
+
try {
|
|
31943
|
+
await rename2(stagedBinaryPath, currentBinaryPath);
|
|
31944
|
+
await chmod4(currentBinaryPath, 493);
|
|
31945
|
+
} catch (err) {
|
|
31946
|
+
await rename2(prevBinaryPath, currentBinaryPath).catch(() => {
|
|
31947
|
+
});
|
|
31948
|
+
throw err;
|
|
31949
|
+
}
|
|
31950
|
+
return { prevBinaryPath, currentBinaryPath };
|
|
31951
|
+
}
|
|
31952
|
+
function pendingUpgradeMarkerPath(slockHome) {
|
|
31953
|
+
return join4(slockHome, "upgrade-pending.json");
|
|
31954
|
+
}
|
|
31955
|
+
async function writePendingUpgradeMarker(slockHome, marker) {
|
|
31956
|
+
const path3 = pendingUpgradeMarkerPath(slockHome);
|
|
31957
|
+
const tmp = `${path3}.tmp`;
|
|
31958
|
+
await mkdir9(slockHome, { recursive: true });
|
|
31959
|
+
await writeFile8(tmp, `${JSON.stringify(marker, null, 2)}
|
|
31960
|
+
`, "utf8");
|
|
31961
|
+
await rename2(tmp, path3);
|
|
31962
|
+
}
|
|
31963
|
+
async function readPendingUpgradeMarker(slockHome) {
|
|
31964
|
+
try {
|
|
31965
|
+
const raw = await readFile8(pendingUpgradeMarkerPath(slockHome), "utf8");
|
|
31966
|
+
const parsed = JSON.parse(raw);
|
|
31967
|
+
if (typeof parsed.requestId === "string" && typeof parsed.targetVersion === "string" && typeof parsed.fromVersion === "string" && typeof parsed.startedAt === "string") {
|
|
31968
|
+
return parsed;
|
|
31969
|
+
}
|
|
31970
|
+
return null;
|
|
31971
|
+
} catch {
|
|
31972
|
+
return null;
|
|
31973
|
+
}
|
|
31974
|
+
}
|
|
31975
|
+
async function clearPendingUpgradeMarker(slockHome) {
|
|
31976
|
+
await rm2(pendingUpgradeMarkerPath(slockHome), { force: true });
|
|
31977
|
+
}
|
|
31978
|
+
async function runSeaUpgrade(opts) {
|
|
31979
|
+
const d = opts.deps ?? {};
|
|
31980
|
+
const platform = opts.platform ?? process.platform;
|
|
31981
|
+
const arch = opts.arch ?? process.arch;
|
|
31982
|
+
const emit7 = (phase, message) => d.onProgress?.({ requestId: opts.requestId, phase, message });
|
|
31983
|
+
const stagingDir = d.stagingDir ?? join4(opts.slockHome, "upgrade-staging", opts.targetVersion);
|
|
31984
|
+
emit7("downloading", `downloading ${opts.targetVersion}`);
|
|
31985
|
+
let staged;
|
|
31986
|
+
try {
|
|
31987
|
+
staged = await (d.stageFn ?? stageSeaPhase)(stagingDir, opts.targetVersion, platform, arch, {
|
|
31988
|
+
fetchFn: d.fetchFn,
|
|
31989
|
+
baseUrl: d.baseUrl,
|
|
31990
|
+
// Thread requestId + the progress sink so the stage can stream throttled
|
|
31991
|
+
// byte-% `downloading` frames (otherwise the bar sits static at the
|
|
31992
|
+
// downloading milestone for the whole download).
|
|
31993
|
+
requestId: opts.requestId,
|
|
31994
|
+
onProgress: d.onProgress
|
|
31995
|
+
});
|
|
31996
|
+
} catch (e) {
|
|
31997
|
+
return { ok: false, failedPhase: "stage", reason: e instanceof Error ? e.message : String(e) };
|
|
31998
|
+
}
|
|
31999
|
+
emit7("verifying", `verified sha256 ${staged.sha256.slice(0, 12)}`);
|
|
32000
|
+
await writePendingUpgradeMarker(opts.slockHome, {
|
|
32001
|
+
requestId: opts.requestId,
|
|
32002
|
+
fromVersion: opts.fromVersion,
|
|
32003
|
+
targetVersion: opts.targetVersion,
|
|
32004
|
+
startedAt: opts.nowIso
|
|
32005
|
+
});
|
|
32006
|
+
emit7("applying", "swapping binary");
|
|
32007
|
+
let swap;
|
|
32008
|
+
try {
|
|
32009
|
+
swap = await (d.swapFn ?? swapSeaPhase)(opts.currentBinaryPath, staged.stagedBinaryPath);
|
|
32010
|
+
} catch (e) {
|
|
32011
|
+
await clearPendingUpgradeMarker(opts.slockHome).catch(() => {
|
|
32012
|
+
});
|
|
32013
|
+
return { ok: false, failedPhase: "swap", reason: e instanceof Error ? e.message : String(e) };
|
|
32014
|
+
}
|
|
32015
|
+
emit7("restarting", "restarting service on new version");
|
|
32016
|
+
return { ok: true, swap };
|
|
32017
|
+
}
|
|
32018
|
+
async function fetchComputerDistTags(fetchFn = fetch) {
|
|
32019
|
+
const url = "https://registry.npmjs.org/@slock-ai/computer";
|
|
32020
|
+
const controller = new AbortController();
|
|
32021
|
+
const timeoutId = setTimeout(() => controller.abort(), 1e4);
|
|
32022
|
+
try {
|
|
32023
|
+
const res = await fetchFn(url, { signal: controller.signal, headers: { accept: "application/json" } });
|
|
32024
|
+
if (!res.ok) return null;
|
|
32025
|
+
const data = await res.json();
|
|
32026
|
+
return data["dist-tags"] ?? null;
|
|
32027
|
+
} catch {
|
|
32028
|
+
return null;
|
|
32029
|
+
} finally {
|
|
32030
|
+
clearTimeout(timeoutId);
|
|
32031
|
+
}
|
|
32032
|
+
}
|
|
32033
|
+
async function resolveSeaTargetVersion(channel2, fetchDistTags = () => fetchComputerDistTags()) {
|
|
32034
|
+
if (channel2.startsWith("pinned:")) {
|
|
32035
|
+
const v = channel2.slice("pinned:".length);
|
|
32036
|
+
return SEMVER_RE.test(v) ? v : null;
|
|
32037
|
+
}
|
|
32038
|
+
const tags = await fetchDistTags();
|
|
32039
|
+
const resolved = tags?.[channel2];
|
|
32040
|
+
return typeof resolved === "string" && resolved.length > 0 ? resolved : null;
|
|
32041
|
+
}
|
|
32042
|
+
async function resolveAndRunSeaUpgrade(opts) {
|
|
32043
|
+
const d = opts.deps ?? {};
|
|
32044
|
+
let targetVersion;
|
|
32045
|
+
try {
|
|
32046
|
+
if (d.resolveTargetVersion) {
|
|
32047
|
+
targetVersion = await d.resolveTargetVersion();
|
|
32048
|
+
} else {
|
|
32049
|
+
const channel2 = await (d.readChannelFn ?? readChannel)(opts.slockHome);
|
|
32050
|
+
targetVersion = await resolveSeaTargetVersion(channel2, d.fetchDistTags);
|
|
32051
|
+
}
|
|
32052
|
+
} catch (e) {
|
|
32053
|
+
return { ok: false, failedPhase: "resolve", reason: e instanceof Error ? e.message : String(e) };
|
|
32054
|
+
}
|
|
32055
|
+
if (!targetVersion) {
|
|
32056
|
+
return { ok: false, failedPhase: "resolve", reason: "UPGRADE_VERSION_RESOLVE_FAILED" };
|
|
32057
|
+
}
|
|
32058
|
+
if (targetVersion === opts.fromVersion) {
|
|
32059
|
+
return { ok: true, alreadyCurrent: true, targetVersion };
|
|
32060
|
+
}
|
|
32061
|
+
const result = await (d.runSeaUpgradeFn ?? runSeaUpgrade)({
|
|
32062
|
+
slockHome: opts.slockHome,
|
|
32063
|
+
requestId: opts.requestId,
|
|
32064
|
+
fromVersion: opts.fromVersion,
|
|
32065
|
+
targetVersion,
|
|
32066
|
+
currentBinaryPath: opts.currentBinaryPath,
|
|
32067
|
+
nowIso: opts.nowIso,
|
|
32068
|
+
platform: opts.platform,
|
|
32069
|
+
arch: opts.arch,
|
|
32070
|
+
deps: {
|
|
32071
|
+
baseUrl: d.baseUrl,
|
|
32072
|
+
fetchFn: d.fetchFn,
|
|
32073
|
+
onProgress: d.onProgress,
|
|
32074
|
+
stageFn: d.stageFn,
|
|
32075
|
+
swapFn: d.swapFn,
|
|
32076
|
+
stagingDir: d.stagingDir
|
|
32077
|
+
}
|
|
32078
|
+
});
|
|
32079
|
+
return { ...result, targetVersion };
|
|
32080
|
+
}
|
|
32081
|
+
|
|
31795
32082
|
// src/service.ts
|
|
31796
|
-
|
|
32083
|
+
var seaRequire = createRequire2(import.meta.url);
|
|
32084
|
+
var cachedIsSea;
|
|
32085
|
+
function isSeaBinary() {
|
|
32086
|
+
if (cachedIsSea !== void 0) return cachedIsSea;
|
|
32087
|
+
try {
|
|
32088
|
+
cachedIsSea = seaRequire("node:sea").isSea();
|
|
32089
|
+
} catch {
|
|
32090
|
+
cachedIsSea = false;
|
|
32091
|
+
}
|
|
32092
|
+
return cachedIsSea;
|
|
32093
|
+
}
|
|
32094
|
+
function buildResidentSpawn(mode, serverId, selfEntry = process.argv[1] ?? "", execArgv = process.execArgv, isSea = isSeaBinary()) {
|
|
31797
32095
|
const tail = serverId ? [mode, serverId] : [mode];
|
|
32096
|
+
if (isSea) {
|
|
32097
|
+
return { command: process.execPath, args: [...execArgv, ...tail] };
|
|
32098
|
+
}
|
|
31798
32099
|
return { command: process.execPath, args: [...execArgv, selfEntry, ...tail] };
|
|
31799
32100
|
}
|
|
31800
32101
|
var PARENT_LOCK_HELD_ENV_VAR = "SLOCK_COMPUTER_PARENT_MUTATION_LOCK_HELD";
|
|
31801
32102
|
async function spawnDetachedService(slockHome) {
|
|
31802
|
-
await
|
|
32103
|
+
await mkdir10(serviceRunDir(slockHome), { recursive: true });
|
|
31803
32104
|
const supLogFd = await open(serviceLogPath(slockHome), "a");
|
|
31804
32105
|
const { command, args } = buildResidentSpawn("__service", null);
|
|
31805
32106
|
const child = spawn2(command, args, {
|
|
@@ -31824,7 +32125,22 @@ async function spawnDetachedService(slockHome) {
|
|
|
31824
32125
|
return pid;
|
|
31825
32126
|
}
|
|
31826
32127
|
var EX_CONFIG_EXIT_CODE = 78;
|
|
32128
|
+
async function ensureSeaRuntimePackageDir() {
|
|
32129
|
+
if (!isSeaBinary() || process.env.PI_PACKAGE_DIR) return;
|
|
32130
|
+
try {
|
|
32131
|
+
const dir = joinPath(resolveSlockHome(), "runtime-pkg");
|
|
32132
|
+
await mkdir10(dir, { recursive: true });
|
|
32133
|
+
await writeFile9(
|
|
32134
|
+
joinPath(dir, "package.json"),
|
|
32135
|
+
`${JSON.stringify({ name: "slock-computer-sea-runtime", version: COMPUTER_VERSION })}
|
|
32136
|
+
`
|
|
32137
|
+
);
|
|
32138
|
+
process.env.PI_PACKAGE_DIR = dir;
|
|
32139
|
+
} catch {
|
|
32140
|
+
}
|
|
32141
|
+
}
|
|
31827
32142
|
var defaultCoreFactory = async (creds) => {
|
|
32143
|
+
await ensureSeaRuntimePackageDir();
|
|
31828
32144
|
let coreMod;
|
|
31829
32145
|
try {
|
|
31830
32146
|
coreMod = await import("@slock-ai/daemon/core");
|
|
@@ -31849,37 +32165,85 @@ var defaultCoreFactory = async (creds) => {
|
|
|
31849
32165
|
serverUrl: creds.serverUrl,
|
|
31850
32166
|
apiKey: creds.apiKey,
|
|
31851
32167
|
localTrace: true,
|
|
32168
|
+
// In a SEA single-binary there is no sidecar `slock` CLI script for the
|
|
32169
|
+
// daemon to resolve, so inject the `__cli` sentinel: the agent CLI wrapper
|
|
32170
|
+
// then execs `<exe> __cli "$@"`, re-execing this binary in CLI mode
|
|
32171
|
+
// (runBundledSlockCli). Normal installs leave this unset → the daemon
|
|
32172
|
+
// resolves the bundled CLI dist path as before.
|
|
32173
|
+
slockCliPath: isSeaBinary() ? "__cli" : void 0,
|
|
31852
32174
|
// Managed-Computer remote control (server → WS → runner). This runner
|
|
31853
32175
|
// is the service's in-process `__run` child, so:
|
|
31854
32176
|
// restart → SIGTERM ourselves → runResident's shutdown does
|
|
31855
32177
|
// core.stop() + exit 0 → classifyRunnerExit = graceful → the
|
|
31856
32178
|
// service supervisor respawns this runner (effective restart).
|
|
31857
|
-
// upgrade →
|
|
31858
|
-
//
|
|
31859
|
-
|
|
32179
|
+
// upgrade (SEA + requestId) → run the SEA upgrade IN-PROCESS so we can
|
|
32180
|
+
// stream `computer:upgrade:progress` over this live WS, then SIGTERM
|
|
32181
|
+
// ourselves; the supervisor respawns the swapped binary and the new
|
|
32182
|
+
// process reports `done` via onComputerUpgradeReconcile on reconnect.
|
|
32183
|
+
// upgrade (non-SEA dev run, or no requestId) → SEA-only no-op. The
|
|
32184
|
+
// npm-install-root upgrade path is retired: a managed Computer is a
|
|
32185
|
+
// SEA single binary; a source/dev run has no binary to self-swap.
|
|
32186
|
+
onComputerControl: (action, ctx) => {
|
|
31860
32187
|
if (action === "restart") {
|
|
31861
32188
|
process.kill(process.pid, "SIGTERM");
|
|
31862
32189
|
return;
|
|
31863
32190
|
}
|
|
31864
|
-
|
|
31865
|
-
|
|
31866
|
-
const child = spawn2(process.execPath, [...process.execArgv, selfEntry, "upgrade"], {
|
|
31867
|
-
detached: true,
|
|
31868
|
-
stdio: "ignore",
|
|
31869
|
-
windowsHide: true,
|
|
31870
|
-
// Attribute this upgrade to the web surface in upgrade.log — the
|
|
31871
|
-
// `upgrade` command reads SLOCK_UPGRADE_TRIGGER and defaults to
|
|
31872
|
-
// "cli" when unset.
|
|
31873
|
-
env: { ...process.env, SLOCK_UPGRADE_TRIGGER: "web" }
|
|
31874
|
-
});
|
|
31875
|
-
child.on("error", () => {
|
|
31876
|
-
});
|
|
31877
|
-
child.unref();
|
|
31878
|
-
} catch {
|
|
32191
|
+
if (isSeaBinary() && ctx.requestId) {
|
|
32192
|
+
return runManagedSeaUpgrade(ctx.requestId, ctx);
|
|
31879
32193
|
}
|
|
32194
|
+
console.warn(
|
|
32195
|
+
`[Computer] Ignoring upgrade request: in-process SEA upgrade unavailable (isSea=${isSeaBinary()}, requestId=${ctx.requestId ? "present" : "absent"}). Managed upgrade requires a SEA binary invoked with a requestId.`
|
|
32196
|
+
);
|
|
32197
|
+
},
|
|
32198
|
+
// Blip-stitch: on every (re)connect, if THIS process booted from a freshly
|
|
32199
|
+
// swapped binary (a pending-upgrade marker is present), report the upgrade
|
|
32200
|
+
// done with the version we actually are now, then clear the marker.
|
|
32201
|
+
onComputerUpgradeReconcile: async (emitDone) => {
|
|
32202
|
+
const slockHome = resolveSlockHome();
|
|
32203
|
+
const marker = await readPendingUpgradeMarker(slockHome);
|
|
32204
|
+
if (!marker) return;
|
|
32205
|
+
const rolledBack = COMPUTER_VERSION !== marker.targetVersion;
|
|
32206
|
+
emitDone({
|
|
32207
|
+
requestId: marker.requestId,
|
|
32208
|
+
ok: !rolledBack,
|
|
32209
|
+
newVersion: COMPUTER_VERSION,
|
|
32210
|
+
...rolledBack ? { rolledBack: true } : {}
|
|
32211
|
+
});
|
|
32212
|
+
await clearPendingUpgradeMarker(slockHome);
|
|
31880
32213
|
}
|
|
31881
32214
|
});
|
|
31882
32215
|
};
|
|
32216
|
+
function seaProgressToFrame(e) {
|
|
32217
|
+
return { phase: e.phase, message: e.message, percent: e.percent };
|
|
32218
|
+
}
|
|
32219
|
+
async function runManagedSeaUpgrade(requestId, ctx) {
|
|
32220
|
+
const slockHome = resolveSlockHome();
|
|
32221
|
+
let result;
|
|
32222
|
+
try {
|
|
32223
|
+
result = await resolveAndRunSeaUpgrade({
|
|
32224
|
+
slockHome,
|
|
32225
|
+
requestId,
|
|
32226
|
+
fromVersion: COMPUTER_VERSION,
|
|
32227
|
+
currentBinaryPath: process.execPath,
|
|
32228
|
+
nowIso: (/* @__PURE__ */ new Date()).toISOString(),
|
|
32229
|
+
deps: {
|
|
32230
|
+
onProgress: (e) => ctx.emitUpgradeProgress(seaProgressToFrame(e))
|
|
32231
|
+
}
|
|
32232
|
+
});
|
|
32233
|
+
} catch (err) {
|
|
32234
|
+
ctx.emitUpgradeDone({ ok: false, error: err instanceof Error ? err.message : String(err) });
|
|
32235
|
+
return;
|
|
32236
|
+
}
|
|
32237
|
+
if (!result.ok) {
|
|
32238
|
+
ctx.emitUpgradeDone({ ok: false, error: result.reason ?? result.failedPhase ?? "upgrade_failed" });
|
|
32239
|
+
return;
|
|
32240
|
+
}
|
|
32241
|
+
if (result.alreadyCurrent) {
|
|
32242
|
+
ctx.emitUpgradeDone({ ok: true, newVersion: COMPUTER_VERSION });
|
|
32243
|
+
return;
|
|
32244
|
+
}
|
|
32245
|
+
process.kill(process.pid, "SIGTERM");
|
|
32246
|
+
}
|
|
31883
32247
|
function classifyRunnerExit(code, signal) {
|
|
31884
32248
|
if (code === EX_CONFIG_EXIT_CODE) return "config-error";
|
|
31885
32249
|
if (signal === "SIGTERM" || signal === "SIGINT") return "graceful";
|
|
@@ -31918,6 +32282,9 @@ async function runResident(serverId, deps = {}) {
|
|
|
31918
32282
|
}
|
|
31919
32283
|
var RECONCILE_INTERVAL_MS = 5e3;
|
|
31920
32284
|
var CHILD_RESTART_BACKOFF_MS = 2e3;
|
|
32285
|
+
function canSupervisorSpawnChild(serverId, running, restarting) {
|
|
32286
|
+
return !running.has(serverId) && !restarting.has(serverId);
|
|
32287
|
+
}
|
|
31921
32288
|
function formatReadySummary(ready, serverIds, opts) {
|
|
31922
32289
|
if (opts.serverId && serverIds.length === 1) {
|
|
31923
32290
|
const pid = ready.get(opts.serverId);
|
|
@@ -31958,10 +32325,10 @@ async function runServiceStartupRecovery(slockHome) {
|
|
|
31958
32325
|
}
|
|
31959
32326
|
async function resolveServiceIdentity() {
|
|
31960
32327
|
const here = fileURLToPath2(import.meta.url);
|
|
31961
|
-
const installRoot =
|
|
32328
|
+
const installRoot = dirname9(dirname9(here));
|
|
31962
32329
|
let version = null;
|
|
31963
32330
|
try {
|
|
31964
|
-
const raw = await
|
|
32331
|
+
const raw = await readFile9(joinPath(installRoot, "package.json"), "utf8");
|
|
31965
32332
|
const parsed = JSON.parse(raw);
|
|
31966
32333
|
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
31967
32334
|
version = parsed.version;
|
|
@@ -31981,8 +32348,8 @@ async function writeServiceVersionEvidence(slockHome) {
|
|
|
31981
32348
|
};
|
|
31982
32349
|
const dest = serviceVersionPath(slockHome);
|
|
31983
32350
|
const tmp = `${dest}.tmp`;
|
|
31984
|
-
await
|
|
31985
|
-
await
|
|
32351
|
+
await writeFile9(tmp, JSON.stringify(payload) + "\n", { mode: 384 });
|
|
32352
|
+
await rename3(tmp, dest);
|
|
31986
32353
|
} catch (err) {
|
|
31987
32354
|
const msg = err instanceof Error ? err.message : String(err);
|
|
31988
32355
|
process.stderr.write(
|
|
@@ -31991,34 +32358,19 @@ async function writeServiceVersionEvidence(slockHome) {
|
|
|
31991
32358
|
);
|
|
31992
32359
|
}
|
|
31993
32360
|
}
|
|
31994
|
-
async function readServiceVersionEvidence(slockHome) {
|
|
31995
|
-
try {
|
|
31996
|
-
const raw = await readFile7(serviceVersionPath(slockHome), "utf8");
|
|
31997
|
-
const parsed = JSON.parse(raw);
|
|
31998
|
-
if (typeof parsed.installRoot !== "string" || typeof parsed.pid !== "number" || typeof parsed.writtenAt !== "string" || parsed.version !== null && typeof parsed.version !== "string") {
|
|
31999
|
-
return null;
|
|
32000
|
-
}
|
|
32001
|
-
return {
|
|
32002
|
-
version: parsed.version ?? null,
|
|
32003
|
-
installRoot: parsed.installRoot,
|
|
32004
|
-
pid: parsed.pid,
|
|
32005
|
-
writtenAt: parsed.writtenAt
|
|
32006
|
-
};
|
|
32007
|
-
} catch {
|
|
32008
|
-
return null;
|
|
32009
|
-
}
|
|
32010
|
-
}
|
|
32011
32361
|
async function runService() {
|
|
32012
32362
|
const slockHome = resolveSlockHome();
|
|
32013
|
-
await
|
|
32363
|
+
await mkdir10(serviceRunDir(slockHome), { recursive: true });
|
|
32014
32364
|
await runServiceStartupRecovery(slockHome);
|
|
32015
32365
|
await writePidfileAt(servicePidPath(slockHome), process.pid);
|
|
32016
32366
|
await writeServiceVersionEvidence(slockHome);
|
|
32017
32367
|
const children = /* @__PURE__ */ new Map();
|
|
32368
|
+
const restarting = /* @__PURE__ */ new Set();
|
|
32018
32369
|
const { [PARENT_LOCK_HELD_ENV_VAR]: _parentLockMarker, ...childEnv } = process.env;
|
|
32019
32370
|
const spawnChild = async (serverId) => {
|
|
32371
|
+
if (children.has(serverId)) return;
|
|
32020
32372
|
const logPath = serverRunnerLogPath(slockHome, serverId);
|
|
32021
|
-
await
|
|
32373
|
+
await mkdir10(dirname9(logPath), { recursive: true });
|
|
32022
32374
|
const logFd = await open(logPath, "a");
|
|
32023
32375
|
const { command, args } = buildResidentSpawn("__run", serverId);
|
|
32024
32376
|
const child = spawn2(command, args, {
|
|
@@ -32049,9 +32401,14 @@ async function runService() {
|
|
|
32049
32401
|
return;
|
|
32050
32402
|
}
|
|
32051
32403
|
if (classification === "graceful") {
|
|
32052
|
-
|
|
32053
|
-
|
|
32054
|
-
await
|
|
32404
|
+
restarting.add(serverId);
|
|
32405
|
+
try {
|
|
32406
|
+
await new Promise((r) => setTimeout(r, CHILD_RESTART_BACKOFF_MS));
|
|
32407
|
+
if (await readServerAttachment(slockHome, serverId) && !shuttingDown) {
|
|
32408
|
+
await spawnChild(serverId);
|
|
32409
|
+
}
|
|
32410
|
+
} finally {
|
|
32411
|
+
restarting.delete(serverId);
|
|
32055
32412
|
}
|
|
32056
32413
|
return;
|
|
32057
32414
|
}
|
|
@@ -32066,9 +32423,14 @@ async function runService() {
|
|
|
32066
32423
|
);
|
|
32067
32424
|
return;
|
|
32068
32425
|
}
|
|
32069
|
-
|
|
32070
|
-
|
|
32071
|
-
await
|
|
32426
|
+
restarting.add(serverId);
|
|
32427
|
+
try {
|
|
32428
|
+
await new Promise((r) => setTimeout(r, CHILD_RESTART_BACKOFF_MS));
|
|
32429
|
+
if (await readServerAttachment(slockHome, serverId) && !shuttingDown) {
|
|
32430
|
+
await spawnChild(serverId);
|
|
32431
|
+
}
|
|
32432
|
+
} finally {
|
|
32433
|
+
restarting.delete(serverId);
|
|
32072
32434
|
}
|
|
32073
32435
|
})();
|
|
32074
32436
|
});
|
|
@@ -32083,7 +32445,7 @@ async function runService() {
|
|
|
32083
32445
|
const reconcile = async () => {
|
|
32084
32446
|
const wanted = new Set(await listManagedServerIds(slockHome));
|
|
32085
32447
|
for (const id of wanted) {
|
|
32086
|
-
if (
|
|
32448
|
+
if (canSupervisorSpawnChild(id, children, restarting)) await spawnChild(id);
|
|
32087
32449
|
}
|
|
32088
32450
|
for (const [id, handle] of children) {
|
|
32089
32451
|
if (!wanted.has(id)) killChild(handle);
|
|
@@ -32219,7 +32581,7 @@ async function runDetach(serverId, serverLabel = serverId) {
|
|
|
32219
32581
|
var USER_SESSION_EXPIRY_LEEWAY_MS = 3e4;
|
|
32220
32582
|
async function readUserSessionAuth(slockHome) {
|
|
32221
32583
|
try {
|
|
32222
|
-
const parsed = JSON.parse(await
|
|
32584
|
+
const parsed = JSON.parse(await readFile10(userSessionPath(slockHome), "utf8"));
|
|
32223
32585
|
return {
|
|
32224
32586
|
accessToken: typeof parsed.accessToken === "string" ? parsed.accessToken : "",
|
|
32225
32587
|
...typeof parsed.serverUrl === "string" ? { serverUrl: parsed.serverUrl } : {}
|
|
@@ -32230,7 +32592,7 @@ async function readUserSessionAuth(slockHome) {
|
|
|
32230
32592
|
}
|
|
32231
32593
|
async function hasValidUserSession(slockHome) {
|
|
32232
32594
|
try {
|
|
32233
|
-
const parsed = JSON.parse(await
|
|
32595
|
+
const parsed = JSON.parse(await readFile10(userSessionPath(slockHome), "utf8"));
|
|
32234
32596
|
return parsed.kind === "user-session" && typeof parsed.accessToken === "string" && parsed.accessToken.length > 0 && !isJwtExpired(parsed.accessToken);
|
|
32235
32597
|
} catch {
|
|
32236
32598
|
return false;
|
|
@@ -32250,7 +32612,7 @@ async function refreshUserSession(slockHome, serverUrl) {
|
|
|
32250
32612
|
const file = userSessionPath(slockHome);
|
|
32251
32613
|
let session;
|
|
32252
32614
|
try {
|
|
32253
|
-
session = JSON.parse(await
|
|
32615
|
+
session = JSON.parse(await readFile10(file, "utf8"));
|
|
32254
32616
|
} catch {
|
|
32255
32617
|
return false;
|
|
32256
32618
|
}
|
|
@@ -32269,8 +32631,8 @@ async function refreshUserSession(slockHome, serverUrl) {
|
|
|
32269
32631
|
if (res.status !== 200 || typeof body?.accessToken !== "string" || typeof body.refreshToken !== "string") {
|
|
32270
32632
|
return false;
|
|
32271
32633
|
}
|
|
32272
|
-
await
|
|
32273
|
-
await
|
|
32634
|
+
await mkdir11(dirname10(file), { recursive: true });
|
|
32635
|
+
await writeFile10(
|
|
32274
32636
|
tmpFile,
|
|
32275
32637
|
JSON.stringify(
|
|
32276
32638
|
{
|
|
@@ -32287,11 +32649,11 @@ async function refreshUserSession(slockHome, serverUrl) {
|
|
|
32287
32649
|
),
|
|
32288
32650
|
{ mode: 384 }
|
|
32289
32651
|
);
|
|
32290
|
-
await
|
|
32291
|
-
await
|
|
32652
|
+
await chmod5(tmpFile, 384);
|
|
32653
|
+
await rename4(tmpFile, file);
|
|
32292
32654
|
return true;
|
|
32293
32655
|
} catch {
|
|
32294
|
-
await
|
|
32656
|
+
await rm3(tmpFile, { force: true }).catch(() => void 0);
|
|
32295
32657
|
return false;
|
|
32296
32658
|
}
|
|
32297
32659
|
}
|
|
@@ -32599,10 +32961,10 @@ async function runSetup(opts, deps = {}) {
|
|
|
32599
32961
|
|
|
32600
32962
|
// src/status.ts
|
|
32601
32963
|
init_esm_shims();
|
|
32602
|
-
import { readFile as
|
|
32964
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
32603
32965
|
async function readUserSession(path3) {
|
|
32604
32966
|
try {
|
|
32605
|
-
const parsed = JSON.parse(await
|
|
32967
|
+
const parsed = JSON.parse(await readFile11(path3, "utf8"));
|
|
32606
32968
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
32607
32969
|
return { state: "present", session: parsed, error: null };
|
|
32608
32970
|
}
|
|
@@ -32962,15 +33324,10 @@ async function runDoctorChecks() {
|
|
|
32962
33324
|
}
|
|
32963
33325
|
async function runDoctor(opts) {
|
|
32964
33326
|
const slockHome = resolveSlockHome();
|
|
32965
|
-
if (opts.resetHealth && opts.serverId) {
|
|
32966
|
-
await resetHealth(slockHome, opts.serverId);
|
|
32967
|
-
info(`Reset health state for server ${opts.serverLabel ?? opts.serverId}.`);
|
|
32968
|
-
info(`Service will resume auto-restart on next daemon exit.`);
|
|
32969
|
-
}
|
|
32970
33327
|
const checks = await runDoctorChecks();
|
|
32971
33328
|
const allOk = checks.every((c) => c.ok);
|
|
32972
33329
|
let cleanupReport = null;
|
|
32973
|
-
if (opts.cleanup
|
|
33330
|
+
if (opts.cleanup) {
|
|
32974
33331
|
cleanupReport = await runFullCleanup(slockHome);
|
|
32975
33332
|
}
|
|
32976
33333
|
let crashes = [];
|
|
@@ -33000,7 +33357,7 @@ async function runDoctor(opts) {
|
|
|
33000
33357
|
info(` - ${c.at}${code}${sig}`);
|
|
33001
33358
|
}
|
|
33002
33359
|
info(` \u2192 Once you fix the underlying issue, run`);
|
|
33003
|
-
info(` \`slock-computer
|
|
33360
|
+
info(` \`slock-computer reset --runner --server ${opts.serverLabel ?? opts.serverId}\` to resume auto-restart.`);
|
|
33004
33361
|
}
|
|
33005
33362
|
if (cleanupReport) {
|
|
33006
33363
|
info("");
|
|
@@ -33035,14 +33392,14 @@ async function runDoctor(opts) {
|
|
|
33035
33392
|
|
|
33036
33393
|
// src/logs.ts
|
|
33037
33394
|
init_esm_shims();
|
|
33038
|
-
import { readFile as
|
|
33395
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
33039
33396
|
var DEFAULT_LINES = 200;
|
|
33040
33397
|
async function runLogs(opts) {
|
|
33041
33398
|
const home = resolveSlockHome();
|
|
33042
33399
|
const file = opts.service ? serviceLogPath(home) : serverRunnerLogPath(home, await resolveTargetServerId({ server: opts.server }));
|
|
33043
33400
|
let content;
|
|
33044
33401
|
try {
|
|
33045
|
-
content = await
|
|
33402
|
+
content = await readFile12(file, "utf8");
|
|
33046
33403
|
} catch {
|
|
33047
33404
|
fail(
|
|
33048
33405
|
"NO_DAEMON_LOG",
|
|
@@ -33061,8 +33418,8 @@ init_esm_shims();
|
|
|
33061
33418
|
|
|
33062
33419
|
// src/serviceState.ts
|
|
33063
33420
|
init_esm_shims();
|
|
33064
|
-
import { mkdir as
|
|
33065
|
-
import { dirname as
|
|
33421
|
+
import { mkdir as mkdir12, readFile as readFile13, writeFile as writeFile11, appendFile as appendFile3 } from "fs/promises";
|
|
33422
|
+
import { dirname as dirname11 } from "path";
|
|
33066
33423
|
|
|
33067
33424
|
// src/lib/state.ts
|
|
33068
33425
|
init_esm_shims();
|
|
@@ -33084,7 +33441,7 @@ var DEFAULT_STATE = {
|
|
|
33084
33441
|
};
|
|
33085
33442
|
async function readServiceState(slockHome) {
|
|
33086
33443
|
try {
|
|
33087
|
-
const raw = await
|
|
33444
|
+
const raw = await readFile13(serviceStatePath(slockHome), "utf8");
|
|
33088
33445
|
const parsed = JSON.parse(raw);
|
|
33089
33446
|
if (!parsed || typeof parsed !== "object") return { ...DEFAULT_STATE };
|
|
33090
33447
|
const obj = parsed;
|
|
@@ -33097,8 +33454,8 @@ async function readServiceState(slockHome) {
|
|
|
33097
33454
|
}
|
|
33098
33455
|
async function writeServiceState(slockHome, file) {
|
|
33099
33456
|
const path3 = serviceStatePath(slockHome);
|
|
33100
|
-
await
|
|
33101
|
-
await
|
|
33457
|
+
await mkdir12(dirname11(path3), { recursive: true });
|
|
33458
|
+
await writeFile11(path3, JSON.stringify(file), { mode: 384 });
|
|
33102
33459
|
}
|
|
33103
33460
|
function isCrashEntry(value) {
|
|
33104
33461
|
if (!value || typeof value !== "object") return false;
|
|
@@ -33115,7 +33472,7 @@ async function emitServiceStateTransition(slockHome, fromState, toState, trigger
|
|
|
33115
33472
|
};
|
|
33116
33473
|
try {
|
|
33117
33474
|
const path3 = serviceLogPath(slockHome);
|
|
33118
|
-
await
|
|
33475
|
+
await mkdir12(dirname11(path3), { recursive: true });
|
|
33119
33476
|
await appendFile3(path3, JSON.stringify(entry) + "\n");
|
|
33120
33477
|
} catch {
|
|
33121
33478
|
}
|
|
@@ -33209,14 +33566,14 @@ async function runReset(opts) {
|
|
|
33209
33566
|
// src/concurrency.ts
|
|
33210
33567
|
init_esm_shims();
|
|
33211
33568
|
var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
|
|
33212
|
-
import { mkdir as
|
|
33213
|
-
import { join as
|
|
33569
|
+
import { mkdir as mkdir13 } from "fs/promises";
|
|
33570
|
+
import { join as join5 } from "path";
|
|
33214
33571
|
var STALE_LOCK_THRESHOLD_MS = 6e4;
|
|
33215
33572
|
async function withMutationLock(fn) {
|
|
33216
33573
|
const slockHome = resolveSlockHome();
|
|
33217
33574
|
const lockTarget = computerDir(slockHome);
|
|
33218
|
-
await
|
|
33219
|
-
const lockfilePath =
|
|
33575
|
+
await mkdir13(lockTarget, { recursive: true });
|
|
33576
|
+
const lockfilePath = join5(lockTarget, ".lock");
|
|
33220
33577
|
let release = null;
|
|
33221
33578
|
try {
|
|
33222
33579
|
release = await import_proper_lockfile.default.lock(lockTarget, {
|
|
@@ -33252,1060 +33609,15 @@ async function withMutationLock(fn) {
|
|
|
33252
33609
|
}
|
|
33253
33610
|
}
|
|
33254
33611
|
|
|
33255
|
-
// src/channel.ts
|
|
33256
|
-
init_esm_shims();
|
|
33257
|
-
import { readFile as readFile12, writeFile as writeFile10, mkdir as mkdir12 } from "fs/promises";
|
|
33258
|
-
import { dirname as dirname11 } from "path";
|
|
33259
|
-
var DEFAULT_CHANNEL = "latest";
|
|
33260
|
-
var SEMVER_RE = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
|
|
33261
|
-
function parseChannel(raw) {
|
|
33262
|
-
const v = raw.trim();
|
|
33263
|
-
if (v === "latest" || v === "alpha") return v;
|
|
33264
|
-
if (v.startsWith("pinned:")) {
|
|
33265
|
-
const semver = v.slice("pinned:".length);
|
|
33266
|
-
if (SEMVER_RE.test(semver)) return `pinned:${semver}`;
|
|
33267
|
-
return null;
|
|
33268
|
-
}
|
|
33269
|
-
return null;
|
|
33270
|
-
}
|
|
33271
|
-
async function readChannel(slockHome) {
|
|
33272
|
-
try {
|
|
33273
|
-
const raw = await readFile12(channelPath(slockHome), "utf8");
|
|
33274
|
-
const parsed = parseChannel(raw);
|
|
33275
|
-
if (parsed !== null) return parsed;
|
|
33276
|
-
} catch {
|
|
33277
|
-
}
|
|
33278
|
-
return DEFAULT_CHANNEL;
|
|
33279
|
-
}
|
|
33280
|
-
async function writeChannel(slockHome, channel2) {
|
|
33281
|
-
const p = channelPath(slockHome);
|
|
33282
|
-
await mkdir12(dirname11(p), { recursive: true });
|
|
33283
|
-
await writeFile10(p, `${channel2}
|
|
33284
|
-
`, { mode: 384 });
|
|
33285
|
-
}
|
|
33286
|
-
async function runChannelShow(slockHome) {
|
|
33287
|
-
void computerDir;
|
|
33288
|
-
const channel2 = await readChannel(slockHome);
|
|
33289
|
-
info(channel2);
|
|
33290
|
-
}
|
|
33291
|
-
async function runChannelSet(slockHome, raw) {
|
|
33292
|
-
const parsed = parseChannel(raw);
|
|
33293
|
-
if (parsed === null) {
|
|
33294
|
-
fail(
|
|
33295
|
-
"CHANNEL_INVALID",
|
|
33296
|
-
`Invalid channel "${raw}". Accepted: \`latest\`, \`alpha\`, or \`pinned:<semver>\` (e.g. pinned:0.52.2).`
|
|
33297
|
-
);
|
|
33298
|
-
}
|
|
33299
|
-
await writeChannel(slockHome, parsed);
|
|
33300
|
-
info(`Channel set to ${parsed}.`);
|
|
33301
|
-
info(`Note: service reads channel at startup; restart \`slock-computer start\` to apply.`);
|
|
33302
|
-
}
|
|
33303
|
-
|
|
33304
33612
|
// src/upgradeCli.ts
|
|
33305
33613
|
init_esm_shims();
|
|
33306
|
-
import { readFile as
|
|
33614
|
+
import { readFile as readFile14, rm as rm4 } from "fs/promises";
|
|
33307
33615
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
33308
|
-
import { dirname as dirname12, join as
|
|
33616
|
+
import { dirname as dirname12, join as join6 } from "path";
|
|
33309
33617
|
|
|
33310
|
-
// src/
|
|
33311
|
-
init_esm_shims();
|
|
33312
|
-
import { spawn as spawn4 } from "child_process";
|
|
33313
|
-
import { mkdir as mkdir13, readFile as readFile14, writeFile as writeFile11, rm as rm3, rename as rename4 } from "fs/promises";
|
|
33314
|
-
import { join as join6 } from "path";
|
|
33315
|
-
import { createHash as createHash3 } from "crypto";
|
|
33316
|
-
|
|
33317
|
-
// src/preflightDepDrift.ts
|
|
33618
|
+
// src/upgradeLog.ts
|
|
33318
33619
|
init_esm_shims();
|
|
33319
|
-
import {
|
|
33320
|
-
import { spawn as spawn3 } from "child_process";
|
|
33321
|
-
import { createRequire as createRequire2 } from "module";
|
|
33322
|
-
import { join as join5 } from "path";
|
|
33323
|
-
import { pathToFileURL } from "url";
|
|
33324
|
-
var DAEMON_PACKAGE_NAME = "@slock-ai/daemon";
|
|
33325
|
-
async function preflightDepDriftCheck(currentBinaryDir, stagedTarballPath, deps = {}) {
|
|
33326
|
-
const readTarball = deps.readTarballPackageJson ?? defaultReadTarballPackageJson;
|
|
33327
|
-
const readCurrent = deps.readCurrentPackageJson ?? defaultReadCurrentPackageJson;
|
|
33328
|
-
const satisfies = deps.semverSatisfies ?? defaultSemverSatisfies;
|
|
33329
|
-
const allowUnsafe = deps.allowUnsafeSpec === true;
|
|
33330
|
-
let targetPkg;
|
|
33331
|
-
let currentPkg;
|
|
33332
|
-
try {
|
|
33333
|
-
targetPkg = await readTarball(stagedTarballPath);
|
|
33334
|
-
} catch (e) {
|
|
33335
|
-
return {
|
|
33336
|
-
ok: false,
|
|
33337
|
-
reason: "read_failed",
|
|
33338
|
-
detail: `cannot read target tarball package.json: ${errMsg(e)}`
|
|
33339
|
-
};
|
|
33340
|
-
}
|
|
33341
|
-
try {
|
|
33342
|
-
currentPkg = await readCurrent(currentBinaryDir);
|
|
33343
|
-
} catch (e) {
|
|
33344
|
-
return {
|
|
33345
|
-
ok: false,
|
|
33346
|
-
reason: "read_failed",
|
|
33347
|
-
detail: `cannot read current binary package.json: ${errMsg(e)}`
|
|
33348
|
-
};
|
|
33349
|
-
}
|
|
33350
|
-
const targetSpec = targetPkg.dependencies?.[DAEMON_PACKAGE_NAME];
|
|
33351
|
-
const currentSpec = currentPkg.dependencies?.[DAEMON_PACKAGE_NAME];
|
|
33352
|
-
if (typeof targetSpec !== "string" || targetSpec.length === 0) {
|
|
33353
|
-
return {
|
|
33354
|
-
ok: false,
|
|
33355
|
-
reason: "read_failed",
|
|
33356
|
-
detail: `target tarball package.json has no ${DAEMON_PACKAGE_NAME} dependency entry`,
|
|
33357
|
-
currentSpec
|
|
33358
|
-
};
|
|
33359
|
-
}
|
|
33360
|
-
const unsafeTarget = isUnsafeSpec(targetSpec) || isUnparseableRange(targetSpec);
|
|
33361
|
-
if (!allowUnsafe && unsafeTarget) {
|
|
33362
|
-
return {
|
|
33363
|
-
ok: false,
|
|
33364
|
-
reason: "unsafe_spec",
|
|
33365
|
-
detail: `${DAEMON_PACKAGE_NAME} target spec is not a safe semver range (target="${targetSpec}"). Auto-upgrade only supports target semver ranges that can be hydrated and verified before swap.`,
|
|
33366
|
-
currentSpec,
|
|
33367
|
-
targetSpec
|
|
33368
|
-
};
|
|
33369
|
-
}
|
|
33370
|
-
void satisfies;
|
|
33371
|
-
return { ok: true, currentSpec, targetSpec };
|
|
33372
|
-
}
|
|
33373
|
-
async function verifyHydratedDaemonDependency(extractedPackageDir, targetSpec, deps = {}) {
|
|
33374
|
-
const readInstalled = deps.readInstalledDaemonVersion ?? defaultReadInstalledDaemonVersion;
|
|
33375
|
-
const satisfies = deps.semverSatisfies ?? defaultSemverSatisfies;
|
|
33376
|
-
const allowUnsafe = deps.allowUnsafeSpec === true;
|
|
33377
|
-
const unsafeTarget = isUnsafeSpec(targetSpec) || isUnparseableRange(targetSpec);
|
|
33378
|
-
if (!allowUnsafe && unsafeTarget) {
|
|
33379
|
-
return {
|
|
33380
|
-
ok: false,
|
|
33381
|
-
reason: "unsafe_spec",
|
|
33382
|
-
detail: `${DAEMON_PACKAGE_NAME} target spec is not a safe semver range (target="${targetSpec}").`,
|
|
33383
|
-
targetSpec
|
|
33384
|
-
};
|
|
33385
|
-
}
|
|
33386
|
-
let installedVersion;
|
|
33387
|
-
try {
|
|
33388
|
-
installedVersion = await readInstalled(extractedPackageDir);
|
|
33389
|
-
} catch (e) {
|
|
33390
|
-
return {
|
|
33391
|
-
ok: false,
|
|
33392
|
-
reason: "read_failed",
|
|
33393
|
-
detail: `cannot read hydrated ${DAEMON_PACKAGE_NAME} version: ${errMsg(e)}`,
|
|
33394
|
-
targetSpec
|
|
33395
|
-
};
|
|
33396
|
-
}
|
|
33397
|
-
if (installedVersion === null || installedVersion.length === 0) {
|
|
33398
|
-
return {
|
|
33399
|
-
ok: false,
|
|
33400
|
-
reason: "read_failed",
|
|
33401
|
-
detail: `${DAEMON_PACKAGE_NAME} is not installed in the hydrated staged package`,
|
|
33402
|
-
targetSpec
|
|
33403
|
-
};
|
|
33404
|
-
}
|
|
33405
|
-
if (allowUnsafe && unsafeTarget) {
|
|
33406
|
-
return { ok: true, targetSpec, installedVersion };
|
|
33407
|
-
}
|
|
33408
|
-
if (!satisfies(installedVersion, targetSpec)) {
|
|
33409
|
-
return {
|
|
33410
|
-
ok: false,
|
|
33411
|
-
reason: "installed_unsatisfied",
|
|
33412
|
-
detail: `hydrated ${DAEMON_PACKAGE_NAME}@${installedVersion} does not satisfy target spec "${targetSpec}".`,
|
|
33413
|
-
targetSpec,
|
|
33414
|
-
installedVersion
|
|
33415
|
-
};
|
|
33416
|
-
}
|
|
33417
|
-
return { ok: true, targetSpec, installedVersion };
|
|
33418
|
-
}
|
|
33419
|
-
function isUnparseableRange(spec) {
|
|
33420
|
-
const s = spec.trim();
|
|
33421
|
-
if (s.length === 0) return true;
|
|
33422
|
-
if (/\s/.test(s) && !s.includes("||")) {
|
|
33423
|
-
return s.split(/\s+/).some((p) => isUnparseableRange(p));
|
|
33424
|
-
}
|
|
33425
|
-
if (s.includes("||")) {
|
|
33426
|
-
return s.split("||").every((p) => isUnparseableRange(p.trim()));
|
|
33427
|
-
}
|
|
33428
|
-
if (s.includes("x") || s.split(".").length < 3) {
|
|
33429
|
-
const canon = s.replace(/\.x/g, ".0");
|
|
33430
|
-
const padded = canon.split(".").length < 3 ? `${canon}.0.0`.split(".").slice(0, 3).join(".") : canon;
|
|
33431
|
-
return parseSemver(padded) === null;
|
|
33432
|
-
}
|
|
33433
|
-
let core = s;
|
|
33434
|
-
if (core.startsWith(">=") || core.startsWith("<=")) core = core.slice(2);
|
|
33435
|
-
else if (core.startsWith("^") || core.startsWith("~") || core.startsWith(">") || core.startsWith("<") || core.startsWith("=")) {
|
|
33436
|
-
core = core.slice(1);
|
|
33437
|
-
}
|
|
33438
|
-
return parseSemver(core) === null;
|
|
33439
|
-
}
|
|
33440
|
-
function isUnsafeSpec(spec) {
|
|
33441
|
-
const s = spec.trim();
|
|
33442
|
-
if (s.length === 0) return true;
|
|
33443
|
-
if (s === "*" || s === "latest") return true;
|
|
33444
|
-
if (s.startsWith("workspace:")) return true;
|
|
33445
|
-
if (s.startsWith("file:") || s.startsWith("link:")) return true;
|
|
33446
|
-
if (s.startsWith("git+") || s.startsWith("git:")) return true;
|
|
33447
|
-
if (s.startsWith("github:") || /^[^/\s]+\/[^/\s]+(?:#.*)?$/.test(s)) {
|
|
33448
|
-
return true;
|
|
33449
|
-
}
|
|
33450
|
-
if (s.startsWith("http://") || s.startsWith("https://")) return true;
|
|
33451
|
-
if (s.startsWith("npm:")) return true;
|
|
33452
|
-
if (s.startsWith("./") || s.startsWith("../") || s.startsWith("/")) return true;
|
|
33453
|
-
return false;
|
|
33454
|
-
}
|
|
33455
|
-
function errMsg(e) {
|
|
33456
|
-
return e instanceof Error ? e.message : String(e);
|
|
33457
|
-
}
|
|
33458
|
-
async function defaultReadTarballPackageJson(tarballPath) {
|
|
33459
|
-
const raw = await new Promise((resolve2, reject) => {
|
|
33460
|
-
const child = spawn3("tar", ["-xzOf", tarballPath, "package/package.json"], {
|
|
33461
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
33462
|
-
});
|
|
33463
|
-
let stdoutBuf = "";
|
|
33464
|
-
let stderrBuf = "";
|
|
33465
|
-
child.stdout.on("data", (chunk) => {
|
|
33466
|
-
stdoutBuf += String(chunk);
|
|
33467
|
-
});
|
|
33468
|
-
child.stderr.on("data", (chunk) => {
|
|
33469
|
-
stderrBuf += String(chunk);
|
|
33470
|
-
});
|
|
33471
|
-
child.on("error", (err) => reject(err));
|
|
33472
|
-
child.on("close", (code) => {
|
|
33473
|
-
if (code !== 0) {
|
|
33474
|
-
reject(new Error(`tar -xzOf exited ${code}: ${stderrBuf.slice(0, 300)}`));
|
|
33475
|
-
return;
|
|
33476
|
-
}
|
|
33477
|
-
resolve2(stdoutBuf);
|
|
33478
|
-
});
|
|
33479
|
-
});
|
|
33480
|
-
return JSON.parse(raw);
|
|
33481
|
-
}
|
|
33482
|
-
async function defaultReadCurrentPackageJson(currentBinaryDir) {
|
|
33483
|
-
const raw = await readFile13(join5(currentBinaryDir, "package.json"), "utf8");
|
|
33484
|
-
return JSON.parse(raw);
|
|
33485
|
-
}
|
|
33486
|
-
async function defaultReadInstalledDaemonVersion(currentBinaryDir) {
|
|
33487
|
-
let searchPaths;
|
|
33488
|
-
try {
|
|
33489
|
-
const anchor = pathToFileURL(join5(currentBinaryDir, "package.json"));
|
|
33490
|
-
const req = createRequire2(anchor);
|
|
33491
|
-
searchPaths = req.resolve.paths(DAEMON_PACKAGE_NAME);
|
|
33492
|
-
} catch {
|
|
33493
|
-
return null;
|
|
33494
|
-
}
|
|
33495
|
-
if (!searchPaths || searchPaths.length === 0) return null;
|
|
33496
|
-
const subPath = DAEMON_PACKAGE_NAME.split("/");
|
|
33497
|
-
for (const base of searchPaths) {
|
|
33498
|
-
const candidate = join5(base, ...subPath, "package.json");
|
|
33499
|
-
try {
|
|
33500
|
-
const raw = await readFile13(candidate, "utf8");
|
|
33501
|
-
const parsed = JSON.parse(raw);
|
|
33502
|
-
if (parsed.name !== DAEMON_PACKAGE_NAME) continue;
|
|
33503
|
-
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
33504
|
-
return parsed.version;
|
|
33505
|
-
}
|
|
33506
|
-
return null;
|
|
33507
|
-
} catch {
|
|
33508
|
-
}
|
|
33509
|
-
}
|
|
33510
|
-
return null;
|
|
33511
|
-
}
|
|
33512
|
-
function defaultSemverSatisfies(version, range) {
|
|
33513
|
-
const ver = parseSemver(version);
|
|
33514
|
-
if (ver === null) return false;
|
|
33515
|
-
const trimmed = range.trim();
|
|
33516
|
-
if (trimmed.length === 0) return false;
|
|
33517
|
-
if (/\s/.test(trimmed) && !trimmed.includes("||")) {
|
|
33518
|
-
return trimmed.split(/\s+/).every((part) => defaultSemverSatisfies(version, part));
|
|
33519
|
-
}
|
|
33520
|
-
if (trimmed.includes("||")) {
|
|
33521
|
-
return trimmed.split("||").some((part) => defaultSemverSatisfies(version, part.trim()));
|
|
33522
|
-
}
|
|
33523
|
-
if (trimmed.startsWith("^")) {
|
|
33524
|
-
return satisfiesCaret(ver, trimmed.slice(1));
|
|
33525
|
-
}
|
|
33526
|
-
if (trimmed.startsWith("~")) {
|
|
33527
|
-
return satisfiesTilde(ver, trimmed.slice(1));
|
|
33528
|
-
}
|
|
33529
|
-
if (trimmed.startsWith(">=")) return cmp(ver, trimmed.slice(2)) >= 0;
|
|
33530
|
-
if (trimmed.startsWith("<=")) return cmp(ver, trimmed.slice(2)) <= 0;
|
|
33531
|
-
if (trimmed.startsWith(">")) return cmp(ver, trimmed.slice(1)) > 0;
|
|
33532
|
-
if (trimmed.startsWith("<")) return cmp(ver, trimmed.slice(1)) < 0;
|
|
33533
|
-
if (trimmed.startsWith("=")) return cmp(ver, trimmed.slice(1)) === 0;
|
|
33534
|
-
if (trimmed.includes("x") || trimmed.split(".").length < 3) {
|
|
33535
|
-
const canon = trimmed.replace(/\.x/g, ".0");
|
|
33536
|
-
const padded = canon.split(".").length < 3 ? `${canon}.0.0`.split(".").slice(0, 3).join(".") : canon;
|
|
33537
|
-
return satisfiesCaret(ver, padded);
|
|
33538
|
-
}
|
|
33539
|
-
return cmp(ver, trimmed) === 0;
|
|
33540
|
-
}
|
|
33541
|
-
function parseSemver(s) {
|
|
33542
|
-
const m = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(s.trim());
|
|
33543
|
-
if (!m) return null;
|
|
33544
|
-
return {
|
|
33545
|
-
major: Number.parseInt(m[1], 10),
|
|
33546
|
-
minor: Number.parseInt(m[2], 10),
|
|
33547
|
-
patch: Number.parseInt(m[3], 10),
|
|
33548
|
-
pre: m[4] ?? ""
|
|
33549
|
-
};
|
|
33550
|
-
}
|
|
33551
|
-
function cmp(a, bStr) {
|
|
33552
|
-
const b = parseSemver(bStr);
|
|
33553
|
-
if (b === null) return Number.NaN;
|
|
33554
|
-
if (a.major !== b.major) return a.major - b.major;
|
|
33555
|
-
if (a.minor !== b.minor) return a.minor - b.minor;
|
|
33556
|
-
if (a.patch !== b.patch) return a.patch - b.patch;
|
|
33557
|
-
if (a.pre === "" && b.pre !== "") return 1;
|
|
33558
|
-
if (a.pre !== "" && b.pre === "") return -1;
|
|
33559
|
-
if (a.pre === b.pre) return 0;
|
|
33560
|
-
return a.pre < b.pre ? -1 : 1;
|
|
33561
|
-
}
|
|
33562
|
-
function satisfiesCaret(ver, base) {
|
|
33563
|
-
const b = parseSemver(base);
|
|
33564
|
-
if (b === null) return false;
|
|
33565
|
-
if (cmp(ver, base) < 0) return false;
|
|
33566
|
-
if (b.major > 0) return ver.major === b.major;
|
|
33567
|
-
if (b.minor > 0) return ver.major === 0 && ver.minor === b.minor;
|
|
33568
|
-
return ver.major === 0 && ver.minor === 0 && ver.patch === b.patch;
|
|
33569
|
-
}
|
|
33570
|
-
function satisfiesTilde(ver, base) {
|
|
33571
|
-
const b = parseSemver(base);
|
|
33572
|
-
if (b === null) return false;
|
|
33573
|
-
if (cmp(ver, base) < 0) return false;
|
|
33574
|
-
return ver.major === b.major && ver.minor === b.minor;
|
|
33575
|
-
}
|
|
33576
|
-
|
|
33577
|
-
// src/upgrade.ts
|
|
33578
|
-
async function stagePhase(slockHome, version, deps = {}) {
|
|
33579
|
-
const npmPack = deps.npmPack ?? defaultNpmPack;
|
|
33580
|
-
const fsReadFile = deps.fsReadFile ?? readFile14;
|
|
33581
|
-
const stagedPath = upgradeStagingDir(slockHome, version);
|
|
33582
|
-
await mkdir13(stagedPath, { recursive: true });
|
|
33583
|
-
const packageRef = `@slock-ai/computer@${version}`;
|
|
33584
|
-
const result = await npmPack(stagedPath, packageRef);
|
|
33585
|
-
if (result.exitCode !== 0) {
|
|
33586
|
-
const err = new Error(
|
|
33587
|
-
`npm pack failed for ${packageRef}: exit ${result.exitCode}. stderr: ${result.stderr.slice(0, 500)}`
|
|
33588
|
-
);
|
|
33589
|
-
err.code = "UPGRADE_DOWNLOAD_FAILED";
|
|
33590
|
-
throw err;
|
|
33591
|
-
}
|
|
33592
|
-
let tarballBytes;
|
|
33593
|
-
try {
|
|
33594
|
-
tarballBytes = await fsReadFile(result.tarballPath);
|
|
33595
|
-
} catch (e) {
|
|
33596
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
33597
|
-
const err = new Error(`staged tarball missing at ${result.tarballPath}: ${msg}`);
|
|
33598
|
-
err.code = "UPGRADE_DOWNLOAD_FAILED";
|
|
33599
|
-
throw err;
|
|
33600
|
-
}
|
|
33601
|
-
const tarballSha1 = createHash3("sha1").update(tarballBytes).digest("hex");
|
|
33602
|
-
return { stagedPath, version, tarballSha1 };
|
|
33603
|
-
}
|
|
33604
|
-
function defaultNpmPack(cwd, packageRef) {
|
|
33605
|
-
return new Promise((resolve2) => {
|
|
33606
|
-
const child = spawn4("npm", ["pack", packageRef, "--json"], {
|
|
33607
|
-
cwd,
|
|
33608
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
33609
|
-
});
|
|
33610
|
-
let stdoutBuf = "";
|
|
33611
|
-
let stderrBuf = "";
|
|
33612
|
-
child.stdout.on("data", (chunk) => {
|
|
33613
|
-
stdoutBuf += String(chunk);
|
|
33614
|
-
});
|
|
33615
|
-
child.stderr.on("data", (chunk) => {
|
|
33616
|
-
stderrBuf += String(chunk);
|
|
33617
|
-
});
|
|
33618
|
-
child.on("error", (err) => {
|
|
33619
|
-
resolve2({ tarballPath: "", exitCode: 1, stderr: String(err) });
|
|
33620
|
-
});
|
|
33621
|
-
child.on("close", (code) => {
|
|
33622
|
-
if (code !== 0) {
|
|
33623
|
-
resolve2({ tarballPath: "", exitCode: code ?? 1, stderr: stderrBuf });
|
|
33624
|
-
return;
|
|
33625
|
-
}
|
|
33626
|
-
try {
|
|
33627
|
-
const parsed = JSON.parse(stdoutBuf);
|
|
33628
|
-
const filename = parsed[0]?.filename;
|
|
33629
|
-
if (!filename) {
|
|
33630
|
-
resolve2({ tarballPath: "", exitCode: 1, stderr: "npm pack returned no filename" });
|
|
33631
|
-
return;
|
|
33632
|
-
}
|
|
33633
|
-
resolve2({ tarballPath: join6(cwd, filename), exitCode: 0, stderr: stderrBuf });
|
|
33634
|
-
} catch (e) {
|
|
33635
|
-
resolve2({ tarballPath: "", exitCode: 1, stderr: `parse npm pack output: ${e}` });
|
|
33636
|
-
}
|
|
33637
|
-
});
|
|
33638
|
-
});
|
|
33639
|
-
}
|
|
33640
|
-
async function verifyPhase(staged, deps = {}) {
|
|
33641
|
-
const fetchAdvertisedHash = deps.fetchAdvertisedHash ?? defaultFetchAdvertisedHash;
|
|
33642
|
-
let advertised;
|
|
33643
|
-
try {
|
|
33644
|
-
advertised = await fetchAdvertisedHash(staged.version);
|
|
33645
|
-
} catch (e) {
|
|
33646
|
-
void e;
|
|
33647
|
-
return { ok: false, reason: "registry_fetch_failed" };
|
|
33648
|
-
}
|
|
33649
|
-
if (advertised === null) {
|
|
33650
|
-
return { ok: false, reason: "no_advertised_sha" };
|
|
33651
|
-
}
|
|
33652
|
-
if (staged.tarballSha1 !== advertised) {
|
|
33653
|
-
return {
|
|
33654
|
-
ok: false,
|
|
33655
|
-
reason: "checksum_mismatch",
|
|
33656
|
-
actual: staged.tarballSha1,
|
|
33657
|
-
advertised
|
|
33658
|
-
};
|
|
33659
|
-
}
|
|
33660
|
-
return { ok: true, actual: staged.tarballSha1, advertised };
|
|
33661
|
-
}
|
|
33662
|
-
async function defaultFetchAdvertisedHash(version) {
|
|
33663
|
-
const url = "https://registry.npmjs.org/@slock-ai/computer";
|
|
33664
|
-
const controller = new AbortController();
|
|
33665
|
-
const timeoutId = setTimeout(() => controller.abort(), 1e4);
|
|
33666
|
-
try {
|
|
33667
|
-
const res = await fetch(url, {
|
|
33668
|
-
signal: controller.signal,
|
|
33669
|
-
headers: { accept: "application/json" }
|
|
33670
|
-
});
|
|
33671
|
-
if (!res.ok) return null;
|
|
33672
|
-
const data = await res.json();
|
|
33673
|
-
const shasum = data.versions?.[version]?.dist?.shasum;
|
|
33674
|
-
return typeof shasum === "string" && shasum.length > 0 ? shasum : null;
|
|
33675
|
-
} catch {
|
|
33676
|
-
return null;
|
|
33677
|
-
} finally {
|
|
33678
|
-
clearTimeout(timeoutId);
|
|
33679
|
-
}
|
|
33680
|
-
}
|
|
33681
|
-
async function cleanupStaged(slockHome, version) {
|
|
33682
|
-
try {
|
|
33683
|
-
await rm3(upgradeStagingDir(slockHome, version), { recursive: true, force: true });
|
|
33684
|
-
} catch {
|
|
33685
|
-
}
|
|
33686
|
-
}
|
|
33687
|
-
async function snapshotPhase(slockHome, snap) {
|
|
33688
|
-
const path3 = upgradeSnapshotPath(slockHome);
|
|
33689
|
-
const tmp = `${path3}.tmp`;
|
|
33690
|
-
await mkdir13(computerDir(slockHome), { recursive: true });
|
|
33691
|
-
await writeFile11(tmp, JSON.stringify(snap, null, 2), { mode: 384 });
|
|
33692
|
-
await rename4(tmp, path3);
|
|
33693
|
-
}
|
|
33694
|
-
async function clearUpgradeSnapshot(slockHome) {
|
|
33695
|
-
try {
|
|
33696
|
-
const { unlink: unlink5 } = await import("fs/promises");
|
|
33697
|
-
await unlink5(upgradeSnapshotPath(slockHome));
|
|
33698
|
-
} catch {
|
|
33699
|
-
}
|
|
33700
|
-
}
|
|
33701
|
-
async function extractTarball(tarballPath, destDir, deps = {}) {
|
|
33702
|
-
const tarSpawn = deps.tarSpawn ?? defaultTarSpawn;
|
|
33703
|
-
await mkdir13(destDir, { recursive: true });
|
|
33704
|
-
const result = await tarSpawn(tarballPath, destDir);
|
|
33705
|
-
if (result.exitCode !== 0) {
|
|
33706
|
-
const err = new Error(
|
|
33707
|
-
`tar -xzf failed for ${tarballPath}: exit ${result.exitCode}. stderr: ${result.stderr.slice(0, 500)}`
|
|
33708
|
-
);
|
|
33709
|
-
err.code = "UPGRADE_EXTRACT_FAILED";
|
|
33710
|
-
throw err;
|
|
33711
|
-
}
|
|
33712
|
-
return { extractedPackageDir: join6(destDir, "package") };
|
|
33713
|
-
}
|
|
33714
|
-
function defaultTarSpawn(tarballPath, destDir) {
|
|
33715
|
-
return new Promise((resolve2) => {
|
|
33716
|
-
const child = spawn4("tar", ["-xzf", tarballPath, "-C", destDir], {
|
|
33717
|
-
stdio: ["ignore", "ignore", "pipe"]
|
|
33718
|
-
});
|
|
33719
|
-
let stderrBuf = "";
|
|
33720
|
-
child.stderr.on("data", (chunk) => {
|
|
33721
|
-
stderrBuf += String(chunk);
|
|
33722
|
-
});
|
|
33723
|
-
child.on("error", (err) => {
|
|
33724
|
-
resolve2({ exitCode: 1, stderr: String(err) });
|
|
33725
|
-
});
|
|
33726
|
-
child.on("close", (code) => {
|
|
33727
|
-
resolve2({ exitCode: code ?? 1, stderr: stderrBuf });
|
|
33728
|
-
});
|
|
33729
|
-
});
|
|
33730
|
-
}
|
|
33731
|
-
async function hydrateStagedDependencies(extractedPackageDir, deps = {}) {
|
|
33732
|
-
const npmInstall = deps.npmInstall ?? defaultNpmInstallProductionDeps;
|
|
33733
|
-
const result = await npmInstall(extractedPackageDir);
|
|
33734
|
-
if (result.exitCode !== 0) {
|
|
33735
|
-
const err = new Error(
|
|
33736
|
-
`npm install failed for staged package dependencies: exit ${result.exitCode}`
|
|
33737
|
-
);
|
|
33738
|
-
err.code = "UPGRADE_DEPENDENCY_INSTALL_FAILED";
|
|
33739
|
-
throw err;
|
|
33740
|
-
}
|
|
33741
|
-
}
|
|
33742
|
-
function defaultNpmInstallProductionDeps(cwd) {
|
|
33743
|
-
return new Promise((resolve2) => {
|
|
33744
|
-
const child = spawn4(
|
|
33745
|
-
"npm",
|
|
33746
|
-
[
|
|
33747
|
-
"install",
|
|
33748
|
-
"--omit=dev",
|
|
33749
|
-
"--ignore-scripts",
|
|
33750
|
-
"--no-audit",
|
|
33751
|
-
"--no-fund",
|
|
33752
|
-
"--package-lock=false"
|
|
33753
|
-
],
|
|
33754
|
-
{
|
|
33755
|
-
cwd,
|
|
33756
|
-
stdio: ["ignore", "ignore", "pipe"]
|
|
33757
|
-
}
|
|
33758
|
-
);
|
|
33759
|
-
let stderrBuf = "";
|
|
33760
|
-
child.stderr.on("data", (chunk) => {
|
|
33761
|
-
stderrBuf += String(chunk);
|
|
33762
|
-
});
|
|
33763
|
-
child.on("error", (err) => {
|
|
33764
|
-
resolve2({ exitCode: 1, stderr: String(err) });
|
|
33765
|
-
});
|
|
33766
|
-
child.on("close", (code) => {
|
|
33767
|
-
resolve2({ exitCode: code ?? 1, stderr: stderrBuf });
|
|
33768
|
-
});
|
|
33769
|
-
});
|
|
33770
|
-
}
|
|
33771
|
-
async function swapPhase(currentBinaryDir, stagedBinaryDir, deps = {}) {
|
|
33772
|
-
const fsRename = deps.fsRename ?? rename4;
|
|
33773
|
-
const fsRm = deps.fsRm ?? ((p) => rm3(p, { recursive: true, force: true }));
|
|
33774
|
-
const prevBinaryDir = `${currentBinaryDir}.prev`;
|
|
33775
|
-
try {
|
|
33776
|
-
await fsRm(prevBinaryDir);
|
|
33777
|
-
} catch {
|
|
33778
|
-
}
|
|
33779
|
-
await fsRename(currentBinaryDir, prevBinaryDir);
|
|
33780
|
-
try {
|
|
33781
|
-
await fsRename(stagedBinaryDir, currentBinaryDir);
|
|
33782
|
-
} catch (e) {
|
|
33783
|
-
try {
|
|
33784
|
-
await fsRename(prevBinaryDir, currentBinaryDir);
|
|
33785
|
-
} catch {
|
|
33786
|
-
}
|
|
33787
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
33788
|
-
const err = new Error(`swap failed (staged \u2192 current): ${msg}`);
|
|
33789
|
-
err.code = "UPGRADE_SWAP_FAILED";
|
|
33790
|
-
throw err;
|
|
33791
|
-
}
|
|
33792
|
-
return { prevBinaryDir, newBinaryDir: currentBinaryDir };
|
|
33793
|
-
}
|
|
33794
|
-
async function rollbackSwap(currentBinaryDir, deps = {}) {
|
|
33795
|
-
const fsRename = deps.fsRename ?? rename4;
|
|
33796
|
-
const fsRm = deps.fsRm ?? ((p) => rm3(p, { recursive: true, force: true }));
|
|
33797
|
-
const prevBinaryDir = `${currentBinaryDir}.prev`;
|
|
33798
|
-
try {
|
|
33799
|
-
await fsRm(currentBinaryDir);
|
|
33800
|
-
await fsRename(prevBinaryDir, currentBinaryDir);
|
|
33801
|
-
return { rolledBack: true };
|
|
33802
|
-
} catch {
|
|
33803
|
-
return { rolledBack: false };
|
|
33804
|
-
}
|
|
33805
|
-
}
|
|
33806
|
-
async function rollbackRestart(slockHome, currentBinaryDir, deps = {}) {
|
|
33807
|
-
const readServicePid = deps.readServicePid ?? (() => defaultReadServicePid(slockHome));
|
|
33808
|
-
const isProcessAlive4 = deps.isProcessAlive ?? defaultIsProcessAlive;
|
|
33809
|
-
const killService = deps.killService ?? defaultKillService;
|
|
33810
|
-
const forceKillService = deps.forceKillService ?? defaultForceKillService;
|
|
33811
|
-
const waitForExit = deps.waitForExit ?? defaultWaitForExit;
|
|
33812
|
-
const spawnFreshService = deps.spawnFreshService;
|
|
33813
|
-
const healthCheck = deps.healthCheck ?? (() => defaultHealthCheck(slockHome));
|
|
33814
|
-
const healthTimeoutMs = deps.healthTimeoutMs ?? 3e4;
|
|
33815
|
-
const healthPollIntervalMs = deps.healthPollIntervalMs ?? 500;
|
|
33816
|
-
let serviceStopped = false;
|
|
33817
|
-
try {
|
|
33818
|
-
const pid = await readServicePid();
|
|
33819
|
-
if (pid === null || !isProcessAlive4(pid)) {
|
|
33820
|
-
serviceStopped = true;
|
|
33821
|
-
} else {
|
|
33822
|
-
await killService(pid);
|
|
33823
|
-
const exited = await waitForExit(pid, 1e4);
|
|
33824
|
-
if (!exited) {
|
|
33825
|
-
await forceKillService(pid);
|
|
33826
|
-
const escalatedExit = await waitForExit(pid, 5e3);
|
|
33827
|
-
serviceStopped = escalatedExit;
|
|
33828
|
-
} else {
|
|
33829
|
-
serviceStopped = true;
|
|
33830
|
-
}
|
|
33831
|
-
}
|
|
33832
|
-
} catch (e) {
|
|
33833
|
-
return {
|
|
33834
|
-
binaryRestored: false,
|
|
33835
|
-
serviceStopped: false,
|
|
33836
|
-
serviceRespawned: false,
|
|
33837
|
-
serviceHealthy: false,
|
|
33838
|
-
failedStep: "stop",
|
|
33839
|
-
reason: e instanceof Error ? e.message : String(e)
|
|
33840
|
-
};
|
|
33841
|
-
}
|
|
33842
|
-
if (!serviceStopped) {
|
|
33843
|
-
return {
|
|
33844
|
-
binaryRestored: false,
|
|
33845
|
-
serviceStopped: false,
|
|
33846
|
-
serviceRespawned: false,
|
|
33847
|
-
serviceHealthy: false,
|
|
33848
|
-
failedStep: "stop",
|
|
33849
|
-
reason: "new-version service did not exit (graceful + SIGKILL both timed out)"
|
|
33850
|
-
};
|
|
33851
|
-
}
|
|
33852
|
-
const swapRb = await rollbackSwap(currentBinaryDir, {
|
|
33853
|
-
fsRename: deps.fsRename,
|
|
33854
|
-
fsRm: deps.fsRm
|
|
33855
|
-
});
|
|
33856
|
-
if (!swapRb.rolledBack) {
|
|
33857
|
-
return {
|
|
33858
|
-
binaryRestored: false,
|
|
33859
|
-
serviceStopped: true,
|
|
33860
|
-
serviceRespawned: false,
|
|
33861
|
-
serviceHealthy: false,
|
|
33862
|
-
failedStep: "binary",
|
|
33863
|
-
reason: "could not restore .prev \u2192 currentBinaryDir"
|
|
33864
|
-
};
|
|
33865
|
-
}
|
|
33866
|
-
if (!spawnFreshService) {
|
|
33867
|
-
return {
|
|
33868
|
-
binaryRestored: true,
|
|
33869
|
-
serviceStopped: true,
|
|
33870
|
-
serviceRespawned: false,
|
|
33871
|
-
serviceHealthy: false,
|
|
33872
|
-
failedStep: "respawn",
|
|
33873
|
-
reason: "no spawnFreshService callback wired; old service not respawned"
|
|
33874
|
-
};
|
|
33875
|
-
}
|
|
33876
|
-
try {
|
|
33877
|
-
await spawnFreshService();
|
|
33878
|
-
} catch (e) {
|
|
33879
|
-
return {
|
|
33880
|
-
binaryRestored: true,
|
|
33881
|
-
serviceStopped: true,
|
|
33882
|
-
serviceRespawned: false,
|
|
33883
|
-
serviceHealthy: false,
|
|
33884
|
-
failedStep: "respawn",
|
|
33885
|
-
reason: e instanceof Error ? e.message : String(e)
|
|
33886
|
-
};
|
|
33887
|
-
}
|
|
33888
|
-
const start2 = Date.now();
|
|
33889
|
-
let healthy = false;
|
|
33890
|
-
while (Date.now() - start2 < healthTimeoutMs) {
|
|
33891
|
-
if (await healthCheck()) {
|
|
33892
|
-
healthy = true;
|
|
33893
|
-
break;
|
|
33894
|
-
}
|
|
33895
|
-
await new Promise((r) => setTimeout(r, healthPollIntervalMs));
|
|
33896
|
-
}
|
|
33897
|
-
if (!healthy) {
|
|
33898
|
-
return {
|
|
33899
|
-
binaryRestored: true,
|
|
33900
|
-
serviceStopped: true,
|
|
33901
|
-
serviceRespawned: true,
|
|
33902
|
-
serviceHealthy: false,
|
|
33903
|
-
failedStep: "health",
|
|
33904
|
-
reason: "old service failed health check after rollback respawn"
|
|
33905
|
-
};
|
|
33906
|
-
}
|
|
33907
|
-
return {
|
|
33908
|
-
binaryRestored: true,
|
|
33909
|
-
serviceStopped: true,
|
|
33910
|
-
serviceRespawned: true,
|
|
33911
|
-
serviceHealthy: true
|
|
33912
|
-
};
|
|
33913
|
-
}
|
|
33914
|
-
async function restartPhase(slockHome, deps = {}) {
|
|
33915
|
-
const readServicePid = deps.readServicePid ?? (() => defaultReadServicePid(slockHome));
|
|
33916
|
-
const killService = deps.killService ?? defaultKillService;
|
|
33917
|
-
const forceKillService = deps.forceKillService ?? defaultForceKillService;
|
|
33918
|
-
const waitForExit = deps.waitForExit ?? defaultWaitForExit;
|
|
33919
|
-
const spawnFreshService = deps.spawnFreshService;
|
|
33920
|
-
const healthCheck = deps.healthCheck ?? (() => defaultHealthCheck(slockHome));
|
|
33921
|
-
const healthTimeoutMs = deps.healthTimeoutMs ?? 3e4;
|
|
33922
|
-
const healthPollIntervalMs = deps.healthPollIntervalMs ?? 500;
|
|
33923
|
-
const forceKill = deps.forceKill === true;
|
|
33924
|
-
const oldPid = await readServicePid();
|
|
33925
|
-
if (oldPid !== null) {
|
|
33926
|
-
try {
|
|
33927
|
-
if (forceKill) {
|
|
33928
|
-
await forceKillService(oldPid);
|
|
33929
|
-
} else {
|
|
33930
|
-
await killService(oldPid);
|
|
33931
|
-
const exited = await waitForExit(oldPid, 1e4);
|
|
33932
|
-
if (!exited) {
|
|
33933
|
-
await forceKillService(oldPid);
|
|
33934
|
-
const escalatedExit = await waitForExit(oldPid, 5e3);
|
|
33935
|
-
if (!escalatedExit) {
|
|
33936
|
-
return { ok: false, reason: "service_kill_failed" };
|
|
33937
|
-
}
|
|
33938
|
-
}
|
|
33939
|
-
}
|
|
33940
|
-
} catch {
|
|
33941
|
-
return { ok: false, reason: "service_kill_failed" };
|
|
33942
|
-
}
|
|
33943
|
-
}
|
|
33944
|
-
if (spawnFreshService) {
|
|
33945
|
-
try {
|
|
33946
|
-
await spawnFreshService();
|
|
33947
|
-
} catch {
|
|
33948
|
-
return { ok: false, reason: "spawn_failed" };
|
|
33949
|
-
}
|
|
33950
|
-
}
|
|
33951
|
-
const start2 = Date.now();
|
|
33952
|
-
while (Date.now() - start2 < healthTimeoutMs) {
|
|
33953
|
-
if (await healthCheck()) {
|
|
33954
|
-
return { ok: true, healthAfterMs: Date.now() - start2 };
|
|
33955
|
-
}
|
|
33956
|
-
await new Promise((r) => setTimeout(r, healthPollIntervalMs));
|
|
33957
|
-
}
|
|
33958
|
-
return { ok: false, reason: "health_check_timeout" };
|
|
33959
|
-
}
|
|
33960
|
-
async function defaultReadServicePid(slockHome) {
|
|
33961
|
-
const { pid } = await findLiveServicePid(slockHome);
|
|
33962
|
-
return pid;
|
|
33963
|
-
}
|
|
33964
|
-
async function defaultKillService(pid) {
|
|
33965
|
-
try {
|
|
33966
|
-
process.kill(pid, "SIGTERM");
|
|
33967
|
-
} catch (err) {
|
|
33968
|
-
if (err.code === "ESRCH") return;
|
|
33969
|
-
throw err;
|
|
33970
|
-
}
|
|
33971
|
-
}
|
|
33972
|
-
async function defaultForceKillService(pid) {
|
|
33973
|
-
try {
|
|
33974
|
-
process.kill(pid, "SIGKILL");
|
|
33975
|
-
} catch (err) {
|
|
33976
|
-
if (err.code === "ESRCH") return;
|
|
33977
|
-
throw err;
|
|
33978
|
-
}
|
|
33979
|
-
}
|
|
33980
|
-
async function defaultIsDaemonBusy(_slockHome) {
|
|
33981
|
-
return false;
|
|
33982
|
-
}
|
|
33983
|
-
async function defaultWaitForExit(pid, timeoutMs) {
|
|
33984
|
-
const start2 = Date.now();
|
|
33985
|
-
while (Date.now() - start2 < timeoutMs) {
|
|
33986
|
-
try {
|
|
33987
|
-
process.kill(pid, 0);
|
|
33988
|
-
} catch (err) {
|
|
33989
|
-
if (err.code === "ESRCH") return true;
|
|
33990
|
-
}
|
|
33991
|
-
await new Promise((r) => setTimeout(r, 200));
|
|
33992
|
-
}
|
|
33993
|
-
return false;
|
|
33994
|
-
}
|
|
33995
|
-
async function defaultHealthCheck(slockHome) {
|
|
33996
|
-
const pid = await defaultReadServicePid(slockHome);
|
|
33997
|
-
if (pid === null) return false;
|
|
33998
|
-
try {
|
|
33999
|
-
process.kill(pid, 0);
|
|
34000
|
-
return true;
|
|
34001
|
-
} catch (err) {
|
|
34002
|
-
return err.code === "EPERM";
|
|
34003
|
-
}
|
|
34004
|
-
}
|
|
34005
|
-
async function cleanupSuccessPhase(slockHome, version, prevBinaryDir) {
|
|
34006
|
-
await cleanupStaged(slockHome, version);
|
|
34007
|
-
await clearUpgradeSnapshot(slockHome);
|
|
34008
|
-
try {
|
|
34009
|
-
await rm3(prevBinaryDir, { recursive: true, force: true });
|
|
34010
|
-
} catch {
|
|
34011
|
-
}
|
|
34012
|
-
}
|
|
34013
|
-
async function runUpgrade(slockHome, opts) {
|
|
34014
|
-
const deps = opts.deps ?? {};
|
|
34015
|
-
const drainMode = opts.drainMode ?? "drain";
|
|
34016
|
-
if (drainMode === "defer") {
|
|
34017
|
-
const isBusy = deps.isDaemonBusy ?? defaultIsDaemonBusy;
|
|
34018
|
-
let busy = false;
|
|
34019
|
-
try {
|
|
34020
|
-
busy = await isBusy(slockHome);
|
|
34021
|
-
} catch {
|
|
34022
|
-
busy = false;
|
|
34023
|
-
}
|
|
34024
|
-
if (busy) {
|
|
34025
|
-
return {
|
|
34026
|
-
ok: false,
|
|
34027
|
-
phase: "drain",
|
|
34028
|
-
reason: "deferred: daemon busy (--drain defer)"
|
|
34029
|
-
};
|
|
34030
|
-
}
|
|
34031
|
-
}
|
|
34032
|
-
let staged;
|
|
34033
|
-
try {
|
|
34034
|
-
staged = await stagePhase(slockHome, opts.targetVersion, {
|
|
34035
|
-
npmPack: deps.npmPack,
|
|
34036
|
-
fsReadFile: deps.fsReadFile
|
|
34037
|
-
});
|
|
34038
|
-
} catch (e) {
|
|
34039
|
-
await cleanupStaged(slockHome, opts.targetVersion);
|
|
34040
|
-
return { ok: false, phase: "stage", reason: e instanceof Error ? e.message : String(e) };
|
|
34041
|
-
}
|
|
34042
|
-
const verify = await verifyPhase(staged, { fetchAdvertisedHash: deps.fetchAdvertisedHash });
|
|
34043
|
-
if (!verify.ok) {
|
|
34044
|
-
await cleanupStaged(slockHome, opts.targetVersion);
|
|
34045
|
-
return { ok: false, phase: "verify", reason: verify.reason, staged, verify };
|
|
34046
|
-
}
|
|
34047
|
-
let stagedTarballPath;
|
|
34048
|
-
try {
|
|
34049
|
-
stagedTarballPath = await locateStagedTarball(staged.stagedPath);
|
|
34050
|
-
} catch (e) {
|
|
34051
|
-
await cleanupStaged(slockHome, opts.targetVersion);
|
|
34052
|
-
return {
|
|
34053
|
-
ok: false,
|
|
34054
|
-
phase: "preflight",
|
|
34055
|
-
reason: e instanceof Error ? e.message : String(e),
|
|
34056
|
-
staged,
|
|
34057
|
-
verify
|
|
34058
|
-
};
|
|
34059
|
-
}
|
|
34060
|
-
const preflight = await preflightDepDriftCheck(
|
|
34061
|
-
opts.currentBinaryDir,
|
|
34062
|
-
stagedTarballPath,
|
|
34063
|
-
deps.preflight ?? {}
|
|
34064
|
-
);
|
|
34065
|
-
if (!preflight.ok) {
|
|
34066
|
-
await cleanupStaged(slockHome, opts.targetVersion);
|
|
34067
|
-
return {
|
|
34068
|
-
ok: false,
|
|
34069
|
-
phase: "preflight",
|
|
34070
|
-
reason: preflight.detail ?? preflight.reason ?? "dep_drift",
|
|
34071
|
-
staged,
|
|
34072
|
-
verify,
|
|
34073
|
-
preflight
|
|
34074
|
-
};
|
|
34075
|
-
}
|
|
34076
|
-
const targetDaemonSpec = preflight.targetSpec;
|
|
34077
|
-
if (typeof targetDaemonSpec !== "string" || targetDaemonSpec.length === 0) {
|
|
34078
|
-
await cleanupStaged(slockHome, opts.targetVersion);
|
|
34079
|
-
return {
|
|
34080
|
-
ok: false,
|
|
34081
|
-
phase: "preflight",
|
|
34082
|
-
reason: `${preflight.detail ?? preflight.reason ?? "dep_drift"}: missing target ${DAEMON_PACKAGE_NAME} spec`,
|
|
34083
|
-
staged,
|
|
34084
|
-
verify,
|
|
34085
|
-
preflight
|
|
34086
|
-
};
|
|
34087
|
-
}
|
|
34088
|
-
const snap = {
|
|
34089
|
-
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
34090
|
-
fromVersion: opts.fromVersion,
|
|
34091
|
-
toVersion: opts.targetVersion,
|
|
34092
|
-
channel: opts.channel
|
|
34093
|
-
};
|
|
34094
|
-
try {
|
|
34095
|
-
await snapshotPhase(slockHome, snap);
|
|
34096
|
-
} catch (e) {
|
|
34097
|
-
await cleanupStaged(slockHome, opts.targetVersion);
|
|
34098
|
-
return {
|
|
34099
|
-
ok: false,
|
|
34100
|
-
phase: "snapshot",
|
|
34101
|
-
reason: e instanceof Error ? e.message : String(e),
|
|
34102
|
-
staged,
|
|
34103
|
-
verify
|
|
34104
|
-
};
|
|
34105
|
-
}
|
|
34106
|
-
let extractedPackageDir;
|
|
34107
|
-
try {
|
|
34108
|
-
const ex = await extractTarball(
|
|
34109
|
-
stagedTarballPath,
|
|
34110
|
-
join6(staged.stagedPath, "extracted"),
|
|
34111
|
-
{ tarSpawn: deps.tarSpawn }
|
|
34112
|
-
);
|
|
34113
|
-
extractedPackageDir = ex.extractedPackageDir;
|
|
34114
|
-
} catch (e) {
|
|
34115
|
-
await cleanupStaged(slockHome, opts.targetVersion);
|
|
34116
|
-
await clearUpgradeSnapshot(slockHome);
|
|
34117
|
-
return {
|
|
34118
|
-
ok: false,
|
|
34119
|
-
phase: "extract",
|
|
34120
|
-
reason: e instanceof Error ? e.message : String(e),
|
|
34121
|
-
staged,
|
|
34122
|
-
verify
|
|
34123
|
-
};
|
|
34124
|
-
}
|
|
34125
|
-
try {
|
|
34126
|
-
await hydrateStagedDependencies(extractedPackageDir, { npmInstall: deps.npmInstall });
|
|
34127
|
-
} catch (e) {
|
|
34128
|
-
await cleanupStaged(slockHome, opts.targetVersion);
|
|
34129
|
-
await clearUpgradeSnapshot(slockHome);
|
|
34130
|
-
return {
|
|
34131
|
-
ok: false,
|
|
34132
|
-
phase: "hydrate",
|
|
34133
|
-
reason: e instanceof Error ? e.message : String(e),
|
|
34134
|
-
staged,
|
|
34135
|
-
verify
|
|
34136
|
-
};
|
|
34137
|
-
}
|
|
34138
|
-
const hydratedDaemon = await verifyHydratedDaemonDependency(
|
|
34139
|
-
extractedPackageDir,
|
|
34140
|
-
targetDaemonSpec,
|
|
34141
|
-
deps.preflight ?? {}
|
|
34142
|
-
);
|
|
34143
|
-
if (!hydratedDaemon.ok) {
|
|
34144
|
-
await cleanupStaged(slockHome, opts.targetVersion);
|
|
34145
|
-
await clearUpgradeSnapshot(slockHome);
|
|
34146
|
-
return {
|
|
34147
|
-
ok: false,
|
|
34148
|
-
phase: "hydrate",
|
|
34149
|
-
reason: `staged dependency tree verification failed: ${hydratedDaemon.detail ?? hydratedDaemon.reason ?? "hydrated dependency verification failed"}`,
|
|
34150
|
-
staged,
|
|
34151
|
-
verify,
|
|
34152
|
-
preflight,
|
|
34153
|
-
hydratedDaemon
|
|
34154
|
-
};
|
|
34155
|
-
}
|
|
34156
|
-
let swap;
|
|
34157
|
-
try {
|
|
34158
|
-
swap = await swapPhase(opts.currentBinaryDir, extractedPackageDir, {
|
|
34159
|
-
fsRename: deps.fsRename,
|
|
34160
|
-
fsRm: deps.fsRm
|
|
34161
|
-
});
|
|
34162
|
-
} catch (e) {
|
|
34163
|
-
return {
|
|
34164
|
-
ok: false,
|
|
34165
|
-
phase: "swap",
|
|
34166
|
-
reason: e instanceof Error ? e.message : String(e),
|
|
34167
|
-
staged,
|
|
34168
|
-
verify
|
|
34169
|
-
};
|
|
34170
|
-
}
|
|
34171
|
-
const restart = await restartPhase(slockHome, {
|
|
34172
|
-
readServicePid: deps.readServicePid,
|
|
34173
|
-
killService: deps.killService,
|
|
34174
|
-
forceKillService: deps.forceKillService,
|
|
34175
|
-
waitForExit: deps.waitForExit,
|
|
34176
|
-
spawnFreshService: deps.spawnFreshService,
|
|
34177
|
-
healthCheck: deps.healthCheck,
|
|
34178
|
-
healthTimeoutMs: deps.healthTimeoutMs,
|
|
34179
|
-
healthPollIntervalMs: deps.healthPollIntervalMs,
|
|
34180
|
-
forceKill: drainMode === "force"
|
|
34181
|
-
});
|
|
34182
|
-
if (!restart.ok) {
|
|
34183
|
-
const rb = await rollbackRestart(slockHome, opts.currentBinaryDir, {
|
|
34184
|
-
readServicePid: deps.readServicePid,
|
|
34185
|
-
isProcessAlive: deps.isProcessAlive,
|
|
34186
|
-
killService: deps.killService,
|
|
34187
|
-
forceKillService: deps.forceKillService,
|
|
34188
|
-
waitForExit: deps.waitForExit,
|
|
34189
|
-
spawnFreshService: deps.spawnFreshService,
|
|
34190
|
-
healthCheck: deps.healthCheck,
|
|
34191
|
-
healthTimeoutMs: deps.healthTimeoutMs,
|
|
34192
|
-
healthPollIntervalMs: deps.healthPollIntervalMs,
|
|
34193
|
-
fsRename: deps.fsRename,
|
|
34194
|
-
fsRm: deps.fsRm
|
|
34195
|
-
});
|
|
34196
|
-
return {
|
|
34197
|
-
ok: false,
|
|
34198
|
-
phase: "restart",
|
|
34199
|
-
reason: restart.reason,
|
|
34200
|
-
staged,
|
|
34201
|
-
verify,
|
|
34202
|
-
swap,
|
|
34203
|
-
restart,
|
|
34204
|
-
rolledBack: rb.binaryRestored && rb.serviceHealthy,
|
|
34205
|
-
rollback: rb
|
|
34206
|
-
};
|
|
34207
|
-
}
|
|
34208
|
-
const rolling = await rollingDaemonHealthCheck(slockHome, {
|
|
34209
|
-
listManagedServerIds: deps.listManagedServerIds,
|
|
34210
|
-
readDaemonPid: deps.readDaemonPid,
|
|
34211
|
-
isProcessAlive: deps.isProcessAlive,
|
|
34212
|
-
perDaemonTimeoutMs: deps.perDaemonTimeoutMs,
|
|
34213
|
-
pollIntervalMs: deps.daemonPollIntervalMs
|
|
34214
|
-
});
|
|
34215
|
-
if (!rolling.ok) {
|
|
34216
|
-
const rb = await rollbackRestart(slockHome, opts.currentBinaryDir, {
|
|
34217
|
-
readServicePid: deps.readServicePid,
|
|
34218
|
-
isProcessAlive: deps.isProcessAlive,
|
|
34219
|
-
killService: deps.killService,
|
|
34220
|
-
forceKillService: deps.forceKillService,
|
|
34221
|
-
waitForExit: deps.waitForExit,
|
|
34222
|
-
spawnFreshService: deps.spawnFreshService,
|
|
34223
|
-
healthCheck: deps.healthCheck,
|
|
34224
|
-
healthTimeoutMs: deps.healthTimeoutMs,
|
|
34225
|
-
healthPollIntervalMs: deps.healthPollIntervalMs,
|
|
34226
|
-
fsRename: deps.fsRename,
|
|
34227
|
-
fsRm: deps.fsRm
|
|
34228
|
-
});
|
|
34229
|
-
const failed = rolling.daemons.find((d) => !d.ok);
|
|
34230
|
-
return {
|
|
34231
|
-
ok: false,
|
|
34232
|
-
phase: "rolling_health",
|
|
34233
|
-
reason: failed ? `daemon ${failed.serverId} unhealthy after restart: ${failed.reason ?? "unknown"}` : "rolling daemon health failed",
|
|
34234
|
-
staged,
|
|
34235
|
-
verify,
|
|
34236
|
-
swap,
|
|
34237
|
-
restart,
|
|
34238
|
-
rolling,
|
|
34239
|
-
rolledBack: rb.binaryRestored && rb.serviceHealthy,
|
|
34240
|
-
rollback: rb
|
|
34241
|
-
};
|
|
34242
|
-
}
|
|
34243
|
-
await cleanupSuccessPhase(slockHome, opts.targetVersion, swap.prevBinaryDir);
|
|
34244
|
-
return { ok: true, phase: "cleanup", staged, verify, preflight, hydratedDaemon, swap, restart, rolling };
|
|
34245
|
-
}
|
|
34246
|
-
async function rollingDaemonHealthCheck(slockHome, deps = {}) {
|
|
34247
|
-
const list = deps.listManagedServerIds ?? listManagedServerIds;
|
|
34248
|
-
const readDaemonPid = deps.readDaemonPid ?? defaultReadDaemonPid;
|
|
34249
|
-
const isProcessAlive4 = deps.isProcessAlive ?? defaultIsProcessAlive;
|
|
34250
|
-
const perDaemonTimeoutMs = deps.perDaemonTimeoutMs ?? 3e4;
|
|
34251
|
-
const pollIntervalMs = deps.pollIntervalMs ?? 500;
|
|
34252
|
-
const managed = await list(slockHome);
|
|
34253
|
-
const daemons = [];
|
|
34254
|
-
for (const serverId of managed) {
|
|
34255
|
-
const start2 = Date.now();
|
|
34256
|
-
let lastReason = "timeout";
|
|
34257
|
-
let healthy = false;
|
|
34258
|
-
while (Date.now() - start2 < perDaemonTimeoutMs) {
|
|
34259
|
-
const pid = await readDaemonPid(slockHome, serverId);
|
|
34260
|
-
if (pid === null) {
|
|
34261
|
-
lastReason = "pidfile_missing";
|
|
34262
|
-
} else if (!isProcessAlive4(pid)) {
|
|
34263
|
-
lastReason = "process_dead";
|
|
34264
|
-
} else {
|
|
34265
|
-
healthy = true;
|
|
34266
|
-
break;
|
|
34267
|
-
}
|
|
34268
|
-
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
34269
|
-
}
|
|
34270
|
-
const entry = healthy ? { serverId, ok: true, healthAfterMs: Date.now() - start2 } : { serverId, ok: false, reason: lastReason };
|
|
34271
|
-
daemons.push(entry);
|
|
34272
|
-
if (!healthy) {
|
|
34273
|
-
return { ok: false, daemons };
|
|
34274
|
-
}
|
|
34275
|
-
}
|
|
34276
|
-
return { ok: true, daemons };
|
|
34277
|
-
}
|
|
34278
|
-
async function defaultReadDaemonPid(slockHome, serverId) {
|
|
34279
|
-
try {
|
|
34280
|
-
const raw = (await readFile14(serverRunnerPidPath(slockHome, serverId), "utf8")).trim();
|
|
34281
|
-
const pid = Number.parseInt(raw, 10);
|
|
34282
|
-
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
34283
|
-
} catch {
|
|
34284
|
-
return null;
|
|
34285
|
-
}
|
|
34286
|
-
}
|
|
34287
|
-
function defaultIsProcessAlive(pid) {
|
|
34288
|
-
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
34289
|
-
try {
|
|
34290
|
-
process.kill(pid, 0);
|
|
34291
|
-
return true;
|
|
34292
|
-
} catch (err) {
|
|
34293
|
-
return err.code === "EPERM";
|
|
34294
|
-
}
|
|
34295
|
-
}
|
|
34296
|
-
async function locateStagedTarball(stagedPath) {
|
|
34297
|
-
const { readdir: readdir5 } = await import("fs/promises");
|
|
34298
|
-
const entries = await readdir5(stagedPath);
|
|
34299
|
-
const tgz = entries.find((e) => e.endsWith(".tgz"));
|
|
34300
|
-
if (!tgz) {
|
|
34301
|
-
throw new Error(`no .tgz tarball found in staged dir ${stagedPath}`);
|
|
34302
|
-
}
|
|
34303
|
-
return join6(stagedPath, tgz);
|
|
34304
|
-
}
|
|
34305
|
-
|
|
34306
|
-
// src/upgradeLog.ts
|
|
34307
|
-
init_esm_shims();
|
|
34308
|
-
import { chmod as chmod5, mkdir as mkdir14, open as open2 } from "fs/promises";
|
|
33620
|
+
import { chmod as chmod6, mkdir as mkdir14, open as open2 } from "fs/promises";
|
|
34309
33621
|
var FILE_MODE = 384;
|
|
34310
33622
|
function resolveUpgradeTrigger(raw) {
|
|
34311
33623
|
return raw === "web" || raw === "tray" ? raw : "cli";
|
|
@@ -34365,31 +33677,29 @@ async function appendUpgradeLogEntry(slockHome, entry) {
|
|
|
34365
33677
|
await handle.close();
|
|
34366
33678
|
}
|
|
34367
33679
|
if (process.platform !== "win32") {
|
|
34368
|
-
await
|
|
33680
|
+
await chmod6(path3, FILE_MODE);
|
|
34369
33681
|
}
|
|
34370
33682
|
}
|
|
34371
33683
|
|
|
34372
33684
|
// src/upgradeCli.ts
|
|
34373
|
-
function
|
|
34374
|
-
|
|
34375
|
-
|
|
34376
|
-
|
|
34377
|
-
|
|
34378
|
-
|
|
34379
|
-
|
|
34380
|
-
|
|
34381
|
-
const pinned = parsed.dependencies?.["@slock-ai/daemon"];
|
|
34382
|
-
if (typeof pinned !== "string" || pinned.length === 0) return null;
|
|
34383
|
-
if (pinned === "workspace:*") return null;
|
|
34384
|
-
return pinned;
|
|
34385
|
-
} catch {
|
|
34386
|
-
return null;
|
|
33685
|
+
function mapSeaFailedPhaseToCode(failedPhase, reason) {
|
|
33686
|
+
switch (failedPhase) {
|
|
33687
|
+
case "resolve":
|
|
33688
|
+
return "UPGRADE_NETWORK_FAILED";
|
|
33689
|
+
case "stage":
|
|
33690
|
+
return reason && /sha[_ ]?mismatch|UPGRADE_SHA_MISMATCH/i.test(reason) ? "UPGRADE_INTEGRITY_FAILED" : "UPGRADE_NETWORK_FAILED";
|
|
33691
|
+
case "swap":
|
|
33692
|
+
return "UPGRADE_SWAP_FAILED";
|
|
34387
33693
|
}
|
|
34388
33694
|
}
|
|
34389
|
-
async function defaultSpawnFreshService(slockHome) {
|
|
34390
|
-
await spawnDetachedService(slockHome);
|
|
34391
|
-
}
|
|
34392
33695
|
async function runUpgradeCli(slockHome, opts, deps = {}) {
|
|
33696
|
+
const isSea = deps.isSeaBinary ?? isSeaBinary;
|
|
33697
|
+
if (!isSea()) {
|
|
33698
|
+
fail(
|
|
33699
|
+
"UPGRADE_SEA_ONLY",
|
|
33700
|
+
"`slock-computer upgrade` self-upgrades the SEA single-binary; this run is not a SEA binary (npm-installed or source/dev). Re-install the SEA binary to upgrade: `curl -fsSL https://github.com/botiverse/slock/releases/latest/download/install.sh | sh`."
|
|
33701
|
+
);
|
|
33702
|
+
}
|
|
34393
33703
|
let channel2;
|
|
34394
33704
|
if (opts.channel !== void 0) {
|
|
34395
33705
|
const parsed = parseChannel(opts.channel);
|
|
@@ -34423,8 +33733,7 @@ async function runUpgradeCli(slockHome, opts, deps = {}) {
|
|
|
34423
33733
|
`Could not reach npm registry to resolve ${channel2} dist-tag. Check connectivity or pass --target-version <semver>.`
|
|
34424
33734
|
);
|
|
34425
33735
|
}
|
|
34426
|
-
const
|
|
34427
|
-
const resolved = tags[tagName];
|
|
33736
|
+
const resolved = tags[channel2];
|
|
34428
33737
|
if (typeof resolved !== "string" || resolved.length === 0) {
|
|
34429
33738
|
fail(
|
|
34430
33739
|
"UPGRADE_VERSION_RESOLVE_FAILED",
|
|
@@ -34434,14 +33743,7 @@ async function runUpgradeCli(slockHome, opts, deps = {}) {
|
|
|
34434
33743
|
targetVersion = resolved;
|
|
34435
33744
|
}
|
|
34436
33745
|
const fromVersion = await (deps.currentVersion ?? defaultCurrentVersion)();
|
|
34437
|
-
const
|
|
34438
|
-
const isEphemeralFn = deps.isEphemeralNpxContext ?? isEphemeralNpxContext;
|
|
34439
|
-
if (isEphemeralFn(currentBinaryDir)) {
|
|
34440
|
-
fail(
|
|
34441
|
-
"UPGRADE_EPHEMERAL_CONTEXT",
|
|
34442
|
-
`You're running upgrade via the npx ephemeral entrypoint, which can't reach your installed Computer service. Please run the installed \`slock-computer upgrade\` instead. If \`slock-computer\` isn't on your PATH, install it globally first: \`npm i -g @slock-ai/computer@latest\`.`
|
|
34443
|
-
);
|
|
34444
|
-
}
|
|
33746
|
+
const currentBinaryPath = (deps.currentBinaryPath ?? (() => process.execPath))();
|
|
34445
33747
|
if (targetVersion === fromVersion) {
|
|
34446
33748
|
info(`Already at ${fromVersion} (channel ${channel2}). No upgrade target.`);
|
|
34447
33749
|
await appendUpgradeLogEntry(slockHome, {
|
|
@@ -34457,87 +33759,60 @@ async function runUpgradeCli(slockHome, opts, deps = {}) {
|
|
|
34457
33759
|
}
|
|
34458
33760
|
info(`Planning upgrade: ${fromVersion} \u2192 ${targetVersion} (channel ${channel2}).`);
|
|
34459
33761
|
if (opts.dryRun) {
|
|
34460
|
-
const
|
|
34461
|
-
const
|
|
34462
|
-
let staged;
|
|
33762
|
+
const stageFn = deps.stageSeaPhaseFn ?? stageSeaPhase;
|
|
33763
|
+
const stagingDir = join6(slockHome, "upgrade-staging", `${targetVersion}-dryrun`);
|
|
34463
33764
|
try {
|
|
34464
|
-
staged = await
|
|
33765
|
+
const staged = await stageFn(stagingDir, targetVersion, process.platform, process.arch);
|
|
33766
|
+
info(
|
|
33767
|
+
`Dry run OK: ${targetVersion} downloaded + verified (sha256 ${staged.sha256.slice(0, 12)}). No swap performed.`
|
|
33768
|
+
);
|
|
34465
33769
|
} catch (e) {
|
|
34466
33770
|
const msg = e instanceof Error ? e.message : String(e);
|
|
34467
|
-
|
|
34468
|
-
|
|
34469
|
-
|
|
34470
|
-
|
|
34471
|
-
if (!verify.ok) {
|
|
34472
|
-
fail(
|
|
34473
|
-
"UPGRADE_VERIFY_FAILED",
|
|
34474
|
-
`Dry run: verify failed (${verify.reason}). actual=${verify.actual ?? "?"} advertised=${verify.advertised ?? "?"}.`
|
|
34475
|
-
);
|
|
33771
|
+
const code2 = mapSeaFailedPhaseToCode("stage", msg);
|
|
33772
|
+
await rm4(stagingDir, { recursive: true, force: true }).catch(() => {
|
|
33773
|
+
});
|
|
33774
|
+
fail(code2, `Dry run aborted: stage failed: ${msg}`);
|
|
34476
33775
|
}
|
|
34477
|
-
|
|
34478
|
-
|
|
34479
|
-
);
|
|
33776
|
+
await rm4(stagingDir, { recursive: true, force: true }).catch(() => {
|
|
33777
|
+
});
|
|
34480
33778
|
return;
|
|
34481
33779
|
}
|
|
34482
|
-
|
|
34483
|
-
|
|
34484
|
-
|
|
34485
|
-
|
|
34486
|
-
|
|
34487
|
-
|
|
34488
|
-
}
|
|
34489
|
-
const readBundledFn = deps.readBundledDaemonVersion ?? readBundledDaemonVersion;
|
|
34490
|
-
const fromBundledDaemonVersion = await readBundledFn(currentBinaryDir);
|
|
34491
|
-
const runUpgradeFn = deps.runUpgradeFn ?? runUpgrade;
|
|
34492
|
-
const spawnFreshService = deps.spawnFreshService ?? defaultSpawnFreshService;
|
|
34493
|
-
const outcome = await runUpgradeFn(slockHome, {
|
|
34494
|
-
targetVersion,
|
|
33780
|
+
const runFn = deps.resolveAndRunSeaUpgradeFn ?? resolveAndRunSeaUpgrade;
|
|
33781
|
+
const result = await runFn({
|
|
33782
|
+
slockHome,
|
|
33783
|
+
// Synthetic requestId — the CLI path has no remote requestId; the marker
|
|
33784
|
+
// is still written so a post-restart reconcile reports done coherently.
|
|
33785
|
+
requestId: `cli-${Date.now()}`,
|
|
34495
33786
|
fromVersion,
|
|
34496
|
-
|
|
34497
|
-
|
|
34498
|
-
drainMode,
|
|
33787
|
+
currentBinaryPath,
|
|
33788
|
+
nowIso: (/* @__PURE__ */ new Date()).toISOString(),
|
|
34499
33789
|
deps: {
|
|
34500
|
-
//
|
|
34501
|
-
// and
|
|
34502
|
-
|
|
34503
|
-
// and rollback later trips `failedStep="respawn"`. See Dayu
|
|
34504
|
-
// blocker (#wg-slock-computer:b43b36fb msg=911eb84e).
|
|
34505
|
-
spawnFreshService: () => spawnFreshService(slockHome)
|
|
33790
|
+
// Force the already-resolved target so the SEA flow doesn't re-resolve
|
|
33791
|
+
// the channel (and so `--target-version` / `--channel` overrides win).
|
|
33792
|
+
resolveTargetVersion: async () => targetVersion
|
|
34506
33793
|
}
|
|
34507
33794
|
});
|
|
34508
|
-
const toBundledDaemonVersion = await readBundledFn(currentBinaryDir);
|
|
34509
|
-
const bundle = (version, bundledDaemonVersion) => bundledDaemonVersion !== null ? { computerVersion: version, bundledDaemonVersion } : { computerVersion: version };
|
|
34510
|
-
const fromBundle = bundle(fromVersion, fromBundledDaemonVersion);
|
|
34511
|
-
const toBundle = bundle(targetVersion, toBundledDaemonVersion);
|
|
34512
33795
|
const logTrigger = opts.trigger ?? "cli";
|
|
34513
|
-
if (
|
|
34514
|
-
|
|
34515
|
-
`
|
|
34516
|
-
|
|
34517
|
-
|
|
34518
|
-
|
|
34519
|
-
|
|
34520
|
-
|
|
34521
|
-
|
|
34522
|
-
|
|
34523
|
-
|
|
34524
|
-
|
|
34525
|
-
|
|
34526
|
-
|
|
34527
|
-
const rolledBack = outcome.rolledBack === true ? " (swap rolled back)" : "";
|
|
34528
|
-
if (outcome.phase === "drain") {
|
|
34529
|
-
info(
|
|
34530
|
-
`Upgrade deferred: --drain=defer and daemons are busy. Re-run when idle, or use --force to interrupt in-flight turns.`
|
|
34531
|
-
);
|
|
34532
|
-
return;
|
|
34533
|
-
}
|
|
34534
|
-
if (outcome.phase === "cleanup") {
|
|
33796
|
+
if (result.ok) {
|
|
33797
|
+
if (result.alreadyCurrent) {
|
|
33798
|
+
info(`Already at ${fromVersion} (channel ${channel2}). No upgrade target.`);
|
|
33799
|
+
await appendUpgradeLogEntry(slockHome, {
|
|
33800
|
+
fromBundle: { computerVersion: fromVersion },
|
|
33801
|
+
toBundle: { computerVersion: fromVersion },
|
|
33802
|
+
channel: channel2,
|
|
33803
|
+
trigger: logTrigger,
|
|
33804
|
+
outcome: "err",
|
|
33805
|
+
errorCode: "UPGRADE_NO_TARGET"
|
|
33806
|
+
}).catch(() => {
|
|
33807
|
+
});
|
|
33808
|
+
return;
|
|
33809
|
+
}
|
|
34535
33810
|
info(
|
|
34536
|
-
`Upgrade ${fromVersion} \u2192 ${targetVersion}
|
|
33811
|
+
`Upgrade staged: ${fromVersion} \u2192 ${targetVersion} downloaded, verified, and swapped. Restart the Computer service to run the new version (the managed supervisor does this automatically).`
|
|
34537
33812
|
);
|
|
34538
33813
|
await appendUpgradeLogEntry(slockHome, {
|
|
34539
|
-
fromBundle,
|
|
34540
|
-
toBundle,
|
|
33814
|
+
fromBundle: { computerVersion: fromVersion },
|
|
33815
|
+
toBundle: { computerVersion: targetVersion },
|
|
34541
33816
|
channel: channel2,
|
|
34542
33817
|
trigger: logTrigger,
|
|
34543
33818
|
outcome: "ok"
|
|
@@ -34545,10 +33820,10 @@ async function runUpgradeCli(slockHome, opts, deps = {}) {
|
|
|
34545
33820
|
});
|
|
34546
33821
|
return;
|
|
34547
33822
|
}
|
|
34548
|
-
const code =
|
|
33823
|
+
const code = mapSeaFailedPhaseToCode(result.failedPhase ?? "stage", result.reason);
|
|
34549
33824
|
await appendUpgradeLogEntry(slockHome, {
|
|
34550
|
-
fromBundle,
|
|
34551
|
-
toBundle,
|
|
33825
|
+
fromBundle: { computerVersion: fromVersion },
|
|
33826
|
+
toBundle: { computerVersion: targetVersion },
|
|
34552
33827
|
channel: channel2,
|
|
34553
33828
|
trigger: logTrigger,
|
|
34554
33829
|
outcome: "err",
|
|
@@ -34557,39 +33832,9 @@ async function runUpgradeCli(slockHome, opts, deps = {}) {
|
|
|
34557
33832
|
});
|
|
34558
33833
|
fail(
|
|
34559
33834
|
code,
|
|
34560
|
-
`Upgrade failed at phase=${
|
|
33835
|
+
`Upgrade failed at phase=${result.failedPhase ?? "stage"}: ${result.reason ?? "unknown"}.`
|
|
34561
33836
|
);
|
|
34562
33837
|
}
|
|
34563
|
-
function mapFailurePhaseToCode(outcome) {
|
|
34564
|
-
switch (outcome.phase) {
|
|
34565
|
-
case "stage":
|
|
34566
|
-
return "UPGRADE_NETWORK_FAILED";
|
|
34567
|
-
case "verify":
|
|
34568
|
-
return "UPGRADE_INTEGRITY_FAILED";
|
|
34569
|
-
case "preflight":
|
|
34570
|
-
return "UPGRADE_DEPS_CHANGED";
|
|
34571
|
-
case "snapshot":
|
|
34572
|
-
return "UPGRADE_SWAP_FAILED";
|
|
34573
|
-
case "extract":
|
|
34574
|
-
return "UPGRADE_INTEGRITY_FAILED";
|
|
34575
|
-
case "hydrate":
|
|
34576
|
-
if (outcome.hydratedDaemon?.ok === false) {
|
|
34577
|
-
return "UPGRADE_INTEGRITY_FAILED";
|
|
34578
|
-
}
|
|
34579
|
-
return "UPGRADE_NETWORK_FAILED";
|
|
34580
|
-
case "swap":
|
|
34581
|
-
return "UPGRADE_SWAP_FAILED";
|
|
34582
|
-
case "restart":
|
|
34583
|
-
return "UPGRADE_RESTART_FAILED";
|
|
34584
|
-
case "rolling_health":
|
|
34585
|
-
return "UPGRADE_RESTART_FAILED";
|
|
34586
|
-
case "drain":
|
|
34587
|
-
case "cleanup":
|
|
34588
|
-
throw new Error(
|
|
34589
|
-
`mapFailurePhaseToCode: phase=${outcome.phase} should be handled by caller (silenced), not mapped to a closed-set code`
|
|
34590
|
-
);
|
|
34591
|
-
}
|
|
34592
|
-
}
|
|
34593
33838
|
async function defaultFetchDistTags() {
|
|
34594
33839
|
const url = "https://registry.npmjs.org/@slock-ai/computer";
|
|
34595
33840
|
const controller = new AbortController();
|
|
@@ -34613,9 +33858,10 @@ function defaultCurrentBinaryDir() {
|
|
|
34613
33858
|
return dirname12(dirname12(here));
|
|
34614
33859
|
}
|
|
34615
33860
|
async function defaultCurrentVersion() {
|
|
34616
|
-
|
|
33861
|
+
if (isSeaBinary()) return COMPUTER_VERSION;
|
|
33862
|
+
const pkgPath = join6(defaultCurrentBinaryDir(), "package.json");
|
|
34617
33863
|
try {
|
|
34618
|
-
const raw = await
|
|
33864
|
+
const raw = await readFile14(pkgPath, "utf8");
|
|
34619
33865
|
const parsed = JSON.parse(raw);
|
|
34620
33866
|
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
34621
33867
|
return parsed.version;
|
|
@@ -34625,337 +33871,6 @@ async function defaultCurrentVersion() {
|
|
|
34625
33871
|
throw new CliExit(1);
|
|
34626
33872
|
}
|
|
34627
33873
|
|
|
34628
|
-
// src/upgradeTestHarness.ts
|
|
34629
|
-
init_esm_shims();
|
|
34630
|
-
import { mkdir as mkdir15, readdir as readdir4, stat as stat4, writeFile as writeFile12 } from "fs/promises";
|
|
34631
|
-
import { join as join8 } from "path";
|
|
34632
|
-
import { createHash as createHash4 } from "crypto";
|
|
34633
|
-
var PHASES = /* @__PURE__ */ new Set([
|
|
34634
|
-
"stage",
|
|
34635
|
-
"verify",
|
|
34636
|
-
"snapshot",
|
|
34637
|
-
"extract",
|
|
34638
|
-
"swap",
|
|
34639
|
-
"restart",
|
|
34640
|
-
"rolling_health",
|
|
34641
|
-
"cleanup"
|
|
34642
|
-
]);
|
|
34643
|
-
function validateHarnessOptions(opts) {
|
|
34644
|
-
if (opts.simulateFail !== void 0 && !PHASES.has(opts.simulateFail)) {
|
|
34645
|
-
return `--simulate-fail: unknown phase "${opts.simulateFail}". Accepted: ${[...PHASES].join(" | ")}.`;
|
|
34646
|
-
}
|
|
34647
|
-
if (opts.simulateNetworkLossAt !== void 0 && opts.simulateNetworkLossAt !== "stage" && opts.simulateNetworkLossAt !== "verify") {
|
|
34648
|
-
return `--simulate-network-loss-at: only "stage" or "verify" are network-bound phases (got "${opts.simulateNetworkLossAt}").`;
|
|
34649
|
-
}
|
|
34650
|
-
if (opts.simulateSleepWakeMs !== void 0) {
|
|
34651
|
-
if (!Number.isFinite(opts.simulateSleepWakeMs) || opts.simulateSleepWakeMs < 0) {
|
|
34652
|
-
return `--simulate-sleep-wake: must be a non-negative number of ms (got ${opts.simulateSleepWakeMs}).`;
|
|
34653
|
-
}
|
|
34654
|
-
if (opts.simulateSleepWakeMs > 6e4) {
|
|
34655
|
-
return `--simulate-sleep-wake: max 60000ms to keep the harness bounded (got ${opts.simulateSleepWakeMs}).`;
|
|
34656
|
-
}
|
|
34657
|
-
}
|
|
34658
|
-
return null;
|
|
34659
|
-
}
|
|
34660
|
-
function buildSimulatedDeps(slockHome, opts) {
|
|
34661
|
-
const targetVersion = opts.targetVersion ?? "0.99.0";
|
|
34662
|
-
const fromVersion = opts.fromVersion ?? "0.0.0-test";
|
|
34663
|
-
const tarballBytes = Buffer.from(`harness-tarball-${targetVersion}`, "utf8");
|
|
34664
|
-
const expectedSha = createHash4("sha1").update(tarballBytes).digest("hex");
|
|
34665
|
-
const sleepMs = opts.simulateSleepWakeMs ?? 0;
|
|
34666
|
-
const maybeSleep = async () => {
|
|
34667
|
-
if (sleepMs > 0) {
|
|
34668
|
-
await new Promise((r) => setTimeout(r, sleepMs));
|
|
34669
|
-
}
|
|
34670
|
-
};
|
|
34671
|
-
let spawnCalls = 0;
|
|
34672
|
-
let healthCalls = 0;
|
|
34673
|
-
return {
|
|
34674
|
-
opts: {
|
|
34675
|
-
targetVersion,
|
|
34676
|
-
fromVersion,
|
|
34677
|
-
channel: "latest",
|
|
34678
|
-
currentBinaryDir: join8(slockHome, "harness-current"),
|
|
34679
|
-
drainMode: opts.drain,
|
|
34680
|
-
deps: {
|
|
34681
|
-
npmPack: async (cwd) => {
|
|
34682
|
-
if (opts.simulateNetworkLossAt === "stage") {
|
|
34683
|
-
throw new Error("simulated network loss during stage (npm pack)");
|
|
34684
|
-
}
|
|
34685
|
-
if (opts.simulateFail === "stage") {
|
|
34686
|
-
return { tarballPath: "", exitCode: 1, stderr: "simulated stage failure" };
|
|
34687
|
-
}
|
|
34688
|
-
const filename = `slock-ai-computer-${targetVersion}.tgz`;
|
|
34689
|
-
await writeFile12(join8(cwd, filename), tarballBytes);
|
|
34690
|
-
return { tarballPath: join8(cwd, filename), exitCode: 0, stderr: "" };
|
|
34691
|
-
},
|
|
34692
|
-
fetchAdvertisedHash: async () => {
|
|
34693
|
-
if (opts.simulateNetworkLossAt === "verify") return null;
|
|
34694
|
-
if (opts.simulateFail === "verify") return "deadbeef";
|
|
34695
|
-
return expectedSha;
|
|
34696
|
-
},
|
|
34697
|
-
tarSpawn: async (_t, destDir) => {
|
|
34698
|
-
if (opts.simulateFail === "extract") {
|
|
34699
|
-
return { exitCode: 1, stderr: "simulated extract failure" };
|
|
34700
|
-
}
|
|
34701
|
-
await mkdir15(join8(destDir, "package"), { recursive: true });
|
|
34702
|
-
await writeFile12(join8(destDir, "package", "marker.txt"), `NEW@${targetVersion}`);
|
|
34703
|
-
return { exitCode: 0, stderr: "" };
|
|
34704
|
-
},
|
|
34705
|
-
npmInstall: async () => ({ exitCode: 0, stderr: "" }),
|
|
34706
|
-
fsRename: opts.simulateFail === "swap" ? async () => {
|
|
34707
|
-
throw new Error("simulated swap failure: fsRename");
|
|
34708
|
-
} : void 0,
|
|
34709
|
-
readServicePid: async () => null,
|
|
34710
|
-
spawnFreshService: async () => {
|
|
34711
|
-
await maybeSleep();
|
|
34712
|
-
spawnCalls += 1;
|
|
34713
|
-
if (opts.simulateFail === "restart" && spawnCalls === 1) {
|
|
34714
|
-
throw new Error("simulated spawn failure");
|
|
34715
|
-
}
|
|
34716
|
-
},
|
|
34717
|
-
healthCheck: async () => {
|
|
34718
|
-
await maybeSleep();
|
|
34719
|
-
healthCalls += 1;
|
|
34720
|
-
if (opts.simulateFail === "restart" && spawnCalls <= 1) return false;
|
|
34721
|
-
return true;
|
|
34722
|
-
},
|
|
34723
|
-
healthTimeoutMs: opts.simulateFail === "restart" ? 200 : 5e3,
|
|
34724
|
-
healthPollIntervalMs: 10,
|
|
34725
|
-
listManagedServerIds: async () => {
|
|
34726
|
-
if (opts.simulateFail === "rolling_health") {
|
|
34727
|
-
return ["aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"];
|
|
34728
|
-
}
|
|
34729
|
-
return [];
|
|
34730
|
-
},
|
|
34731
|
-
readDaemonPid: async () => null,
|
|
34732
|
-
perDaemonTimeoutMs: 50,
|
|
34733
|
-
daemonPollIntervalMs: 10,
|
|
34734
|
-
// PR-E 18/n phase 1.5 dep-drift preflight: the harness mocks
|
|
34735
|
-
// `npmPack` to write fake tarball BYTES (not a real tar archive),
|
|
34736
|
-
// so the production preflight's `tar -xzOf` would fail. Inject
|
|
34737
|
-
// an always-passing predicate so the harness keeps exercising
|
|
34738
|
-
// phases 3-6 (the surface it was built to cover). Dep-drift
|
|
34739
|
-
// refusal semantics are covered by `preflightDepDrift.test.ts`
|
|
34740
|
-
// and the new `runUpgrade: preflight fail …` integration tests
|
|
34741
|
-
// in `upgrade.test.ts`, not here.
|
|
34742
|
-
preflight: {
|
|
34743
|
-
readTarballPackageJson: async () => ({
|
|
34744
|
-
dependencies: { "@slock-ai/daemon": "^0.0.0-harness" }
|
|
34745
|
-
}),
|
|
34746
|
-
readCurrentPackageJson: async () => ({
|
|
34747
|
-
dependencies: { "@slock-ai/daemon": "^0.0.0-harness" }
|
|
34748
|
-
}),
|
|
34749
|
-
readInstalledDaemonVersion: async () => "0.0.0-harness",
|
|
34750
|
-
semverSatisfies: () => true,
|
|
34751
|
-
allowUnsafeSpec: true
|
|
34752
|
-
}
|
|
34753
|
-
// snapshot-phase failure: caller arranges that `mkdir` on the
|
|
34754
|
-
// snapshot path fails. We don't have a snapshot-phase dep hook,
|
|
34755
|
-
// so we instead pre-create the snapshot file as a directory:
|
|
34756
|
-
// that's handled outside `deps` via `arrangeSnapshotFailure`.
|
|
34757
|
-
}
|
|
34758
|
-
}
|
|
34759
|
-
};
|
|
34760
|
-
}
|
|
34761
|
-
async function arrangeSnapshotFailure(slockHome) {
|
|
34762
|
-
const snapshotPath = join8(slockHome, "computer", "upgrade-snapshot.json");
|
|
34763
|
-
await mkdir15(snapshotPath, { recursive: true });
|
|
34764
|
-
}
|
|
34765
|
-
async function pathInfo(path3) {
|
|
34766
|
-
try {
|
|
34767
|
-
const s = await stat4(path3);
|
|
34768
|
-
if (s.isDirectory()) return { kind: "dir", size: 0 };
|
|
34769
|
-
if (s.isFile()) return { kind: "file", size: s.size };
|
|
34770
|
-
return null;
|
|
34771
|
-
} catch {
|
|
34772
|
-
return null;
|
|
34773
|
-
}
|
|
34774
|
-
}
|
|
34775
|
-
async function captureStateSnapshot(slockHome, version, currentBinaryDir) {
|
|
34776
|
-
const stagingPath = upgradeStagingDir(slockHome, version);
|
|
34777
|
-
const stagingExists = await pathInfo(stagingPath);
|
|
34778
|
-
let stagingEntries = null;
|
|
34779
|
-
if (stagingExists?.kind === "dir") {
|
|
34780
|
-
try {
|
|
34781
|
-
stagingEntries = (await readdir4(stagingPath)).sort();
|
|
34782
|
-
} catch {
|
|
34783
|
-
stagingEntries = null;
|
|
34784
|
-
}
|
|
34785
|
-
}
|
|
34786
|
-
let servers = [];
|
|
34787
|
-
try {
|
|
34788
|
-
const ids = await readdir4(serversDir(slockHome));
|
|
34789
|
-
servers = await Promise.all(
|
|
34790
|
-
ids.map(async (serverId) => ({
|
|
34791
|
-
serverId,
|
|
34792
|
-
attachment: await pathInfo(serverAttachmentPath(slockHome, serverId)),
|
|
34793
|
-
managed: await pathInfo(serverManagedFlagPath(slockHome, serverId))
|
|
34794
|
-
}))
|
|
34795
|
-
);
|
|
34796
|
-
} catch {
|
|
34797
|
-
}
|
|
34798
|
-
return {
|
|
34799
|
-
upgradeSnapshot: await pathInfo(upgradeSnapshotPath(slockHome)),
|
|
34800
|
-
stagingDir: stagingExists,
|
|
34801
|
-
stagingEntries,
|
|
34802
|
-
servicePid: await pathInfo(servicePidPath(slockHome)),
|
|
34803
|
-
channelFile: await pathInfo(join8(computerDir(slockHome), "channel")),
|
|
34804
|
-
computerDir: await pathInfo(computerDir(slockHome)),
|
|
34805
|
-
servers,
|
|
34806
|
-
prevBinary: await pathInfo(currentBinaryDir + ".prev"),
|
|
34807
|
-
currentBinary: await pathInfo(currentBinaryDir)
|
|
34808
|
-
};
|
|
34809
|
-
}
|
|
34810
|
-
async function runUpgradeTestHarness(slockHome, opts, writer = (s) => process.stdout.write(s)) {
|
|
34811
|
-
const err = validateHarnessOptions(opts);
|
|
34812
|
-
if (err !== null) {
|
|
34813
|
-
process.stderr.write(`__upgrade-test: ${err}
|
|
34814
|
-
`);
|
|
34815
|
-
process.exitCode = 1;
|
|
34816
|
-
return;
|
|
34817
|
-
}
|
|
34818
|
-
await mkdir15(slockHome, { recursive: true });
|
|
34819
|
-
if (opts.simulateFail === "snapshot") {
|
|
34820
|
-
await arrangeSnapshotFailure(slockHome);
|
|
34821
|
-
}
|
|
34822
|
-
const { opts: upgradeOpts } = buildSimulatedDeps(slockHome, opts);
|
|
34823
|
-
await mkdir15(upgradeOpts.currentBinaryDir, { recursive: true });
|
|
34824
|
-
let outcome;
|
|
34825
|
-
try {
|
|
34826
|
-
outcome = await runUpgrade(slockHome, upgradeOpts);
|
|
34827
|
-
} catch (e) {
|
|
34828
|
-
outcome = {
|
|
34829
|
-
ok: false,
|
|
34830
|
-
phase: "stage",
|
|
34831
|
-
reason: `harness_exception: ${e instanceof Error ? e.message : String(e)}`
|
|
34832
|
-
};
|
|
34833
|
-
}
|
|
34834
|
-
const state = await captureStateSnapshot(
|
|
34835
|
-
slockHome,
|
|
34836
|
-
upgradeOpts.targetVersion,
|
|
34837
|
-
upgradeOpts.currentBinaryDir
|
|
34838
|
-
);
|
|
34839
|
-
const payload = {
|
|
34840
|
-
ok: outcome.ok,
|
|
34841
|
-
phase: outcome.phase,
|
|
34842
|
-
reason: outcome.reason ?? null,
|
|
34843
|
-
rolledBack: outcome.rolledBack ?? false,
|
|
34844
|
-
// Operational rollback per-step status, surfaced so QA can assert
|
|
34845
|
-
// that disk + process both recovered (not just disk). null when no
|
|
34846
|
-
// rollback was attempted (phases ≤ swap, or ok=true).
|
|
34847
|
-
rollback: outcome.rollback ?? null,
|
|
34848
|
-
simulated: {
|
|
34849
|
-
fail: opts.simulateFail ?? null,
|
|
34850
|
-
networkLossAt: opts.simulateNetworkLossAt ?? null,
|
|
34851
|
-
sleepWakeMs: opts.simulateSleepWakeMs ?? null,
|
|
34852
|
-
drain: opts.drain ?? null
|
|
34853
|
-
},
|
|
34854
|
-
state
|
|
34855
|
-
};
|
|
34856
|
-
writer(JSON.stringify(payload) + "\n");
|
|
34857
|
-
}
|
|
34858
|
-
|
|
34859
|
-
// src/upgradeInstallSmoke.ts
|
|
34860
|
-
init_esm_shims();
|
|
34861
|
-
import { copyFile, mkdir as mkdir16, readFile as readFile16 } from "fs/promises";
|
|
34862
|
-
import { createHash as createHash5 } from "crypto";
|
|
34863
|
-
import { dirname as dirname13, isAbsolute, join as join9, resolve as pathResolve } from "path";
|
|
34864
|
-
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
34865
|
-
async function runUpgradeInstallSmoke(slockHome, opts, deps = {}) {
|
|
34866
|
-
if (typeof opts.packageTarball !== "string" || opts.packageTarball.trim().length === 0) {
|
|
34867
|
-
throw new Error("__upgrade-install-smoke: --package-tarball is required.");
|
|
34868
|
-
}
|
|
34869
|
-
const tarballPath = isAbsolute(opts.packageTarball) ? opts.packageTarball : pathResolve(opts.packageTarball);
|
|
34870
|
-
let tarballBytes;
|
|
34871
|
-
try {
|
|
34872
|
-
tarballBytes = await readFile16(tarballPath);
|
|
34873
|
-
} catch (e) {
|
|
34874
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
34875
|
-
throw new Error(`__upgrade-install-smoke: cannot read --package-tarball ${tarballPath}: ${msg}`);
|
|
34876
|
-
}
|
|
34877
|
-
const tarballSha1 = createHash5("sha1").update(tarballBytes).digest("hex");
|
|
34878
|
-
const currentBinaryDir = opts.currentBinaryDir ?? (deps.currentBinaryDir ?? defaultCurrentBinaryDirLocal)();
|
|
34879
|
-
const fromVersion = opts.fromVersion ?? await (deps.currentVersion ?? defaultCurrentVersionLocal)();
|
|
34880
|
-
const channel2 = opts.channel ?? "latest";
|
|
34881
|
-
const spawnFreshService = deps.spawnFreshService ?? (async (h) => {
|
|
34882
|
-
await spawnDetachedService(h);
|
|
34883
|
-
});
|
|
34884
|
-
await mkdir16(slockHome, { recursive: true });
|
|
34885
|
-
const outcome = await runUpgrade(slockHome, {
|
|
34886
|
-
targetVersion: opts.targetVersion,
|
|
34887
|
-
fromVersion,
|
|
34888
|
-
channel: channel2,
|
|
34889
|
-
currentBinaryDir,
|
|
34890
|
-
deps: {
|
|
34891
|
-
// QA-only bypass of `npm pack <pkg>@<ver>`: copy the caller-supplied
|
|
34892
|
-
// tarball into the staging dir under the expected filename. This is
|
|
34893
|
-
// the ONLY deviation from production `runUpgrade` plumbing — every
|
|
34894
|
-
// other dep falls back to its production default.
|
|
34895
|
-
npmPack: async (cwd) => {
|
|
34896
|
-
const filename = `slock-ai-computer-${opts.targetVersion}.tgz`;
|
|
34897
|
-
const stagedTarball = join9(cwd, filename);
|
|
34898
|
-
await copyFile(tarballPath, stagedTarball);
|
|
34899
|
-
return { tarballPath: stagedTarball, exitCode: 0, stderr: "" };
|
|
34900
|
-
},
|
|
34901
|
-
// We trust the local tarball (caller built it from source via
|
|
34902
|
-
// build-disposable-install.mjs). Return its sha1 as the
|
|
34903
|
-
// "advertised" hash so verify is always satisfied.
|
|
34904
|
-
fetchAdvertisedHash: async () => tarballSha1,
|
|
34905
|
-
// Local QA tarballs are built from the workspace and may still
|
|
34906
|
-
// carry workspace/file specs. The production upgrade path hydrates
|
|
34907
|
-
// dependencies from the registry; this smoke isolates the swap /
|
|
34908
|
-
// restart mechanics and keeps dependency hydration out of scope.
|
|
34909
|
-
npmInstall: async () => ({ exitCode: 0, stderr: "" }),
|
|
34910
|
-
spawnFreshService: () => spawnFreshService(slockHome),
|
|
34911
|
-
// PR-E 18/n: the workspace-built tarball still carries
|
|
34912
|
-
// `workspace:*` (or `file:` after pack-rewrite in some workflows)
|
|
34913
|
-
// for `@slock-ai/daemon`, which production preflight rejects as
|
|
34914
|
-
// unsafe. The harness opts in to `allowUnsafeSpec` so the smoke
|
|
34915
|
-
// can run; production `runUpgrade` never sets this flag. The
|
|
34916
|
-
// spec-equality + installed-satisfies checks still run on
|
|
34917
|
-
// semver-shaped specs.
|
|
34918
|
-
preflight: { allowUnsafeSpec: true }
|
|
34919
|
-
}
|
|
34920
|
-
});
|
|
34921
|
-
const activeVersion = await readServiceVersionEvidence(slockHome);
|
|
34922
|
-
return { outcome, activeVersion };
|
|
34923
|
-
}
|
|
34924
|
-
async function runUpgradeInstallSmokeCli(slockHome, opts, writer = (s) => process.stdout.write(s), deps = {}) {
|
|
34925
|
-
let report;
|
|
34926
|
-
try {
|
|
34927
|
-
report = await runUpgradeInstallSmoke(slockHome, opts, deps);
|
|
34928
|
-
} catch (e) {
|
|
34929
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
34930
|
-
writer(
|
|
34931
|
-
JSON.stringify({
|
|
34932
|
-
outcome: { ok: false, phase: "stage", reason: `harness_exception: ${msg}` },
|
|
34933
|
-
activeVersion: null
|
|
34934
|
-
}) + "\n"
|
|
34935
|
-
);
|
|
34936
|
-
return;
|
|
34937
|
-
}
|
|
34938
|
-
writer(JSON.stringify(report) + "\n");
|
|
34939
|
-
}
|
|
34940
|
-
function defaultCurrentBinaryDirLocal() {
|
|
34941
|
-
const here = fileURLToPath4(import.meta.url);
|
|
34942
|
-
return dirname13(dirname13(here));
|
|
34943
|
-
}
|
|
34944
|
-
async function defaultCurrentVersionLocal() {
|
|
34945
|
-
const pkgPath = join9(defaultCurrentBinaryDirLocal(), "package.json");
|
|
34946
|
-
try {
|
|
34947
|
-
const raw = await readFile16(pkgPath, "utf8");
|
|
34948
|
-
const parsed = JSON.parse(raw);
|
|
34949
|
-
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
34950
|
-
return parsed.version;
|
|
34951
|
-
}
|
|
34952
|
-
} catch {
|
|
34953
|
-
}
|
|
34954
|
-
throw new Error(
|
|
34955
|
-
"__upgrade-install-smoke: cannot read current binary version from package.json; pass --from-version explicitly."
|
|
34956
|
-
);
|
|
34957
|
-
}
|
|
34958
|
-
|
|
34959
33874
|
// src/index.ts
|
|
34960
33875
|
function withCliExit(fn) {
|
|
34961
33876
|
return async (...args) => {
|
|
@@ -34970,17 +33885,20 @@ function withCliExit(fn) {
|
|
|
34970
33885
|
}
|
|
34971
33886
|
};
|
|
34972
33887
|
}
|
|
33888
|
+
var FOREGROUND_DESC = "run the service in this terminal instead of the background";
|
|
33889
|
+
var SERVER_SLUG_TARGET_DESC = "target Slock server slug (canonical form `/myserver`; bare `myserver` accepted)";
|
|
33890
|
+
var SERVER_SLUG_OPTIONAL_DESC = "optional: scope to one attached server (canonical `/myserver`; bare accepted; default: all attached)";
|
|
34973
33891
|
var program2 = new Command();
|
|
34974
33892
|
program2.name("slock-computer").description("Slock Computer \u2014 local-machine control plane (login + N per-server attachments).").version(COMPUTER_VERSION);
|
|
34975
33893
|
program2.command("login").description("Log in via device-code (one user identity per Computer / SLOCK_HOME).").option("--server-url <url>", `Slock API base URL; defaults to SLOCK_SERVER_URL or ${DEFAULT_SLOCK_SERVER_URL}`).action(withCliExit(async (opts) => {
|
|
34976
33894
|
await runLogin({ serverUrl: opts.serverUrl });
|
|
34977
33895
|
}));
|
|
34978
|
-
program2.command("attach").argument("<serverSlug>",
|
|
33896
|
+
program2.command("attach").argument("<serverSlug>", SERVER_SLUG_TARGET_DESC).description("Attach this Computer to one Slock server (add-not-replace; multi-server OK).").option("--server-url <url>", `Slock API base URL; defaults to the saved user session, SLOCK_SERVER_URL, or ${DEFAULT_SLOCK_SERVER_URL}`).option("--name <name>", "Computer display name; defaults to a sanitized hostname").option("--no-run", "only authorize + write local state; do not start the service").option("--foreground", FOREGROUND_DESC).action(withCliExit(async (serverSlug, opts) => {
|
|
34979
33897
|
await withMutationLock(
|
|
34980
33898
|
() => runAttach({ serverSlug, serverUrl: opts.serverUrl, name: opts.name, run: opts.run, foreground: opts.foreground })
|
|
34981
33899
|
);
|
|
34982
33900
|
}));
|
|
34983
|
-
program2.command("setup").argument("<serverSlug>",
|
|
33901
|
+
program2.command("setup").argument("<serverSlug>", SERVER_SLUG_TARGET_DESC).description("Set up this Computer for one server: login if needed, attach (or \xA7X.1 migrate-prompt) if needed, then start.").option("--server-url <url>", `Slock API base URL; defaults to the saved user session, SLOCK_SERVER_URL, or ${DEFAULT_SLOCK_SERVER_URL}`).option("--name <name>", "Computer display name for a new attachment; defaults to a sanitized hostname").option("--no-start", "stop after login + attach; do not start the service").option("--foreground", FOREGROUND_DESC).option("-y, --yes", "allow non-interactive setup after confirming the planned actions").action(
|
|
34984
33902
|
withCliExit(async (serverSlug, opts) => {
|
|
34985
33903
|
await withMutationLock(
|
|
34986
33904
|
() => runSetup({
|
|
@@ -34994,13 +33912,13 @@ program2.command("setup").argument("<serverSlug>", "target Slock server slug (ca
|
|
|
34994
33912
|
);
|
|
34995
33913
|
})
|
|
34996
33914
|
);
|
|
34997
|
-
program2.command("detach").argument("<serverSlug>",
|
|
33915
|
+
program2.command("detach").argument("<serverSlug>", `${SERVER_SLUG_TARGET_DESC} (the server to detach from this Computer)`).description("Remove ONE server's local attachment; never touches user-session or other servers.").action(withCliExit(async (serverSlug) => {
|
|
34998
33916
|
await withMutationLock(async () => runDetach(await resolveTargetServerId({ server: serverSlug }), serverSlug));
|
|
34999
33917
|
}));
|
|
35000
33918
|
program2.command("status").description("Show this Computer's aggregate state (login + service + per-server server-runners).").option("--json", "emit the machine-readable report").action(withCliExit(async (opts) => {
|
|
35001
33919
|
await runStatus({ json: opts.json });
|
|
35002
33920
|
}));
|
|
35003
|
-
program2.command("start").argument("[serverSlug]",
|
|
33921
|
+
program2.command("start").argument("[serverSlug]", SERVER_SLUG_OPTIONAL_DESC).description("Start/ensure the Computer service (manages all per-server server-runners).").option("--foreground", FOREGROUND_DESC).action(withCliExit(async (serverSlug, opts) => {
|
|
35004
33922
|
await withMutationLock(
|
|
35005
33923
|
async () => runStart({
|
|
35006
33924
|
foreground: opts.foreground,
|
|
@@ -35012,17 +33930,25 @@ program2.command("start").argument("[serverSlug]", "optional: verify this server
|
|
|
35012
33930
|
program2.command("stop").description("Stop the Computer service (and all managed per-server server-runners).").action(withCliExit(async () => {
|
|
35013
33931
|
await withMutationLock(() => runStop());
|
|
35014
33932
|
}));
|
|
35015
|
-
program2.command("
|
|
33933
|
+
program2.command("restart").argument("[serverSlug]", SERVER_SLUG_OPTIONAL_DESC).description("Restart the Computer service (stop + start; all managed per-server server-runners).").option("--foreground", FOREGROUND_DESC).action(withCliExit(async (serverSlug, opts) => {
|
|
33934
|
+
await withMutationLock(async () => {
|
|
33935
|
+
await runStop();
|
|
33936
|
+
await runStart({
|
|
33937
|
+
foreground: opts.foreground,
|
|
33938
|
+
serverId: serverSlug ? await resolveTargetServerId({ server: serverSlug }) : null,
|
|
33939
|
+
serverLabel: serverSlug ?? null
|
|
33940
|
+
});
|
|
33941
|
+
});
|
|
33942
|
+
}));
|
|
33943
|
+
program2.command("doctor").argument("[serverSlug]", `${SERVER_SLUG_OPTIONAL_DESC} (scopes recent-crash detail to that server)`).description("Diagnose login + per-server attachments + per-server preflight (no secrets).").option("--json", "emit the machine-readable report").option("--fix", "after diagnosis, run the local residue cleanup pass").action(
|
|
35016
33944
|
withCliExit(
|
|
35017
33945
|
async (serverSlug, opts) => {
|
|
35018
33946
|
const serverId = serverSlug ? await resolveTargetServerId({ server: serverSlug }) : void 0;
|
|
35019
33947
|
await runDoctor({
|
|
35020
33948
|
json: opts.json,
|
|
35021
|
-
cleanup: opts.
|
|
35022
|
-
fix: opts.fix,
|
|
33949
|
+
cleanup: opts.fix,
|
|
35023
33950
|
serverId,
|
|
35024
|
-
serverLabel: serverSlug
|
|
35025
|
-
resetHealth: opts.resetHealth
|
|
33951
|
+
serverLabel: serverSlug
|
|
35026
33952
|
});
|
|
35027
33953
|
}
|
|
35028
33954
|
)
|
|
@@ -35039,8 +33965,8 @@ program2.command("reset").description("Clear a degraded state and resume the ser
|
|
|
35039
33965
|
);
|
|
35040
33966
|
})
|
|
35041
33967
|
);
|
|
35042
|
-
program2.command("logs").description("Tail one server's server-runner log (or the service log); secrets redacted.").option("--lines <n>", "trailing lines to show (default 200)", (v) => Number.parseInt(v, 10)).option("--
|
|
35043
|
-
await runLogs({ lines: opts.lines, server:
|
|
33968
|
+
program2.command("logs").argument("[serverSlug]", `${SERVER_SLUG_OPTIONAL_DESC} (required when \u22652 attached; ignored with --service)`).description("Tail one server's server-runner log (or the service log); secrets redacted.").option("--lines <n>", "trailing lines to show (default 200)", (v) => Number.parseInt(v, 10)).option("--service", "tail the global service log instead of a per-server server-runner log").action(withCliExit(async (serverSlug, opts) => {
|
|
33969
|
+
await runLogs({ lines: opts.lines, server: serverSlug ?? null, service: !!opts.service });
|
|
35044
33970
|
}));
|
|
35045
33971
|
var runners = program2.command("runners").description("Computer runner control plane (per-server scoped; \xA712 whitelist server-side).");
|
|
35046
33972
|
runners.command("list").description("List runners on one attached server.").option("--json", "emit the machine-readable list").option("--server <slug>", "select target server slug (required when \u22652 attached)").action(withCliExit(async (opts) => {
|
|
@@ -35062,26 +33988,16 @@ channel.command("set").argument("<channel>", "channel value: latest | alpha | pi
|
|
|
35062
33988
|
);
|
|
35063
33989
|
program2.command("upgrade").description(
|
|
35064
33990
|
[
|
|
35065
|
-
"Auto-upgrade this Computer to the latest version in its channel (or --target-version <semver>).",
|
|
33991
|
+
"Auto-upgrade this Computer (SEA single-binary) to the latest version in its channel (or --target-version <semver>).",
|
|
35066
33992
|
"",
|
|
35067
|
-
"
|
|
35068
|
-
"
|
|
33993
|
+
"Downloads + sha256-verifies the target-version binary for this platform,",
|
|
33994
|
+
"then atomically swaps the running single executable. The managed service",
|
|
33995
|
+
"supervisor restarts on the swapped binary. SEA-only: a non-SEA (npm/dev)",
|
|
33996
|
+
"run has no single binary to self-swap and is refused (UPGRADE_SEA_ONLY)."
|
|
35069
33997
|
].join("\n")
|
|
35070
|
-
).option("--dry-run", "
|
|
33998
|
+
).option("--dry-run", "download + sha256-verify only; do not swap or restart").option("--channel <name>", "override channel for this invocation (latest | alpha | pinned:<semver>)").option("--target-version <semver>", "override target version explicitly (bypasses channel resolution)").action(
|
|
35071
33999
|
withCliExit(
|
|
35072
34000
|
async (opts) => {
|
|
35073
|
-
let drain;
|
|
35074
|
-
if (opts.drain !== void 0) {
|
|
35075
|
-
if (opts.drain !== "drain" && opts.drain !== "defer" && opts.drain !== "force") {
|
|
35076
|
-
process.stderr.write(
|
|
35077
|
-
`slock-computer: UPGRADE_DRAIN_INVALID: Invalid --drain "${opts.drain}". Accepted: drain | defer | force.
|
|
35078
|
-
`
|
|
35079
|
-
);
|
|
35080
|
-
process.exitCode = 1;
|
|
35081
|
-
return;
|
|
35082
|
-
}
|
|
35083
|
-
drain = opts.drain;
|
|
35084
|
-
}
|
|
35085
34001
|
const slockHome = resolveSlockHome();
|
|
35086
34002
|
const trigger = resolveUpgradeTrigger(process.env.SLOCK_UPGRADE_TRIGGER);
|
|
35087
34003
|
try {
|
|
@@ -35090,8 +34006,6 @@ program2.command("upgrade").description(
|
|
|
35090
34006
|
dryRun: opts.dryRun,
|
|
35091
34007
|
channel: opts.channel,
|
|
35092
34008
|
targetVersion: opts.targetVersion,
|
|
35093
|
-
drain,
|
|
35094
|
-
force: opts.force,
|
|
35095
34009
|
trigger
|
|
35096
34010
|
})
|
|
35097
34011
|
);
|
|
@@ -35128,43 +34042,29 @@ program2.command("__service", { hidden: true }).action(withCliExit(async () => {
|
|
|
35128
34042
|
program2.command("__run", { hidden: true }).argument("<serverId>", "server id this daemon child is bound to").action(withCliExit(async (serverId) => {
|
|
35129
34043
|
await runResident(serverId);
|
|
35130
34044
|
}));
|
|
35131
|
-
|
|
35132
|
-
|
|
35133
|
-
const
|
|
35134
|
-
|
|
35135
|
-
|
|
34045
|
+
async function runCli() {
|
|
34046
|
+
if (process.argv[2] === "__cli") {
|
|
34047
|
+
const { runBundledSlockCli } = await import("@slock-ai/daemon/core");
|
|
34048
|
+
await runBundledSlockCli(process.argv.slice(3));
|
|
34049
|
+
return;
|
|
34050
|
+
}
|
|
34051
|
+
await program2.parseAsync(process.argv);
|
|
34052
|
+
}
|
|
34053
|
+
var invokedAsMain = process.argv[1] !== void 0 && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
34054
|
+
if (invokedAsMain) {
|
|
34055
|
+
runCli().catch((err) => {
|
|
34056
|
+
process.stderr.write(`slock-computer: ${err instanceof Error ? err.message : String(err)}
|
|
35136
34057
|
`);
|
|
35137
|
-
|
|
35138
|
-
|
|
35139
|
-
}
|
|
35140
|
-
await runUpgradeTestHarness(resolveSlockHome(), {
|
|
35141
|
-
simulateFail: opts.simulateFail,
|
|
35142
|
-
simulateNetworkLossAt: opts.simulateNetworkLossAt,
|
|
35143
|
-
simulateSleepWakeMs: opts.simulateSleepWake !== void 0 ? Number(opts.simulateSleepWake) : void 0,
|
|
35144
|
-
drain,
|
|
35145
|
-
targetVersion: opts.targetVersion,
|
|
35146
|
-
fromVersion: opts.fromVersion
|
|
35147
|
-
});
|
|
35148
|
-
})
|
|
35149
|
-
);
|
|
35150
|
-
program2.command("__upgrade-install-smoke", { hidden: true }).requiredOption("--package-tarball <path>", "local tarball to swap in for stage (QA only)").option("--target-version <semver>", "target version label", "0.99.0-smoke").option("--from-version <semver>", "from-version label (defaults to current binary's package.json version)").option("--channel <name>", "channel label for snapshot", "latest").option("--current-binary-dir <path>", "override install root (defaults to currently-running binary)").action(
|
|
35151
|
-
withCliExit(async (opts) => {
|
|
35152
|
-
await withMutationLock(
|
|
35153
|
-
() => runUpgradeInstallSmokeCli(resolveSlockHome(), {
|
|
35154
|
-
packageTarball: opts.packageTarball,
|
|
35155
|
-
targetVersion: opts.targetVersion,
|
|
35156
|
-
fromVersion: opts.fromVersion,
|
|
35157
|
-
channel: opts.channel,
|
|
35158
|
-
currentBinaryDir: opts.currentBinaryDir
|
|
35159
|
-
})
|
|
35160
|
-
);
|
|
35161
|
-
})
|
|
35162
|
-
);
|
|
35163
|
-
program2.parseAsync(process.argv).catch((err) => {
|
|
35164
|
-
process.stderr.write(`slock-computer: ${err instanceof Error ? err.message : String(err)}
|
|
34058
|
+
if (process.env.SLOCK_COMPUTER_DEBUG_STACK && err instanceof Error && err.stack) {
|
|
34059
|
+
process.stderr.write(`${err.stack}
|
|
35165
34060
|
`);
|
|
35166
|
-
|
|
35167
|
-
|
|
34061
|
+
}
|
|
34062
|
+
process.exitCode = 1;
|
|
34063
|
+
});
|
|
34064
|
+
}
|
|
34065
|
+
export {
|
|
34066
|
+
program2 as program
|
|
34067
|
+
};
|
|
35168
34068
|
/*! Bundled license information:
|
|
35169
34069
|
|
|
35170
34070
|
undici/lib/web/fetch/body.js:
|