usebeeline 0.1.57 → 0.1.59
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/usebeeline.mjs +283 -84
- package/package.json +1 -1
package/dist/usebeeline.mjs
CHANGED
|
@@ -126,6 +126,152 @@ var init_lifecycle_test_guard = __esm({
|
|
|
126
126
|
}
|
|
127
127
|
});
|
|
128
128
|
|
|
129
|
+
// apps/body/dist/trusty-squire-storage.js
|
|
130
|
+
import { homedir as homedir2 } from "node:os";
|
|
131
|
+
import { isAbsolute, resolve as resolve4 } from "node:path";
|
|
132
|
+
function trustySquireLegacyStorePaths(operatorHome = homedir2(), env = process.env) {
|
|
133
|
+
const roots = /* @__PURE__ */ new Set([resolve4(operatorHome, ".config")]);
|
|
134
|
+
const xdgConfig = env.XDG_CONFIG_HOME?.trim();
|
|
135
|
+
if (xdgConfig && isAbsolute(xdgConfig))
|
|
136
|
+
roots.add(resolve4(xdgConfig));
|
|
137
|
+
return [...roots].map((root) => resolve4(root, "trusty-squire"));
|
|
138
|
+
}
|
|
139
|
+
var init_trusty_squire_storage = __esm({
|
|
140
|
+
"apps/body/dist/trusty-squire-storage.js"() {
|
|
141
|
+
"use strict";
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// apps/body/dist/storage-filesystem.js
|
|
146
|
+
import { chmod, lstat as lstat2, readdir as readdir2, realpath, rm as rm2 } from "node:fs/promises";
|
|
147
|
+
import { resolve as resolve5, sep } from "node:path";
|
|
148
|
+
import { spawn } from "node:child_process";
|
|
149
|
+
function canonicalStoragePath(path) {
|
|
150
|
+
const absolute = resolve5(path);
|
|
151
|
+
return process.platform === "darwin" ? absolute.replace(/^\/(var|tmp|etc)(?=\/|$)/, "/private/$1") : absolute;
|
|
152
|
+
}
|
|
153
|
+
async function ordinaryDirectory(path) {
|
|
154
|
+
const info = await lstat2(path).catch(() => void 0);
|
|
155
|
+
return Boolean(info?.isDirectory() && canonicalStoragePath(await realpath(path)) === canonicalStoragePath(path));
|
|
156
|
+
}
|
|
157
|
+
async function storageTree(root) {
|
|
158
|
+
if (process.platform === "darwin") {
|
|
159
|
+
const tree2 = /* @__PURE__ */ new Map();
|
|
160
|
+
const visit = async (path) => {
|
|
161
|
+
const info = await lstat2(path);
|
|
162
|
+
tree2.set(path, {
|
|
163
|
+
dev: info.dev,
|
|
164
|
+
ino: info.ino,
|
|
165
|
+
blocks: info.blocks * 512,
|
|
166
|
+
links: info.nlink,
|
|
167
|
+
directory: info.isDirectory(),
|
|
168
|
+
lastUsed: Math.max(info.atimeMs, info.mtimeMs)
|
|
169
|
+
});
|
|
170
|
+
if (info.isDirectory() && !isSquirePath(path))
|
|
171
|
+
for (const name of await readdir2(path))
|
|
172
|
+
await visit(resolve5(path, name));
|
|
173
|
+
};
|
|
174
|
+
await visit(root);
|
|
175
|
+
return tree2;
|
|
176
|
+
}
|
|
177
|
+
const child = spawn("/usr/bin/python3", ["-c", TREE_INVENTORY, root], { stdio: ["ignore", "pipe", "pipe"] });
|
|
178
|
+
const tree = /* @__PURE__ */ new Map();
|
|
179
|
+
let pending = "", stderr = "";
|
|
180
|
+
child.stderr.setEncoding("utf8");
|
|
181
|
+
child.stderr.on("data", (chunk) => {
|
|
182
|
+
stderr = (stderr + chunk).slice(-8192);
|
|
183
|
+
});
|
|
184
|
+
const completed = new Promise((done, fail) => {
|
|
185
|
+
child.once("error", fail);
|
|
186
|
+
child.once("close", (code) => code === 0 ? done() : fail(new Error(`storage inventory failed (${code}): ${stderr}`)));
|
|
187
|
+
});
|
|
188
|
+
void completed.catch(() => void 0);
|
|
189
|
+
const add = (line) => {
|
|
190
|
+
const [path, dev, ino, blocks, links, directory, lastUsed] = JSON.parse(line);
|
|
191
|
+
tree.set(path, { dev, ino, blocks, links, directory, lastUsed });
|
|
192
|
+
};
|
|
193
|
+
child.stdout.setEncoding("utf8");
|
|
194
|
+
for await (const chunk of child.stdout) {
|
|
195
|
+
pending += chunk;
|
|
196
|
+
let newline;
|
|
197
|
+
while ((newline = pending.indexOf("\n")) >= 0) {
|
|
198
|
+
add(pending.slice(0, newline));
|
|
199
|
+
pending = pending.slice(newline + 1);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
await completed;
|
|
203
|
+
if (pending)
|
|
204
|
+
add(pending);
|
|
205
|
+
return tree;
|
|
206
|
+
}
|
|
207
|
+
function isSquirePath(path) {
|
|
208
|
+
const absolute = resolve5(path);
|
|
209
|
+
return absolute.split(sep).some((part, index, parts) => [".trusty-squire", "squire-host-config"].includes(part) || part === "trusty-squire" && parts[index - 1] !== "skills") || trustySquireLegacyStorePaths().some((root) => absolute === root || absolute.startsWith(`${root}${sep}`));
|
|
210
|
+
}
|
|
211
|
+
async function removeStoragePath(root, knownTree) {
|
|
212
|
+
const original = knownTree ?? await storageTree(root);
|
|
213
|
+
let index = removalIndexes.get(original);
|
|
214
|
+
if (!index) {
|
|
215
|
+
index = {
|
|
216
|
+
excluded: [...original.keys()].filter(isSquirePath),
|
|
217
|
+
directories: [...original].filter(([, item]) => item.directory).map(([path]) => path).sort()
|
|
218
|
+
};
|
|
219
|
+
removalIndexes.set(original, index);
|
|
220
|
+
}
|
|
221
|
+
const { excluded, directories } = index;
|
|
222
|
+
if (excluded.some((path) => path === root || path.startsWith(`${root}/`)))
|
|
223
|
+
throw new Error("refusing removal of Trusty Squire storage");
|
|
224
|
+
if (isSquirePath(root))
|
|
225
|
+
throw new Error("refusing removal of Trusty Squire storage");
|
|
226
|
+
const prefix = `${root}/`;
|
|
227
|
+
let low = 0, high = directories.length;
|
|
228
|
+
while (low < high) {
|
|
229
|
+
const middle = low + high >>> 1;
|
|
230
|
+
if (directories[middle] < prefix)
|
|
231
|
+
low = middle + 1;
|
|
232
|
+
else
|
|
233
|
+
high = middle;
|
|
234
|
+
}
|
|
235
|
+
const ownedDirectories = original.get(root)?.directory ? [root] : [];
|
|
236
|
+
for (let cursor = low; cursor < directories.length && directories[cursor].startsWith(prefix); cursor++)
|
|
237
|
+
ownedDirectories.push(directories[cursor]);
|
|
238
|
+
for (const path of ownedDirectories) {
|
|
239
|
+
const info = await lstat2(path).catch((error) => {
|
|
240
|
+
if (error.code === "ENOENT")
|
|
241
|
+
return void 0;
|
|
242
|
+
throw error;
|
|
243
|
+
});
|
|
244
|
+
if (!info)
|
|
245
|
+
continue;
|
|
246
|
+
if (info.isDirectory() && process.getuid?.() === info.uid && (info.mode & 448) !== 448)
|
|
247
|
+
await chmod(path, info.mode | 448);
|
|
248
|
+
}
|
|
249
|
+
await rm2(root, { recursive: true, force: true, maxRetries: 3 });
|
|
250
|
+
}
|
|
251
|
+
var TREE_INVENTORY, removalIndexes;
|
|
252
|
+
var init_storage_filesystem = __esm({
|
|
253
|
+
"apps/body/dist/storage-filesystem.js"() {
|
|
254
|
+
"use strict";
|
|
255
|
+
init_trusty_squire_storage();
|
|
256
|
+
TREE_INVENTORY = String.raw`
|
|
257
|
+
import os, sys, json, stat
|
|
258
|
+
root=sys.argv[1]
|
|
259
|
+
def visit(path):
|
|
260
|
+
s=os.lstat(path)
|
|
261
|
+
directory=stat.S_ISDIR(s.st_mode)
|
|
262
|
+
print(json.dumps([path,s.st_dev,s.st_ino,s.st_blocks*512,s.st_nlink,directory,max(s.st_atime_ns,s.st_mtime_ns)/1000000]))
|
|
263
|
+
if directory and not (os.path.basename(path) in ['.trusty-squire','squire-host-config'] or (os.path.basename(path)=='trusty-squire' and os.path.basename(os.path.dirname(path))!='skills')):
|
|
264
|
+
fd=os.open(path,os.O_RDONLY|os.O_DIRECTORY|os.O_NOATIME|os.O_NOFOLLOW)
|
|
265
|
+
try: names=os.listdir(fd)
|
|
266
|
+
finally: os.close(fd)
|
|
267
|
+
for name in names: visit(os.path.join(path,name))
|
|
268
|
+
visit(root)
|
|
269
|
+
|
|
270
|
+
`;
|
|
271
|
+
removalIndexes = /* @__PURE__ */ new WeakMap();
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
129
275
|
// apps/body/dist/self-update-manifest.js
|
|
130
276
|
function resolveManifestUrl(env = process.env) {
|
|
131
277
|
const override = env.BEELINE_UPDATE_MANIFEST_URL?.trim();
|
|
@@ -252,6 +398,7 @@ __export(self_update_exports, {
|
|
|
252
398
|
nodeVersionAtLeast: () => nodeVersionAtLeast,
|
|
253
399
|
normalizeLegacyBundleShape: () => normalizeLegacyBundleShape,
|
|
254
400
|
prepareLatestRelease: () => prepareLatestRelease,
|
|
401
|
+
pruneHelperReleases: () => pruneHelperReleases,
|
|
255
402
|
readBundleEngineMinimum: () => readBundleEngineMinimum,
|
|
256
403
|
readInstalledBundleIdentity: () => readInstalledBundleIdentity,
|
|
257
404
|
readUpdateState: () => readUpdateState,
|
|
@@ -264,12 +411,12 @@ __export(self_update_exports, {
|
|
|
264
411
|
});
|
|
265
412
|
import { createHash as createHash3 } from "node:crypto";
|
|
266
413
|
import { constants as fsConstants } from "node:fs";
|
|
267
|
-
import { access, chmod, copyFile, lstat as
|
|
268
|
-
import { spawn } from "node:child_process";
|
|
269
|
-
import { homedir as
|
|
270
|
-
import { dirname as dirname4, join, resolve as
|
|
414
|
+
import { access, chmod as chmod2, copyFile, lstat as lstat3, mkdir as mkdir3, open, readFile as readFile2, readdir as readdir3, rename as rename2, rm as rm3, symlink, writeFile as writeFile2 } from "node:fs/promises";
|
|
415
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
416
|
+
import { homedir as homedir3 } from "node:os";
|
|
417
|
+
import { dirname as dirname4, join, resolve as resolve6 } from "node:path";
|
|
271
418
|
function anchorLayout(rawLibDir) {
|
|
272
|
-
const libDir =
|
|
419
|
+
const libDir = resolve6(rawLibDir);
|
|
273
420
|
const segments = libDir.split(/[/\\]/);
|
|
274
421
|
const idx = segments.lastIndexOf(RELEASES_SEGMENT);
|
|
275
422
|
if (idx >= 2 && segments[idx - 1] === "lib") {
|
|
@@ -285,9 +432,9 @@ function anchorLayout(rawLibDir) {
|
|
|
285
432
|
// prefix's bin, NOT <prefix>/lib/bin — deriving it one level up was the
|
|
286
433
|
// defect that made activateRelease write its stable forwarders where
|
|
287
434
|
// nothing executed them, leaving stale raw wrappers in <prefix>/bin.
|
|
288
|
-
binDir:
|
|
435
|
+
binDir: resolve6(libDir, "../../bin"),
|
|
289
436
|
libDir,
|
|
290
|
-
releasesRoot:
|
|
437
|
+
releasesRoot: resolve6(libDir, `../${RELEASES_SEGMENT}`)
|
|
291
438
|
};
|
|
292
439
|
}
|
|
293
440
|
function beelineInstallLayout(env = process.env) {
|
|
@@ -299,18 +446,18 @@ function beelineInstallLayout(env = process.env) {
|
|
|
299
446
|
function releaseIdFromPath(path) {
|
|
300
447
|
if (!path?.trim())
|
|
301
448
|
return void 0;
|
|
302
|
-
const segments =
|
|
449
|
+
const segments = resolve6(path.trim()).split(/[/\\]/);
|
|
303
450
|
const idx = segments.lastIndexOf(RELEASES_SEGMENT);
|
|
304
451
|
const id = idx >= 0 ? segments[idx + 1] : void 0;
|
|
305
452
|
return id ? sanitizeReleaseId(id) : void 0;
|
|
306
453
|
}
|
|
307
454
|
function runningReleaseId(layout, env = process.env, invocationPath = process.argv[1]) {
|
|
308
|
-
const releasesRoot =
|
|
309
|
-
const anchor =
|
|
455
|
+
const releasesRoot = resolve6(layout.releasesRoot);
|
|
456
|
+
const anchor = resolve6(layout.libDir);
|
|
310
457
|
for (const raw of [env.BEELINE_LIB_DIR, invocationPath]) {
|
|
311
458
|
if (!raw?.trim())
|
|
312
459
|
continue;
|
|
313
|
-
const resolved =
|
|
460
|
+
const resolved = resolve6(raw.trim());
|
|
314
461
|
if (resolved !== anchor && !resolved.startsWith(`${releasesRoot}/`))
|
|
315
462
|
continue;
|
|
316
463
|
const id = releaseIdFromPath(resolved);
|
|
@@ -320,8 +467,8 @@ function runningReleaseId(layout, env = process.env, invocationPath = process.ar
|
|
|
320
467
|
return void 0;
|
|
321
468
|
}
|
|
322
469
|
function defaultBeelineInstallLayout(env = process.env) {
|
|
323
|
-
const home = env.HOME?.trim() ||
|
|
324
|
-
return anchorLayout(
|
|
470
|
+
const home = env.HOME?.trim() || homedir3();
|
|
471
|
+
return anchorLayout(resolve6(home, ".local", "lib", "beeline"));
|
|
325
472
|
}
|
|
326
473
|
function discoveredBeelineInstallLayout(env = process.env) {
|
|
327
474
|
const explicitAnchor = env.BEELINE_INSTALL_LIB_DIR?.trim();
|
|
@@ -329,7 +476,7 @@ function discoveredBeelineInstallLayout(env = process.env) {
|
|
|
329
476
|
return anchorLayout(explicitAnchor);
|
|
330
477
|
const explicitBinDir = env.BEELINE_INSTALL_DIR?.trim();
|
|
331
478
|
if (explicitBinDir)
|
|
332
|
-
return anchorLayout(
|
|
479
|
+
return anchorLayout(resolve6(dirname4(resolve6(explicitBinDir)), "lib", "beeline"));
|
|
333
480
|
return defaultBeelineInstallLayout(env);
|
|
334
481
|
}
|
|
335
482
|
function hostPlatformKey() {
|
|
@@ -407,7 +554,7 @@ async function writeUpdateState(layout, state) {
|
|
|
407
554
|
} finally {
|
|
408
555
|
await handle.close();
|
|
409
556
|
}
|
|
410
|
-
await
|
|
557
|
+
await rename2(temporary, path);
|
|
411
558
|
await fsyncDir(dirname4(path));
|
|
412
559
|
}
|
|
413
560
|
async function readInstalledBundleIdentity(layout, _state = {}) {
|
|
@@ -422,7 +569,7 @@ function sanitizeReleaseId(id) {
|
|
|
422
569
|
}
|
|
423
570
|
async function pathKind(path) {
|
|
424
571
|
try {
|
|
425
|
-
const info = await
|
|
572
|
+
const info = await lstat3(path);
|
|
426
573
|
if (info.isSymbolicLink())
|
|
427
574
|
return "symlink";
|
|
428
575
|
return info.isDirectory() ? "directory" : "missing";
|
|
@@ -470,7 +617,7 @@ async function fetchText(url, fetchImpl) {
|
|
|
470
617
|
}
|
|
471
618
|
function run(command, args, timeoutMs) {
|
|
472
619
|
return new Promise((resolveRun) => {
|
|
473
|
-
const child =
|
|
620
|
+
const child = spawn2(command, args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
474
621
|
let stderr = "";
|
|
475
622
|
child.stderr?.setEncoding("utf8");
|
|
476
623
|
child.stderr?.on("data", (chunk) => {
|
|
@@ -566,7 +713,7 @@ async function stageRelease(layout, manifestUrl, published, opts = {}) {
|
|
|
566
713
|
}
|
|
567
714
|
await writeFile2(tempArchive, Buffer.concat(chunks), { mode: 384 });
|
|
568
715
|
const entries = (await new Promise((resolveList, rejectList) => {
|
|
569
|
-
const child =
|
|
716
|
+
const child = spawn2("tar", ["-tzf", tempArchive], { stdio: ["ignore", "pipe", "inherit"] });
|
|
570
717
|
const timer = setTimeout(() => child.kill("SIGKILL"), Math.min(3e4, remaining()));
|
|
571
718
|
let out = "";
|
|
572
719
|
child.stdout?.on("data", (chunk) => {
|
|
@@ -619,10 +766,10 @@ async function stageRelease(layout, manifestUrl, published, opts = {}) {
|
|
|
619
766
|
return releaseId;
|
|
620
767
|
} catch (error) {
|
|
621
768
|
if (!previouslyVerified)
|
|
622
|
-
await
|
|
769
|
+
await rm3(releaseDir, { recursive: true, force: true });
|
|
623
770
|
throw error;
|
|
624
771
|
} finally {
|
|
625
|
-
await
|
|
772
|
+
await rm3(tempArchive, { force: true });
|
|
626
773
|
}
|
|
627
774
|
}
|
|
628
775
|
function forwarderScript(tool) {
|
|
@@ -656,12 +803,13 @@ function forwarderScript(tool) {
|
|
|
656
803
|
async function replaceFile(path, contents, mode) {
|
|
657
804
|
const temp = `${path}.new-${process.pid}`;
|
|
658
805
|
await writeFile2(temp, contents, { mode });
|
|
659
|
-
await
|
|
660
|
-
await
|
|
806
|
+
await chmod2(temp, mode);
|
|
807
|
+
await rename2(temp, path);
|
|
661
808
|
}
|
|
662
|
-
async function activateRelease(layout, releaseId) {
|
|
809
|
+
async function activateRelease(layout, releaseId, options = {}) {
|
|
663
810
|
assertInstallTestIsolation(layout);
|
|
664
811
|
let releaseDir = join(layout.releasesRoot, releaseId);
|
|
812
|
+
let previousReleaseId = await activeReleaseId(layout);
|
|
665
813
|
await access(join(releaseDir, BUNDLE_ENTRYPOINT), fsConstants.F_OK);
|
|
666
814
|
await mkdir3(layout.releasesRoot, { recursive: true });
|
|
667
815
|
await mkdir3(layout.binDir, { recursive: true });
|
|
@@ -673,29 +821,39 @@ async function activateRelease(layout, releaseId) {
|
|
|
673
821
|
try {
|
|
674
822
|
await access(legacyDir, fsConstants.F_OK);
|
|
675
823
|
const preservedId = `${legacyId}-${Date.now()}`;
|
|
676
|
-
await
|
|
824
|
+
await rename2(layout.libDir, join(layout.releasesRoot, preservedId));
|
|
677
825
|
await normalizeLegacyBundleShape(join(layout.releasesRoot, preservedId));
|
|
826
|
+
previousReleaseId = preservedId;
|
|
678
827
|
} catch {
|
|
679
|
-
await
|
|
828
|
+
await rename2(layout.libDir, legacyDir);
|
|
680
829
|
await normalizeLegacyBundleShape(legacyDir);
|
|
830
|
+
previousReleaseId = legacyId;
|
|
681
831
|
}
|
|
682
832
|
}
|
|
683
833
|
const tempLink = `${layout.libDir}.new-${process.pid}`;
|
|
684
|
-
await
|
|
834
|
+
await rm3(tempLink, { force: true });
|
|
685
835
|
await symlink(join("beeline-releases", releaseId), tempLink);
|
|
686
|
-
await
|
|
836
|
+
await rename2(tempLink, layout.libDir);
|
|
687
837
|
await fsyncDir(dirname4(layout.libDir));
|
|
688
838
|
await installRecoveryLauncher(layout, releaseDir);
|
|
689
839
|
await writeBinForwarders(layout, releaseDir);
|
|
840
|
+
const state = await readUpdateState(layout);
|
|
841
|
+
await writeUpdateState(layout, {
|
|
842
|
+
...state,
|
|
843
|
+
stagedReleaseId: void 0,
|
|
844
|
+
previousReleaseId: previousReleaseId && previousReleaseId !== releaseId ? previousReleaseId : state.previousReleaseId
|
|
845
|
+
});
|
|
846
|
+
if (!options.deferPruning)
|
|
847
|
+
await pruneHelperReleases(layout);
|
|
690
848
|
}
|
|
691
849
|
async function installRecoveryLauncher(layout, releaseDir) {
|
|
692
850
|
const bootstrap = join(releaseDir, "lib/beeline/recovery-cli.mjs");
|
|
693
851
|
if (await access(bootstrap).then(() => true, () => false)) {
|
|
694
|
-
const stable =
|
|
852
|
+
const stable = resolve6(layout.libDir, "../beeline-bootstrap.mjs");
|
|
695
853
|
const temporary = `${stable}.${process.pid}.tmp`;
|
|
696
854
|
await copyFile(bootstrap, temporary);
|
|
697
|
-
await
|
|
698
|
-
await replaceFile(
|
|
855
|
+
await rename2(temporary, stable);
|
|
856
|
+
await replaceFile(resolve6(layout.libDir, "../beeline-node-path"), `${process.execPath}
|
|
699
857
|
`, 384);
|
|
700
858
|
}
|
|
701
859
|
}
|
|
@@ -719,7 +877,7 @@ async function normalizeLegacyBundleShape(bundleDir) {
|
|
|
719
877
|
continue;
|
|
720
878
|
} catch {
|
|
721
879
|
}
|
|
722
|
-
await
|
|
880
|
+
await rename2(join(bundleDir, name), join(innerLib, name)).catch(() => void 0);
|
|
723
881
|
}
|
|
724
882
|
}
|
|
725
883
|
async function writeBinForwarders(layout, activeBundleRoot) {
|
|
@@ -734,7 +892,7 @@ async function writeBinForwarders(layout, activeBundleRoot) {
|
|
|
734
892
|
} catch {
|
|
735
893
|
continue;
|
|
736
894
|
}
|
|
737
|
-
const independent = await access(
|
|
895
|
+
const independent = await access(resolve6(layout.libDir, "../beeline-bootstrap.mjs")).then(() => true, () => false);
|
|
738
896
|
await replaceFile(join(layout.binDir, name), independent || tool !== "beeline" ? forwarderScript(tool) : forwarderScript("legacy-beeline").replace("/bin/legacy-beeline", "/bin/beeline"), 493);
|
|
739
897
|
}
|
|
740
898
|
}
|
|
@@ -750,7 +908,7 @@ async function repairInstallForwarders(layout, opts = {}) {
|
|
|
750
908
|
} catch {
|
|
751
909
|
current = void 0;
|
|
752
910
|
}
|
|
753
|
-
const independent = await access(
|
|
911
|
+
const independent = await access(resolve6(layout.libDir, "../beeline-bootstrap.mjs")).then(() => true, () => false);
|
|
754
912
|
const expected = independent || tool !== "beeline" ? forwarderScript(tool) : forwarderScript("legacy-beeline").replace("/bin/legacy-beeline", "/bin/beeline");
|
|
755
913
|
return current === expected;
|
|
756
914
|
};
|
|
@@ -789,6 +947,7 @@ async function prepareLatestRelease(layout, options = {}) {
|
|
|
789
947
|
deadlineAt: options.deadlineAt
|
|
790
948
|
});
|
|
791
949
|
await writeUpdateState(layout, {
|
|
950
|
+
...await readUpdateState(layout),
|
|
792
951
|
lastCheckAt: Date.now(),
|
|
793
952
|
lastCheckResult: "verified",
|
|
794
953
|
stagedReleaseId: releaseId
|
|
@@ -804,10 +963,53 @@ async function prepareLatestRelease(layout, options = {}) {
|
|
|
804
963
|
throw error;
|
|
805
964
|
}
|
|
806
965
|
}
|
|
966
|
+
async function pruneHelperReleases(layout, loadedRelease) {
|
|
967
|
+
assertInstallTestIsolation(layout);
|
|
968
|
+
if (!await ordinaryDirectory(layout.releasesRoot))
|
|
969
|
+
return [];
|
|
970
|
+
const active = await activeReleaseId(layout);
|
|
971
|
+
if (!active)
|
|
972
|
+
return [];
|
|
973
|
+
const state = await readUpdateState(layout);
|
|
974
|
+
const candidates = [];
|
|
975
|
+
for (const entry of await readdir3(layout.releasesRoot, { withFileTypes: true })) {
|
|
976
|
+
if (entry.name.startsWith("."))
|
|
977
|
+
continue;
|
|
978
|
+
if (entry.isSymbolicLink()) {
|
|
979
|
+
if (![active, state.previousReleaseId, state.stagedReleaseId, loadedRelease].includes(entry.name))
|
|
980
|
+
await rm3(join(layout.releasesRoot, entry.name), { force: true });
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
if (!entry.isDirectory())
|
|
984
|
+
continue;
|
|
985
|
+
const path = join(layout.releasesRoot, entry.name);
|
|
986
|
+
if (!await ordinaryDirectory(path))
|
|
987
|
+
continue;
|
|
988
|
+
candidates.push({
|
|
989
|
+
id: entry.name,
|
|
990
|
+
modified: (await lstat3(path)).mtimeMs,
|
|
991
|
+
bootable: await access(join(path, BUNDLE_ENTRYPOINT)).then(() => true, () => false)
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
const recordedPrevious = candidates.find((entry) => entry.id === state.previousReleaseId && entry.id !== active && entry.bootable)?.id;
|
|
995
|
+
const previous = recordedPrevious ?? candidates.filter((entry) => entry.bootable && entry.id !== active && entry.id !== state.stagedReleaseId).sort((a, b) => b.modified - a.modified)[0]?.id;
|
|
996
|
+
const keep = /* @__PURE__ */ new Set([active, previous, loadedRelease, state.stagedReleaseId]);
|
|
997
|
+
const removed = [];
|
|
998
|
+
for (const entry of candidates) {
|
|
999
|
+
if (keep.has(entry.id))
|
|
1000
|
+
continue;
|
|
1001
|
+
await removeStoragePath(join(layout.releasesRoot, entry.id));
|
|
1002
|
+
removed.push(entry.id);
|
|
1003
|
+
}
|
|
1004
|
+
if (previous !== state.previousReleaseId)
|
|
1005
|
+
await writeUpdateState(layout, { ...state, previousReleaseId: previous });
|
|
1006
|
+
return removed;
|
|
1007
|
+
}
|
|
807
1008
|
var RELEASES_SEGMENT, STAGED_NODE_PATH_FILE, BUNDLE_ENTRYPOINT, PI_MCP_ADAPTER_ENTRYPOINT, CODEGRAPH_ENTRYPOINT, FORWARDER_TOOLS, FORWARDER_ALIASES, LEGACY_FLAT_BUNDLE_FILES, SelfUpdateManager;
|
|
808
1009
|
var init_self_update = __esm({
|
|
809
1010
|
"apps/body/dist/self-update.js"() {
|
|
810
1011
|
"use strict";
|
|
1012
|
+
init_storage_filesystem();
|
|
811
1013
|
init_lifecycle_test_guard();
|
|
812
1014
|
init_self_update_manifest();
|
|
813
1015
|
RELEASES_SEGMENT = "beeline-releases";
|
|
@@ -859,10 +1061,10 @@ __export(managed_update_exports, {
|
|
|
859
1061
|
versionAtLeast: () => versionAtLeast,
|
|
860
1062
|
withInstallLock: () => withInstallLock
|
|
861
1063
|
});
|
|
862
|
-
import { spawn as
|
|
863
|
-
import { resolve as
|
|
1064
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
1065
|
+
import { resolve as resolve7 } from "node:path";
|
|
864
1066
|
function withInstallLock(layout, work, options = {}) {
|
|
865
|
-
return withKernelLock(`beeline-install:${
|
|
1067
|
+
return withKernelLock(`beeline-install:${resolve7(layout.libDir)}`, work, options.waitMs);
|
|
866
1068
|
}
|
|
867
1069
|
function versionAtLeast(actual, minimum) {
|
|
868
1070
|
const parse = (value) => /^v?(\d+)\.(\d+)\.(\d+)$/.exec(value)?.slice(1).map(Number);
|
|
@@ -887,9 +1089,9 @@ async function runManagedUpdateWorker() {
|
|
|
887
1089
|
}
|
|
888
1090
|
async function prepareReleaseInWorker(env, deadlineAt, entrypoint) {
|
|
889
1091
|
const layout = beelineInstallLayout(env);
|
|
890
|
-
const bootstrap = entrypoint ??
|
|
1092
|
+
const bootstrap = entrypoint ?? resolve7(layout.libDir, "../beeline-bootstrap.mjs");
|
|
891
1093
|
return new Promise((done, reject) => {
|
|
892
|
-
const child =
|
|
1094
|
+
const child = spawn3(process.execPath, [bootstrap, "managed-update-worker"], {
|
|
893
1095
|
detached: true,
|
|
894
1096
|
env: {
|
|
895
1097
|
...env,
|
|
@@ -993,7 +1195,7 @@ var init_managed_update = __esm({
|
|
|
993
1195
|
if (this.#minimum) {
|
|
994
1196
|
const identity = await readInstalledBundleIdentity({
|
|
995
1197
|
...this.options.layout,
|
|
996
|
-
libDir:
|
|
1198
|
+
libDir: resolve7(this.options.layout.releasesRoot, releaseId)
|
|
997
1199
|
});
|
|
998
1200
|
if (!identity?.version || !versionAtLeast(identity.version, this.#minimum)) {
|
|
999
1201
|
this.#prepared = void 0;
|
|
@@ -1075,13 +1277,13 @@ var init_managed_update = __esm({
|
|
|
1075
1277
|
});
|
|
1076
1278
|
|
|
1077
1279
|
// apps/body/dist/recovery-cli.js
|
|
1078
|
-
import { spawn as
|
|
1079
|
-
import { readFile as readFile4, readdir as
|
|
1080
|
-
import { resolve as
|
|
1280
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
1281
|
+
import { readFile as readFile4, readdir as readdir5, stat as stat2, unlink } from "node:fs/promises";
|
|
1282
|
+
import { resolve as resolve9 } from "node:path";
|
|
1081
1283
|
|
|
1082
1284
|
// apps/body/dist/recovery.js
|
|
1083
|
-
import { readFile as readFile3, readdir as
|
|
1084
|
-
import { resolve as
|
|
1285
|
+
import { readFile as readFile3, readdir as readdir4, stat } from "node:fs/promises";
|
|
1286
|
+
import { resolve as resolve8 } from "node:path";
|
|
1085
1287
|
|
|
1086
1288
|
// apps/body/dist/machine-control.js
|
|
1087
1289
|
import { watch } from "node:fs";
|
|
@@ -1193,7 +1395,7 @@ async function requestMachine(stateRoot, request, accept, timeoutMs = 6e5) {
|
|
|
1193
1395
|
init_kernel_lock();
|
|
1194
1396
|
init_lifecycle_test_guard();
|
|
1195
1397
|
import { execFile } from "node:child_process";
|
|
1196
|
-
import { mkdir as mkdir2, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
1398
|
+
import { mkdir as mkdir2, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
1197
1399
|
import { homedir } from "node:os";
|
|
1198
1400
|
import { resolve as resolve3 } from "node:path";
|
|
1199
1401
|
import { promisify } from "node:util";
|
|
@@ -1215,9 +1417,7 @@ Environment=PATH=%h/.local/bin:/usr/local/bin:/usr/bin:/bin
|
|
|
1215
1417
|
ExecStart=%h/.local/bin/beeline helper-bootstrap
|
|
1216
1418
|
ExecReload=/bin/kill -HUP $MAINPID
|
|
1217
1419
|
Restart=always
|
|
1218
|
-
RestartSec=
|
|
1219
|
-
RestartSteps=5
|
|
1220
|
-
RestartMaxDelaySec=60s
|
|
1420
|
+
RestartSec=1s
|
|
1221
1421
|
WatchdogSec=180s
|
|
1222
1422
|
CPUWeight=1000
|
|
1223
1423
|
IOWeight=1000
|
|
@@ -1449,7 +1649,7 @@ async function prepareMachineRelease(layout, options = {}) {
|
|
|
1449
1649
|
return withInstallLock(layout, () => prepareLatestRelease(layout, { ...options, deadlineAt }), {
|
|
1450
1650
|
waitMs: Math.max(0, deadlineAt - Date.now())
|
|
1451
1651
|
});
|
|
1452
|
-
const stable =
|
|
1652
|
+
const stable = resolve8(layout.libDir, "../beeline-bootstrap.mjs");
|
|
1453
1653
|
const entrypoint = env.BEELINE_RECOVERY_ENTRY || await readFile3(stable).then(() => stable, () => process.argv[1]);
|
|
1454
1654
|
return prepareReleaseInWorker({ ...env, ...options?.force ? { BEELINE_FORCE_REPAIR: "1" } : {} }, deadlineAt, entrypoint);
|
|
1455
1655
|
}
|
|
@@ -1458,9 +1658,9 @@ async function convergeMachine(options = {}) {
|
|
|
1458
1658
|
assertLifecycleTestIsolation(env, Boolean(options.run));
|
|
1459
1659
|
const layout = options.layout ?? beelineInstallLayout(env) ?? discoveredBeelineInstallLayout(env);
|
|
1460
1660
|
const deadlineAt = options.deadlineAt ?? Date.now() + (options.timeoutMs ?? 6e5);
|
|
1461
|
-
const stateRoot = env.XDG_STATE_HOME ||
|
|
1462
|
-
const root =
|
|
1463
|
-
const configured = await Promise.all((await
|
|
1661
|
+
const stateRoot = env.XDG_STATE_HOME || resolve8(env.HOME || "", ".local/state");
|
|
1662
|
+
const root = resolve8(stateRoot, "beeline/agents");
|
|
1663
|
+
const configured = await Promise.all((await readdir4(root).catch(() => [])).filter((id) => /^[0-9a-f]{64}$/i.test(id)).map(async (id) => await stat(resolve8(root, id, "runtime.json")).then(() => true, () => false) && !await stat(resolve8(root, id, "stopped")).then(() => true, () => false) ? id : void 0));
|
|
1464
1664
|
const required = /* @__PURE__ */ new Set([
|
|
1465
1665
|
...configured.filter((id) => Boolean(id)),
|
|
1466
1666
|
...options.agentIds ?? []
|
|
@@ -1481,7 +1681,7 @@ async function convergeMachine(options = {}) {
|
|
|
1481
1681
|
let expectedRelease = options.releaseId;
|
|
1482
1682
|
const targetIdentity = options.releaseId ? await readInstalledBundleIdentity({
|
|
1483
1683
|
...layout,
|
|
1484
|
-
libDir:
|
|
1684
|
+
libDir: resolve8(layout.releasesRoot, options.releaseId)
|
|
1485
1685
|
}) : void 0;
|
|
1486
1686
|
const request = options.releaseId && hasControl ? {
|
|
1487
1687
|
operation: "update",
|
|
@@ -1512,33 +1712,32 @@ async function bootstrapMachine(env = process.env) {
|
|
|
1512
1712
|
if (!pid || ![Number(env.BEELINE_BOOT_LAUNCHER_PID), Number(env.BEELINE_BOOT_LAUNCHER_PARENT_PID)].includes(pid))
|
|
1513
1713
|
throw new Error("helper bootstrap requires native service ownership; run beeline start");
|
|
1514
1714
|
const layout = beelineInstallLayout(env) ?? discoveredBeelineInstallLayout(env);
|
|
1715
|
+
const state = await readUpdateState(layout);
|
|
1716
|
+
const active = await activeReleaseId(layout);
|
|
1515
1717
|
let releaseId;
|
|
1516
|
-
|
|
1718
|
+
for (const id of [state.stagedReleaseId, active]) {
|
|
1719
|
+
if (!id || !/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(id))
|
|
1720
|
+
continue;
|
|
1721
|
+
const digest = await readFile3(resolve8(layout.releasesRoot, id, ".stage-ok"), "utf8").catch(() => "");
|
|
1722
|
+
if (/^[0-9a-f]{64}$/i.test(digest.trim()) && await resolveBundleEntrypoint(resolve8(layout.releasesRoot, id))) {
|
|
1723
|
+
releaseId = id;
|
|
1724
|
+
break;
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
if (!releaseId) {
|
|
1517
1728
|
if (env.BEELINE_UPDATE_DISABLE === "1")
|
|
1518
|
-
throw new Error("automatic release selection disabled
|
|
1519
|
-
|
|
1729
|
+
throw new Error("automatic release selection disabled; no verified runnable release");
|
|
1730
|
+
releaseId = (await prepareMachineRelease(layout, {
|
|
1520
1731
|
env,
|
|
1521
1732
|
deadlineAt: Date.now() + 18e4,
|
|
1522
1733
|
logger: (line) => console.error(line)
|
|
1523
|
-
});
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
});
|
|
1528
|
-
} catch (error) {
|
|
1529
|
-
const state = await readUpdateState(layout);
|
|
1530
|
-
const candidates = [state.stagedReleaseId, await activeReleaseId(layout)];
|
|
1531
|
-
const verified = await Promise.all(candidates.map(async (id) => id && /^[0-9a-f]{64}$/i.test((await readFile3(resolve6(layout.releasesRoot, id, ".stage-ok"), "utf8").catch(() => "")).trim()) ? id : void 0));
|
|
1532
|
-
const candidate = verified.find(Boolean);
|
|
1533
|
-
if (!candidate)
|
|
1534
|
-
throw new Error(`startup release selection failed; no verified runnable release: ${String(error)}`);
|
|
1535
|
-
releaseId = candidate;
|
|
1536
|
-
console.error(`[beeline] serving verified ${releaseId}; release freshness unconfirmed: ${String(error)}`);
|
|
1537
|
-
await withInstallLock(layout, () => activateRelease(layout, releaseId), {
|
|
1734
|
+
})).releaseId;
|
|
1735
|
+
}
|
|
1736
|
+
if (releaseId !== active)
|
|
1737
|
+
await withInstallLock(layout, () => activateRelease(layout, releaseId, { deferPruning: true }), {
|
|
1538
1738
|
waitMs: Math.max(0, bootDeadlineAt - Date.now())
|
|
1539
1739
|
});
|
|
1540
|
-
|
|
1541
|
-
const entry = await resolveBundleEntrypoint(resolve6(layout.releasesRoot, releaseId));
|
|
1740
|
+
const entry = await resolveBundleEntrypoint(resolve8(layout.releasesRoot, releaseId));
|
|
1542
1741
|
if (!entry)
|
|
1543
1742
|
throw new Error(`selected release ${releaseId} has no body entry`);
|
|
1544
1743
|
return entry;
|
|
@@ -1599,18 +1798,18 @@ async function main() {
|
|
|
1599
1798
|
if (args[i] === "--manifest-url" && !args[++i])
|
|
1600
1799
|
throw new Error("--manifest-url requires a URL");
|
|
1601
1800
|
}
|
|
1602
|
-
const stateRoot2 = process.env.XDG_STATE_HOME ||
|
|
1801
|
+
const stateRoot2 = process.env.XDG_STATE_HOME || resolve9(process.env.HOME || "", ".local/state");
|
|
1603
1802
|
if (args.includes("--status")) {
|
|
1604
1803
|
const [native, helper] = await Promise.all([
|
|
1605
1804
|
nativeHelperStatus(),
|
|
1606
1805
|
requestMachine(stateRoot2, { operation: "status" }, () => true, 2e3).catch(() => void 0)
|
|
1607
1806
|
]);
|
|
1608
|
-
const root =
|
|
1609
|
-
const agents = await Promise.all((await
|
|
1807
|
+
const root = resolve9(stateRoot2, "beeline/agents");
|
|
1808
|
+
const agents = await Promise.all((await readdir5(root).catch(() => [])).filter((id) => /^[0-9a-f]{64}$/.test(id)).map(async (id) => ({
|
|
1610
1809
|
agentId: id,
|
|
1611
|
-
enabled: !await stat2(
|
|
1810
|
+
enabled: !await stat2(resolve9(root, id, "stopped")).then(() => true, () => false),
|
|
1612
1811
|
...helper?.agents[id] ?? { state: "not hosted" },
|
|
1613
|
-
validRecord: await readFile4(
|
|
1812
|
+
validRecord: await readFile4(resolve9(root, id, "runtime.json"), "utf8").then((raw) => Boolean(JSON.parse(raw).agent)).catch(() => false)
|
|
1614
1813
|
})));
|
|
1615
1814
|
console.log(JSON.stringify({
|
|
1616
1815
|
native,
|
|
@@ -1660,7 +1859,7 @@ async function main() {
|
|
|
1660
1859
|
console.log(`[beeline] executing release ${status.loadedRelease}; helper ${status.pid}; every enabled agent serving`);
|
|
1661
1860
|
return;
|
|
1662
1861
|
}
|
|
1663
|
-
const stateRoot = process.env.XDG_STATE_HOME ||
|
|
1862
|
+
const stateRoot = process.env.XDG_STATE_HOME || resolve9(process.env.HOME || "", ".local/state");
|
|
1664
1863
|
let selectedRelease;
|
|
1665
1864
|
if (command === "start") {
|
|
1666
1865
|
for (let i = 1; i < args.length; i++) {
|
|
@@ -1675,13 +1874,13 @@ async function main() {
|
|
|
1675
1874
|
const requested = agentFlag >= 0 ? args[agentFlag + 1] : args[1] && !args[1].startsWith("--") ? args[1] : void 0;
|
|
1676
1875
|
if (agentFlag >= 0 && !requested)
|
|
1677
1876
|
throw new Error("--agent requires an agent public key");
|
|
1678
|
-
const agentRoot =
|
|
1679
|
-
const ids = (await
|
|
1877
|
+
const agentRoot = resolve9(stateRoot, "beeline/agents");
|
|
1878
|
+
const ids = (await readdir5(agentRoot).catch(() => [])).filter((id) => /^[0-9a-f]{64}$/.test(id));
|
|
1680
1879
|
const selected = requested ? ids.filter((id) => id.startsWith(requested)) : args.includes("--all") ? ids : [];
|
|
1681
1880
|
if (requested && selected.length !== 1)
|
|
1682
1881
|
throw new Error(`no unique paired agent matches ${requested}`);
|
|
1683
1882
|
for (const id of selected)
|
|
1684
|
-
await unlink(
|
|
1883
|
+
await unlink(resolve9(agentRoot, id, "stopped")).catch((error) => {
|
|
1685
1884
|
if (error.code !== "ENOENT")
|
|
1686
1885
|
throw error;
|
|
1687
1886
|
});
|
|
@@ -1705,7 +1904,7 @@ async function main() {
|
|
|
1705
1904
|
const entry = await resolveBundleEntrypoint(layout.libDir);
|
|
1706
1905
|
if (!entry)
|
|
1707
1906
|
throw new Error("no installed Beeline body; run npx usebeeline connect");
|
|
1708
|
-
const child =
|
|
1907
|
+
const child = spawn4(process.execPath, [entry, ...args], {
|
|
1709
1908
|
env: {
|
|
1710
1909
|
...process.env,
|
|
1711
1910
|
BEELINE_LIB_DIR: layout.libDir,
|
|
@@ -1723,7 +1922,7 @@ async function convergeDependencies(layout, deadlineAt) {
|
|
|
1723
1922
|
if (!entry)
|
|
1724
1923
|
throw new Error("dependency phase has no selected body");
|
|
1725
1924
|
await new Promise((done, reject) => {
|
|
1726
|
-
const child =
|
|
1925
|
+
const child = spawn4(process.execPath, [entry, "converge-dependencies"], {
|
|
1727
1926
|
env: { ...process.env, BEELINE_LIB_DIR: layout.libDir, BEELINE_INTERNAL_RECOVERY: "1" },
|
|
1728
1927
|
stdio: "inherit"
|
|
1729
1928
|
});
|