@longleash/cli 0.1.0-rc.4 → 0.1.0-rc.6
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/bin/longleash.mjs +119 -11
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/runtime/app/dist/assets/{app-DvraQaIc.js → app-NtxDJpY6.js} +2 -2
- package/runtime/app/dist/assets/{welcome-CTTvis4A.js → welcome-DFnhFAOE.js} +5 -5
- package/runtime/app/dist/build.json +1 -1
- package/runtime/app/dist/index.html +1 -1
- package/runtime/app/dist/welcome.html +1 -1
package/bin/longleash.mjs
CHANGED
|
@@ -1401,27 +1401,42 @@ function prepareManagedInstall(version, env = process.env) {
|
|
|
1401
1401
|
const hadWrapper = pathExists2(paths.wrapper);
|
|
1402
1402
|
const hadMarker = pathExists2(marker);
|
|
1403
1403
|
let previousWrapper;
|
|
1404
|
+
let previousWrapperMode = 448;
|
|
1404
1405
|
let previousMarker;
|
|
1405
1406
|
let wrapperWritten = false;
|
|
1406
1407
|
let markerWritten = false;
|
|
1408
|
+
let migratedLegacyWrapper = false;
|
|
1409
|
+
let legacyWrapper;
|
|
1407
1410
|
const tempLink = join2(paths.home, `.current-${process.pid}-${randomUUID2()}`);
|
|
1408
1411
|
try {
|
|
1409
1412
|
mkdirSync2(paths.bin, { recursive: true, mode: 448 });
|
|
1410
1413
|
if (hadWrapper) {
|
|
1411
|
-
|
|
1414
|
+
const wrapperStat = lstatSync2(paths.wrapper);
|
|
1415
|
+
if (wrapperStat.isSymbolicLink()) throw new Error(`Refusing symlinked executable: ${paths.wrapper}`);
|
|
1416
|
+
if (!wrapperStat.isFile()) throw new Error(`Refusing non-file executable: ${paths.wrapper}`);
|
|
1417
|
+
if (process.getuid !== void 0 && wrapperStat.uid !== process.getuid()) {
|
|
1418
|
+
throw new Error(`Refusing executable owned by another user: ${paths.wrapper}`);
|
|
1419
|
+
}
|
|
1412
1420
|
previousWrapper = readFileSync2(paths.wrapper, "utf8");
|
|
1421
|
+
previousWrapperMode = wrapperStat.mode & 511;
|
|
1413
1422
|
if (!previousWrapper.includes(WRAPPER_MARKER)) {
|
|
1414
|
-
|
|
1423
|
+
legacyWrapper = recognizeLegacyWrapper(previousWrapper, wrapperStat.mode & 511, paths.wrapper);
|
|
1424
|
+
migratedLegacyWrapper = true;
|
|
1415
1425
|
}
|
|
1416
1426
|
}
|
|
1417
1427
|
if (hadMarker) {
|
|
1418
1428
|
if (lstatSync2(marker).isSymbolicLink()) throw new Error(`Refusing symlinked install marker: ${marker}`);
|
|
1419
1429
|
previousMarker = readFileSync2(marker, "utf8");
|
|
1430
|
+
const parsed = parseInstallMarker(previousMarker, marker);
|
|
1431
|
+
if (parsed.package !== PACKAGE_NAME) throw new Error(`Refusing unrecognized install marker: ${marker}`);
|
|
1432
|
+
legacyWrapper = parsed.legacyWrapper ?? legacyWrapper;
|
|
1420
1433
|
}
|
|
1421
1434
|
const activeCli = join2(paths.current, "node_modules", "@longleash", "cli", "bin", "longleash.mjs");
|
|
1422
1435
|
writeExecutableAtomically(paths.wrapper, wrapper(activeCli, process.execPath));
|
|
1423
1436
|
wrapperWritten = true;
|
|
1424
|
-
|
|
1437
|
+
const nextMarker = { schema: 1, package: PACKAGE_NAME };
|
|
1438
|
+
if (legacyWrapper) nextMarker.legacyWrapper = legacyWrapper;
|
|
1439
|
+
writeFileAtomically(marker, `${JSON.stringify(nextMarker, null, 2)}
|
|
1425
1440
|
`, 384);
|
|
1426
1441
|
markerWritten = true;
|
|
1427
1442
|
symlinkSync(release, tempLink, "dir");
|
|
@@ -1430,6 +1445,7 @@ function prepareManagedInstall(version, env = process.env) {
|
|
|
1430
1445
|
}
|
|
1431
1446
|
renameSync2(tempLink, paths.current);
|
|
1432
1447
|
activated = true;
|
|
1448
|
+
return { migratedLegacyWrapper };
|
|
1433
1449
|
} catch (error) {
|
|
1434
1450
|
try {
|
|
1435
1451
|
rmSync2(tempLink, { force: true });
|
|
@@ -1438,7 +1454,7 @@ function prepareManagedInstall(version, env = process.env) {
|
|
|
1438
1454
|
const restorationFailures = [];
|
|
1439
1455
|
if (wrapperWritten) {
|
|
1440
1456
|
try {
|
|
1441
|
-
restoreManagedFile(paths.wrapper, hadWrapper ? previousWrapper : void 0,
|
|
1457
|
+
restoreManagedFile(paths.wrapper, hadWrapper ? previousWrapper : void 0, previousWrapperMode);
|
|
1442
1458
|
} catch (restoreError) {
|
|
1443
1459
|
restorationFailures.push(`executable: ${String(restoreError)}`);
|
|
1444
1460
|
}
|
|
@@ -1478,18 +1494,78 @@ function prepareManagedInstall(version, env = process.env) {
|
|
|
1478
1494
|
function uninstallManagedRuntime(env = process.env) {
|
|
1479
1495
|
const paths = installPaths(env);
|
|
1480
1496
|
const marker = join2(paths.home, MARKER);
|
|
1481
|
-
if (!pathExists2(marker))
|
|
1497
|
+
if (!pathExists2(marker)) {
|
|
1498
|
+
return {
|
|
1499
|
+
removed: false,
|
|
1500
|
+
configPreserved: resolve2(env.LONGLEASH_DATA ?? join2(homedir2(), ".longleash")),
|
|
1501
|
+
legacyWrapperRestored: false
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1482
1504
|
if (lstatSync2(marker).isSymbolicLink()) throw new Error(`Refusing symlinked install marker: ${marker}`);
|
|
1483
|
-
const parsed =
|
|
1505
|
+
const parsed = parseInstallMarker(readFileSync2(marker, "utf8"), marker);
|
|
1484
1506
|
if (parsed.package !== PACKAGE_NAME) throw new Error(`Refusing to remove unrecognized install directory: ${paths.home}`);
|
|
1507
|
+
let legacyWrapperRestored = false;
|
|
1485
1508
|
if (pathExists2(paths.wrapper)) {
|
|
1486
1509
|
if (lstatSync2(paths.wrapper).isSymbolicLink()) throw new Error(`Refusing symlinked executable: ${paths.wrapper}`);
|
|
1487
1510
|
const wrapperText = readFileSync2(paths.wrapper, "utf8");
|
|
1488
1511
|
if (!wrapperText.includes(WRAPPER_MARKER)) throw new Error(`Refusing to remove an unmanaged executable: ${paths.wrapper}`);
|
|
1489
|
-
|
|
1512
|
+
if (parsed.legacyWrapper) {
|
|
1513
|
+
const backup = validateLegacyBackup(parsed.legacyWrapper, paths.wrapper);
|
|
1514
|
+
writeFileAtomically(paths.wrapper, backup.content, backup.mode);
|
|
1515
|
+
legacyWrapperRestored = true;
|
|
1516
|
+
} else {
|
|
1517
|
+
rmSync2(paths.wrapper);
|
|
1518
|
+
}
|
|
1490
1519
|
}
|
|
1491
1520
|
rmSync2(paths.home, { recursive: true });
|
|
1492
|
-
return {
|
|
1521
|
+
return {
|
|
1522
|
+
removed: true,
|
|
1523
|
+
configPreserved: resolve2(env.LONGLEASH_DATA ?? join2(homedir2(), ".longleash")),
|
|
1524
|
+
legacyWrapperRestored
|
|
1525
|
+
};
|
|
1526
|
+
}
|
|
1527
|
+
function recognizeLegacyWrapper(content, mode, wrapperPath) {
|
|
1528
|
+
if (Buffer.byteLength(content, "utf8") > 16 * 1024) {
|
|
1529
|
+
throw new Error(`Refusing to replace an unmanaged executable: ${wrapperPath}`);
|
|
1530
|
+
}
|
|
1531
|
+
const match = content.match(
|
|
1532
|
+
/^#!\/usr\/bin\/env bash\n# Created by the LongLeash installer\. The three values below are the settings; the behaviour\n# lives in \$LONGLEASH_DIR\/scripts\/longleash\.sh and updates with the code\.\nset -euo pipefail\nexport LONGLEASH_DIR="([^"\n]+)"\nexport LONGLEASH_DEFAULT_ROOTS="([^"\n]*)"\nexport LONGLEASH_DEFAULT_RELAY="([^"\n]*)"\nexec bash "\$LONGLEASH_DIR\/scripts\/longleash\.sh" "\$@"\n?$/
|
|
1533
|
+
);
|
|
1534
|
+
if (!match) throw new Error(`Refusing to replace an unmanaged executable: ${wrapperPath}`);
|
|
1535
|
+
const legacyRoot = match[1];
|
|
1536
|
+
if (!legacyRoot || resolve2(legacyRoot) !== legacyRoot) {
|
|
1537
|
+
throw new Error(`Refusing legacy executable with an unsafe checkout path: ${wrapperPath}`);
|
|
1538
|
+
}
|
|
1539
|
+
const legacyEntrypoint = join2(legacyRoot, "scripts", "longleash.sh");
|
|
1540
|
+
if (!pathExists2(legacyEntrypoint) || lstatSync2(legacyEntrypoint).isSymbolicLink() || !lstatSync2(legacyEntrypoint).isFile()) {
|
|
1541
|
+
throw new Error(`The verified legacy executable points to a missing checkout: ${legacyRoot}`);
|
|
1542
|
+
}
|
|
1543
|
+
return { content, mode: normalizeExecutableMode(mode) };
|
|
1544
|
+
}
|
|
1545
|
+
function validateLegacyBackup(value, wrapperPath) {
|
|
1546
|
+
if (!value || typeof value.content !== "string" || !Number.isSafeInteger(value.mode)) {
|
|
1547
|
+
throw new Error(`Refusing invalid legacy executable backup in the install marker: ${wrapperPath}`);
|
|
1548
|
+
}
|
|
1549
|
+
return recognizeLegacyWrapper(value.content, value.mode, wrapperPath);
|
|
1550
|
+
}
|
|
1551
|
+
function parseInstallMarker(content, markerPath) {
|
|
1552
|
+
let parsed;
|
|
1553
|
+
try {
|
|
1554
|
+
parsed = JSON.parse(content);
|
|
1555
|
+
} catch {
|
|
1556
|
+
throw new Error(`Refusing invalid install marker: ${markerPath}`);
|
|
1557
|
+
}
|
|
1558
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1559
|
+
throw new Error(`Refusing invalid install marker: ${markerPath}`);
|
|
1560
|
+
}
|
|
1561
|
+
const marker = parsed;
|
|
1562
|
+
if (marker.legacyWrapper !== void 0) validateLegacyBackup(marker.legacyWrapper, markerPath);
|
|
1563
|
+
return marker;
|
|
1564
|
+
}
|
|
1565
|
+
function normalizeExecutableMode(mode) {
|
|
1566
|
+
const normalized = mode & 511;
|
|
1567
|
+
if ((normalized & 64) === 0) throw new Error("The legacy LongLeash executable is not owner-executable.");
|
|
1568
|
+
return normalized;
|
|
1493
1569
|
}
|
|
1494
1570
|
function wrapper(cli, node) {
|
|
1495
1571
|
return `#!/bin/sh
|
|
@@ -1616,6 +1692,27 @@ function processAlive(pid) {
|
|
|
1616
1692
|
}
|
|
1617
1693
|
}
|
|
1618
1694
|
|
|
1695
|
+
// src/setup-input.ts
|
|
1696
|
+
function resolveAllowedRootAnswer(answer, defaultRoot) {
|
|
1697
|
+
const trimmed = answer.trim();
|
|
1698
|
+
if (/^(?:y|yes|n|no)$/i.test(trimmed)) {
|
|
1699
|
+
return {
|
|
1700
|
+
ok: false,
|
|
1701
|
+
message: `This question needs a folder path. Press Enter to use ${defaultRoot}, or type another existing folder.`
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
try {
|
|
1705
|
+
const [root] = normalizeRoots([trimmed || defaultRoot]);
|
|
1706
|
+
if (!root) throw new Error("At least one allowed project directory is required.");
|
|
1707
|
+
return { ok: true, root };
|
|
1708
|
+
} catch (error) {
|
|
1709
|
+
return {
|
|
1710
|
+
ok: false,
|
|
1711
|
+
message: `That folder cannot be used: ${error instanceof Error ? error.message : String(error)}`
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1619
1716
|
// src/service.ts
|
|
1620
1717
|
import {
|
|
1621
1718
|
chmodSync as chmodSync2,
|
|
@@ -2218,8 +2315,15 @@ async function setup(options) {
|
|
|
2218
2315
|
try {
|
|
2219
2316
|
console.log("\nLongLeash setup\n");
|
|
2220
2317
|
console.log("Agents remain on this laptop. Choose only folders where they may work.");
|
|
2221
|
-
const
|
|
2222
|
-
options.roots
|
|
2318
|
+
const defaultRoot = process.cwd();
|
|
2319
|
+
while (options.roots.length === 0) {
|
|
2320
|
+
const rootAnswer = await prompt.question(`Allowed project folder (press Enter for ${defaultRoot}): `);
|
|
2321
|
+
const resolved = resolveAllowedRootAnswer(rootAnswer, defaultRoot);
|
|
2322
|
+
if (resolved.ok) options.roots = [resolved.root];
|
|
2323
|
+
else console.log(`
|
|
2324
|
+
${resolved.message}
|
|
2325
|
+
`);
|
|
2326
|
+
}
|
|
2223
2327
|
console.log("\nConnectivity: hosted works away from home; LAN-only has no internet relay.");
|
|
2224
2328
|
const relayAnswer = await prompt.question("Relay [hosted / lan / wss://your-relay/ws] (hosted): ");
|
|
2225
2329
|
options.relay = relayAnswer.trim() || "hosted";
|
|
@@ -2264,7 +2368,10 @@ async function setup(options) {
|
|
|
2264
2368
|
throw new Error("The staged package could not be configured. The active version was not changed.");
|
|
2265
2369
|
}
|
|
2266
2370
|
try {
|
|
2267
|
-
prepared.activate();
|
|
2371
|
+
const activation = prepared.activate();
|
|
2372
|
+
if (activation.migratedLegacyWrapper) {
|
|
2373
|
+
console.log("Migrated the verified legacy LongLeash command. Its checkout was preserved and will be restored if you uninstall this runtime.");
|
|
2374
|
+
}
|
|
2268
2375
|
} catch (error) {
|
|
2269
2376
|
prepared.rollback();
|
|
2270
2377
|
restoreConfigSnapshot(configSnapshot);
|
|
@@ -2492,6 +2599,7 @@ async function uninstall() {
|
|
|
2492
2599
|
if (hooksRemoved !== 0) throw new Error("Provider hooks could not be removed safely. The managed runtime was preserved.");
|
|
2493
2600
|
const result = uninstallManagedRuntime();
|
|
2494
2601
|
console.log(result.removed ? "Removed the managed LongLeash runtime and executable." : "No managed LongLeash runtime was installed.");
|
|
2602
|
+
if (result.legacyWrapperRestored) console.log("Restored the verified legacy LongLeash command that existed before the npm migration.");
|
|
2495
2603
|
console.log(`Preserved settings, paired devices, and audit data in ${result.configPreserved}.`);
|
|
2496
2604
|
return 0;
|
|
2497
2605
|
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@longleash/cli",
|
|
3
|
-
"version": "0.1.0-rc.
|
|
3
|
+
"version": "0.1.0-rc.6",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@longleash/cli",
|
|
9
|
-
"version": "0.1.0-rc.
|
|
9
|
+
"version": "0.1.0-rc.6",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"os": [
|
|
12
12
|
"darwin",
|