stackbite 0.5.103 → 0.5.104
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/package.json +1 -1
- package/packaging/npm/lib/bootstrap.js +33 -20
- package/packaging/npm/lib/cache.js +21 -1
- package/packaging/npm/release.json +23 -23
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@ const { createHash } = require('node:crypto');
|
|
|
7
7
|
const { LIMITS, fail, memberPath, inside, hashFile, readSmall, exists } = require('./common');
|
|
8
8
|
const { download } = require('./download');
|
|
9
9
|
const { extractArchive } = require('./archive');
|
|
10
|
-
const { cacheDirectory, existingCacheDirectory } = require('./cache');
|
|
10
|
+
const { cacheDirectory, existingCacheDirectory, resolveCacheDirectory } = require('./cache');
|
|
11
11
|
const { parseSetup, stageReporter } = require('./setup');
|
|
12
12
|
const timing = require('./startup-timing');
|
|
13
13
|
const packageVersion = require('../../../package.json').version;
|
|
@@ -209,12 +209,24 @@ function installationRoot(environment, platform) {
|
|
|
209
209
|
? path.resolve(environment.LOCALAPPDATA || home, 'Stackbite')
|
|
210
210
|
: path.resolve(environment.XDG_DATA_HOME || path.join(environment.HOME || home, '.local', 'share'), 'stackbite');
|
|
211
211
|
}
|
|
212
|
+
// Resolve only user-selected directory roots. Managed descendants, including
|
|
213
|
+
// the installation root and update directory, must still be real directories.
|
|
214
|
+
async function selectedDirectory(directory, environment, platform) {
|
|
215
|
+
const home = environment.USERPROFILE || environment.HOME || os.homedir();
|
|
216
|
+
const data = platform === 'win32' ? environment.LOCALAPPDATA || home
|
|
217
|
+
: environment.XDG_DATA_HOME || path.join(environment.HOME || home, '.local', 'share');
|
|
218
|
+
const roots = [data, home].map(root => path.resolve(root)).sort((a, b) => b.length - a.length);
|
|
219
|
+
const selected = roots.find(root => directory === root || inside(root, directory));
|
|
220
|
+
if (!selected) return existingCacheDirectory(directory);
|
|
221
|
+
const resolved = await resolveCacheDirectory(selected);
|
|
222
|
+
return existingCacheDirectory(path.join(resolved, path.relative(selected, directory)));
|
|
223
|
+
}
|
|
212
224
|
// Before a runtime is available, read the SAME journal without creating any
|
|
213
225
|
// product directory. The verified bundled engine repeats this under OS locks
|
|
214
226
|
// and owns cache/locator publication. This read is not a JavaScript flock.
|
|
215
227
|
async function admitPublication(environment, platform) {
|
|
216
228
|
const root = installationRoot(environment, platform);
|
|
217
|
-
if (!await
|
|
229
|
+
if (!await selectedDirectory(root, environment, platform)) return;
|
|
218
230
|
const journal = path.join(root, '.stackbite-uninstall.json');
|
|
219
231
|
if (!await exists(journal)) return;
|
|
220
232
|
const record = JSON.parse((await readSmall(journal, 8 * 1024 * 1024)).toString('utf8').replace(/^\uFEFF/, ''));
|
|
@@ -243,7 +255,7 @@ async function locatorIncarnation(root) {
|
|
|
243
255
|
// package can continue it. The digest binds the same request bytes until launch.
|
|
244
256
|
async function acceptedRemovalWithoutCapsule(environment, platform) {
|
|
245
257
|
const root = installationRoot(environment, platform);
|
|
246
|
-
if (!await
|
|
258
|
+
if (!await selectedDirectory(root, environment, platform)) return null;
|
|
247
259
|
const journal = path.join(root, '.stackbite-uninstall.json');
|
|
248
260
|
let info;
|
|
249
261
|
try { info = await fs.lstat(journal); } catch (error) { if (error.code === 'ENOENT') return null; throw error; }
|
|
@@ -262,8 +274,9 @@ async function acceptedRemovalWithoutCapsule(environment, platform) {
|
|
|
262
274
|
digest: createHash('sha256').update(bytes).digest('hex') };
|
|
263
275
|
}
|
|
264
276
|
|
|
265
|
-
async function baselinePin(file) {
|
|
266
|
-
await
|
|
277
|
+
async function baselinePin(file, environment, platform) {
|
|
278
|
+
if (environment) await selectedDirectory(path.dirname(file), environment, platform);
|
|
279
|
+
else await existingCacheDirectory(path.dirname(file));
|
|
267
280
|
let info;
|
|
268
281
|
try { info = await fs.lstat(file, { bigint: true }); }
|
|
269
282
|
catch (error) { if (error.code === 'ENOENT') return null; throw error; }
|
|
@@ -296,11 +309,11 @@ async function captureBaseline(environment, platform, cachedHome) {
|
|
|
296
309
|
for (const selected of homes) {
|
|
297
310
|
for (const name of ['portable.json', platform === 'win32' ? 'stackbite.exe' : 'stackbite', 'stackbite.py']) {
|
|
298
311
|
const file = path.join(selected, name);
|
|
299
|
-
if (await baselinePin(file) !== null) paths.push(file);
|
|
312
|
+
if (await baselinePin(file, environment, platform) !== null) paths.push(file);
|
|
300
313
|
}
|
|
301
314
|
}
|
|
302
315
|
const legacy = [];
|
|
303
|
-
for (const file of new Set(paths)) legacy.push({ path: file, identity: await baselinePin(file) });
|
|
316
|
+
for (const file of new Set(paths)) legacy.push({ path: file, identity: await baselinePin(file, environment, platform) });
|
|
304
317
|
return legacy.some(pin => pin.identity !== null) ? { root, incarnation: null, legacy } : null;
|
|
305
318
|
}
|
|
306
319
|
|
|
@@ -310,23 +323,23 @@ async function assertBaseline(environment, platform) {
|
|
|
310
323
|
if (Buffer.byteLength(raw) > 16384) fail('Invalid installation baseline');
|
|
311
324
|
const expected = JSON.parse(raw), root = installationRoot(environment, platform);
|
|
312
325
|
if (expected.preparation !== undefined) {
|
|
313
|
-
await assertPreparationBaseline(expected, root, platform);
|
|
326
|
+
await assertPreparationBaseline(expected, root, platform, environment);
|
|
314
327
|
return;
|
|
315
328
|
}
|
|
316
329
|
const current = await locatorInstallationId(root);
|
|
317
330
|
if (expected.root !== root || expected.incarnation !== current) fail('Installation changed while staging');
|
|
318
|
-
if (current === null) await assertLegacyBaseline(expected, platform);
|
|
331
|
+
if (current === null) await assertLegacyBaseline(expected, platform, environment);
|
|
319
332
|
}
|
|
320
333
|
// Python-origin restrictions retain FILE_ID_INFO's full volume serial;
|
|
321
334
|
// libuv reports its 32-bit component. The engine still checks the full pin.
|
|
322
335
|
function libuvIdentity(platform, actual, identity) {
|
|
323
336
|
return platform === 'win32' && actual && identity ? { ...identity, device: (BigInt(identity.device) & 0xffffffffn).toString() } : identity;
|
|
324
337
|
}
|
|
325
|
-
async function assertPreparationBaseline(expected, root, platform) {
|
|
338
|
+
async function assertPreparationBaseline(expected, root, platform, environment) {
|
|
326
339
|
const pin = expected.preparation, file = path.join(root, '.stackbite-update', 'update.lock');
|
|
327
340
|
if (Object.keys(expected).sort().join(',') !== 'preparation,root' || expected.root !== root
|
|
328
341
|
|| !pin || Object.keys(pin).sort().join(',') !== 'identity,path' || pin.path !== file || !pin.identity) fail('Invalid installation preparation');
|
|
329
|
-
const actual = await baselinePin(file);
|
|
342
|
+
const actual = await baselinePin(file, environment, platform);
|
|
330
343
|
if (JSON.stringify(actual) !== JSON.stringify(libuvIdentity(platform, actual, pin.identity))) fail('Installation changed while staging');
|
|
331
344
|
}
|
|
332
345
|
// The first present locator's incarnation; null for a legacy or absent locator.
|
|
@@ -339,10 +352,10 @@ async function locatorInstallationId(root) {
|
|
|
339
352
|
}
|
|
340
353
|
return null;
|
|
341
354
|
}
|
|
342
|
-
async function assertLegacyBaseline(expected, platform) {
|
|
355
|
+
async function assertLegacyBaseline(expected, platform, environment) {
|
|
343
356
|
if (!Array.isArray(expected.legacy) || !expected.legacy.length || expected.legacy.length > 32) fail('Invalid legacy installation baseline');
|
|
344
357
|
for (const pin of expected.legacy) {
|
|
345
|
-
const actual = await baselinePin(pin.path);
|
|
358
|
+
const actual = await baselinePin(pin.path, environment, platform);
|
|
346
359
|
if (JSON.stringify(actual) !== JSON.stringify(libuvIdentity(platform, actual, pin.identity))) fail('Installation changed while staging');
|
|
347
360
|
}
|
|
348
361
|
}
|
|
@@ -381,12 +394,12 @@ async function settlePreparation(preparation, environment, platform, source) {
|
|
|
381
394
|
if (!preparation.created || !source || !await source.absent()
|
|
382
395
|
|| await captureBaseline(environment, platform, preparation.target)) return;
|
|
383
396
|
for (const [directory, pin] of preparation.directories) {
|
|
384
|
-
await
|
|
397
|
+
await selectedDirectory(directory, environment, platform);
|
|
385
398
|
const current = await fs.lstat(directory, { bigint: true });
|
|
386
399
|
if (current.dev !== pin.dev || current.ino !== pin.ino) return;
|
|
387
400
|
}
|
|
388
401
|
const { file, identity, handle } = preparation;
|
|
389
|
-
if (JSON.stringify(await baselinePin(file)) !== JSON.stringify(identity)) return;
|
|
402
|
+
if (JSON.stringify(await baselinePin(file, environment, platform)) !== JSON.stringify(identity)) return;
|
|
390
403
|
const held = await handle.stat({ bigint: true });
|
|
391
404
|
if (!held.isFile() || held.nlink !== 1n || held.size > 1n) return;
|
|
392
405
|
const bytes = Buffer.alloc(2);
|
|
@@ -395,7 +408,7 @@ async function settlePreparation(preparation, environment, platform, source) {
|
|
|
395
408
|
if (BigInt(bytesRead) !== held.size || bytesRead === 1 && bytes[0] !== 0x30) return;
|
|
396
409
|
await fs.unlink(file);
|
|
397
410
|
for (const [directory, pin] of preparation.directories) {
|
|
398
|
-
await
|
|
411
|
+
await selectedDirectory(directory, environment, platform);
|
|
399
412
|
const current = await fs.lstat(directory, { bigint: true });
|
|
400
413
|
if (current.dev !== pin.dev || current.ino !== pin.ino) break;
|
|
401
414
|
await fs.rmdir(directory);
|
|
@@ -441,21 +454,21 @@ async function reservePreparation(environment, platform, target, source) {
|
|
|
441
454
|
// object identity only; the bundled engine still takes the kernel gates.
|
|
442
455
|
const root = installationRoot(environment, platform), directory = path.join(root, '.stackbite-update');
|
|
443
456
|
await admitPublication(environment, platform);
|
|
444
|
-
await
|
|
457
|
+
await selectedDirectory(directory, environment, platform);
|
|
445
458
|
for (let parent = root; ; parent = path.dirname(parent)) {
|
|
446
459
|
if (await exists(path.join(parent, '.git'))) fail('Refusing installation preparation in a source repository');
|
|
447
460
|
if (parent === path.dirname(parent)) break;
|
|
448
461
|
}
|
|
449
462
|
if (source) await source.validate();
|
|
450
463
|
const firstCreated = await fs.mkdir(directory, { recursive: true, mode: 0o700 });
|
|
451
|
-
await
|
|
464
|
+
await selectedDirectory(directory, environment, platform);
|
|
452
465
|
const directories = await createdDirectories(firstCreated, [directory, root]);
|
|
453
466
|
const file = path.join(directory, 'update.lock');
|
|
454
467
|
const { handle, created } = await openPreparationFile(file);
|
|
455
468
|
let preparation;
|
|
456
469
|
try {
|
|
457
470
|
const held = await handle.stat({ bigint: true });
|
|
458
|
-
const identity = await baselinePin(file);
|
|
471
|
+
const identity = await baselinePin(file, environment, platform);
|
|
459
472
|
if (!held.isFile() || held.nlink !== 1n || JSON.stringify(identity) !== JSON.stringify({ device: held.dev.toString(), inode: held.ino.toString() })) fail('Invalid installation preparation');
|
|
460
473
|
preparation = { handle, file, identity, created, directories, target };
|
|
461
474
|
baseline = { root, preparation: { path: file, identity } };
|
|
@@ -496,7 +509,7 @@ async function publishCache(payload, target, spec, options) {
|
|
|
496
509
|
async function ensureBinary(spec, { cache = cacheDirectory(), downloadImpl = download, extractImpl = extractArchive,
|
|
497
510
|
checkOnly = false, reportCached = false, includePhone = false, progress = () => {},
|
|
498
511
|
environment = process.env, publishImpl = publishCache, registrationSource = null, ...options } = {}) {
|
|
499
|
-
cache =
|
|
512
|
+
cache = await resolveCacheDirectory(cache);
|
|
500
513
|
environment = { ...environment };
|
|
501
514
|
if (!checkOnly) {
|
|
502
515
|
await admitPublication(environment, spec.platform);
|
|
@@ -20,4 +20,24 @@ async function existingCacheDirectory(directory) {
|
|
|
20
20
|
if (!info.isDirectory() || info.isSymbolicLink()) fail('Cache directory contains a link or non-directory');
|
|
21
21
|
return true;
|
|
22
22
|
}
|
|
23
|
-
|
|
23
|
+
// Resolve the selected cache path once, including an absent suffix. Internal
|
|
24
|
+
// payload paths still use the link checks and pinned hashes in bootstrap.
|
|
25
|
+
async function resolveCacheDirectory(directory) {
|
|
26
|
+
directory = path.resolve(directory);
|
|
27
|
+
const missing = [];
|
|
28
|
+
let current = directory;
|
|
29
|
+
for (;;) {
|
|
30
|
+
try {
|
|
31
|
+
const resolved = await fs.realpath(current);
|
|
32
|
+
if (!(await fs.stat(resolved)).isDirectory()) fail('Cache directory contains a non-directory');
|
|
33
|
+
return path.join(resolved, ...missing.reverse());
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (error.code !== 'ENOENT') throw error;
|
|
36
|
+
const parent = path.dirname(current);
|
|
37
|
+
if (parent === current) throw error;
|
|
38
|
+
missing.push(path.basename(current));
|
|
39
|
+
current = parent;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
module.exports = { cacheDirectory, existingCacheDirectory, resolveCacheDirectory };
|
|
@@ -1,35 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": 1,
|
|
3
|
-
"version": "0.5.
|
|
4
|
-
"commit": "
|
|
3
|
+
"version": "0.5.104",
|
|
4
|
+
"commit": "2a3c74068c19b8fd4f3c75de6b814d1fbbb13398",
|
|
5
5
|
"repository": "Shinick-Han/stackbite",
|
|
6
6
|
"platforms": {
|
|
7
7
|
"win32-x64": {
|
|
8
|
-
"asset": "stackbite-0.5.
|
|
9
|
-
"url": "https://github.com/Shinick-Han/stackbite/releases/download/v0.5.
|
|
10
|
-
"sha256": "
|
|
11
|
-
"size":
|
|
12
|
-
"root": "stackbite-0.5.
|
|
13
|
-
"manifestSha256": "
|
|
14
|
-
"metadataSha256": "
|
|
8
|
+
"asset": "stackbite-0.5.104-windows-x64-2a3c74068c19.zip",
|
|
9
|
+
"url": "https://github.com/Shinick-Han/stackbite/releases/download/v0.5.104/stackbite-0.5.104-windows-x64-2a3c74068c19.zip",
|
|
10
|
+
"sha256": "2ecd22c876fd373b02ab797ac391669ac327b1d1564e96586febf79f7527a5e3",
|
|
11
|
+
"size": 96152210,
|
|
12
|
+
"root": "stackbite-0.5.104-windows-x64-2a3c74068c19",
|
|
13
|
+
"manifestSha256": "6759e5f9b827092b2c6dbf958c2f1b6f6f320b62eddc495c89f505e8388bf8ec",
|
|
14
|
+
"metadataSha256": "4884e5328b1892b7c92c8ea4e8d64c4e0adf381d3debe9985b69aa8c4d95fdb1"
|
|
15
15
|
},
|
|
16
16
|
"linux-x64": {
|
|
17
|
-
"asset": "stackbite-0.5.
|
|
18
|
-
"url": "https://github.com/Shinick-Han/stackbite/releases/download/v0.5.
|
|
19
|
-
"sha256": "
|
|
20
|
-
"size":
|
|
21
|
-
"root": "stackbite-0.5.
|
|
22
|
-
"manifestSha256": "
|
|
23
|
-
"metadataSha256": "
|
|
17
|
+
"asset": "stackbite-0.5.104-linux-x86_64-2a3c74068c19.tar.gz",
|
|
18
|
+
"url": "https://github.com/Shinick-Han/stackbite/releases/download/v0.5.104/stackbite-0.5.104-linux-x86_64-2a3c74068c19.tar.gz",
|
|
19
|
+
"sha256": "4c5d5e0dfe5d3df27ed8bb5e05c6c8a96b47ca0543256df11493c777d4811fdf",
|
|
20
|
+
"size": 77352673,
|
|
21
|
+
"root": "stackbite-0.5.104-linux-x86_64-2a3c74068c19",
|
|
22
|
+
"manifestSha256": "5e155d2bc1e5a71c3adb3de9283068509d6a14432c603a7303a35c741329fa78",
|
|
23
|
+
"metadataSha256": "14bbaf1a24aa530cd884b224b685d9a3a319f98034699d988256a4f61c1d0fc0"
|
|
24
24
|
},
|
|
25
25
|
"darwin-arm64": {
|
|
26
|
-
"asset": "stackbite-0.5.
|
|
27
|
-
"url": "https://github.com/Shinick-Han/stackbite/releases/download/v0.5.
|
|
28
|
-
"sha256": "
|
|
29
|
-
"size":
|
|
30
|
-
"root": "stackbite-0.5.
|
|
31
|
-
"manifestSha256": "
|
|
32
|
-
"metadataSha256": "
|
|
26
|
+
"asset": "stackbite-0.5.104-macos-arm64-2a3c74068c19.tar.gz",
|
|
27
|
+
"url": "https://github.com/Shinick-Han/stackbite/releases/download/v0.5.104/stackbite-0.5.104-macos-arm64-2a3c74068c19.tar.gz",
|
|
28
|
+
"sha256": "2539869082d156dc47777076e53c46600b69978fe4919686412f33ad97433572",
|
|
29
|
+
"size": 33375555,
|
|
30
|
+
"root": "stackbite-0.5.104-macos-arm64-2a3c74068c19",
|
|
31
|
+
"manifestSha256": "f436a6093bf7dc47a3f9732bdd121020465eb9b8c36cc076fb8517a018e35792",
|
|
32
|
+
"metadataSha256": "a3af64a59e7daeb23620f2c140ceba164048c93bd4567a24d84a05a9f3ba65b8"
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
}
|