@longleash/cli 0.1.0-rc.5 → 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 +89 -9
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/runtime/app/dist/assets/{app-CsfqXy-8.js → app-NtxDJpY6.js} +2 -2
- package/runtime/app/dist/assets/{welcome-D-UkLoke.js → welcome-DFnhFAOE.js} +1 -1
- 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
|
|
@@ -2292,7 +2368,10 @@ ${resolved.message}
|
|
|
2292
2368
|
throw new Error("The staged package could not be configured. The active version was not changed.");
|
|
2293
2369
|
}
|
|
2294
2370
|
try {
|
|
2295
|
-
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
|
+
}
|
|
2296
2375
|
} catch (error) {
|
|
2297
2376
|
prepared.rollback();
|
|
2298
2377
|
restoreConfigSnapshot(configSnapshot);
|
|
@@ -2520,6 +2599,7 @@ async function uninstall() {
|
|
|
2520
2599
|
if (hooksRemoved !== 0) throw new Error("Provider hooks could not be removed safely. The managed runtime was preserved.");
|
|
2521
2600
|
const result = uninstallManagedRuntime();
|
|
2522
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.");
|
|
2523
2603
|
console.log(`Preserved settings, paired devices, and audit data in ${result.configPreserved}.`);
|
|
2524
2604
|
return 0;
|
|
2525
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",
|