stackbite 0.5.107 → 0.5.108
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 +123 -15
- package/packaging/npm/release.json +23 -23
package/package.json
CHANGED
|
@@ -505,6 +505,46 @@ function previousCacheHome(home, cache, platform) {
|
|
|
505
505
|
return typeof home === 'string' && path.isAbsolute(home) && /^v1-[a-f0-9]{64}$/.test(path.basename(home))
|
|
506
506
|
&& !!cache && spelling(path.dirname(path.resolve(home))) === spelling(cache);
|
|
507
507
|
}
|
|
508
|
+
// Bounded portable.json of an image; null when absent, linked, oversized or malformed.
|
|
509
|
+
async function portableMetadata(root) {
|
|
510
|
+
try { return JSON.parse((await readSmall(path.join(root, 'portable.json'), 16384)).toString('utf8')); }
|
|
511
|
+
catch { return null; }
|
|
512
|
+
}
|
|
513
|
+
// The release identity Python's former-cache check compares (Windows may omit platform).
|
|
514
|
+
function portableMatches(value, spec) {
|
|
515
|
+
return !!value && typeof value === 'object' && value.product === 'Stackbite' && value.command === 'stackbite'
|
|
516
|
+
&& value.version === spec.version && value.commit === spec.commit
|
|
517
|
+
&& (Object.hasOwn(value, 'platform') ? value.platform : spec.platform === 'win32' ? spec.host : undefined) === spec.host;
|
|
518
|
+
}
|
|
519
|
+
// An absolute literal path with no linked ancestor and no source repository.
|
|
520
|
+
async function literalPath(home) {
|
|
521
|
+
if (!path.isAbsolute(home) || path.resolve(home) !== home) return false;
|
|
522
|
+
for (let ancestor = home; ; ancestor = path.dirname(ancestor)) {
|
|
523
|
+
let info = null;
|
|
524
|
+
try { info = await fs.lstat(ancestor); } catch (error) { if (error.code !== 'ENOENT') return false; }
|
|
525
|
+
if (info?.isSymbolicLink() || info && await exists(path.join(ancestor, '.git'))) return false;
|
|
526
|
+
if (ancestor === path.dirname(ancestor)) return true;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
// A previous-layout cache image recorded by a host with another cache root:
|
|
530
|
+
// the shape and portable identity Python's _former_cache_image accepts. This
|
|
531
|
+
// classifies the file only; no environment state changes it. It is only read.
|
|
532
|
+
async function formerCache(home, spec) {
|
|
533
|
+
if (typeof home !== 'string' || !/^v1-[a-f0-9]{64}$/.test(path.basename(home))
|
|
534
|
+
|| path.basename(path.dirname(home)) !== 'stackbite') return null;
|
|
535
|
+
try {
|
|
536
|
+
if (!await literalPath(home) || !(await fs.lstat(home)).isDirectory()) return null;
|
|
537
|
+
} catch { return null; }
|
|
538
|
+
return portableMatches(await portableMetadata(home), spec) ? home : null;
|
|
539
|
+
}
|
|
540
|
+
// A former cache selection this run may migrate: the engine moves a selection
|
|
541
|
+
// only under its own state, so a per-run STACKBITE_STATE_DIR never does. The
|
|
542
|
+
// engine reads a legacy HELM_STATE_DIR only when STACKBITE_STATE_DIR is absent
|
|
543
|
+
// (even empty, the canonical name wins). It decides again under its locks.
|
|
544
|
+
async function formerSelection(saved, environment, spec) {
|
|
545
|
+
const state = Object.hasOwn(environment, 'STACKBITE_STATE_DIR') ? environment.STACKBITE_STATE_DIR : environment.HELM_STATE_DIR;
|
|
546
|
+
return state && path.resolve(state) !== saved?.state ? null : formerCache(saved?.home, spec);
|
|
547
|
+
}
|
|
508
548
|
async function publishCache(payload, target, spec, options) {
|
|
509
549
|
await assertBaseline(options.environment, spec.platform);
|
|
510
550
|
let migrate = false;
|
|
@@ -515,7 +555,8 @@ async function publishCache(payload, target, spec, options) {
|
|
|
515
555
|
if (!await exists(locator)) continue;
|
|
516
556
|
const saved = JSON.parse((await readSmall(locator, 16384)).toString('utf8').replace(/^\uFEFF/, ''));
|
|
517
557
|
const descriptor = options.environment.STACKBITE_NPM_REGISTRATION;
|
|
518
|
-
migrate = previousCacheHome(saved.home, options.cache, spec.platform)
|
|
558
|
+
migrate = previousCacheHome(saved.home, options.cache, spec.platform)
|
|
559
|
+
|| !!await formerSelection(saved, options.environment, spec);
|
|
519
560
|
if (!migrate && typeof saved.home === 'string' && path.isAbsolute(saved.home)
|
|
520
561
|
&& typeof saved.state === 'string' && path.isAbsolute(saved.state) && /^[a-f0-9]{32}$/.test(saved.installation_id)
|
|
521
562
|
&& (!descriptor || JSON.stringify(saved.npm_registration) === JSON.stringify(JSON.parse(descriptor)))) return;
|
|
@@ -533,15 +574,73 @@ async function publishCache(payload, target, spec, options) {
|
|
|
533
574
|
function runtimeDirectory(environment, platform) {
|
|
534
575
|
return path.join(installationRoot(environment, platform), 'app');
|
|
535
576
|
}
|
|
536
|
-
//
|
|
537
|
-
//
|
|
577
|
+
// This root's first locator, when it fully names an installation: the same
|
|
578
|
+
// shape publishCache keeps without rewriting its home.
|
|
579
|
+
async function establishedLocator(root) {
|
|
580
|
+
for (const name of ['default-install.json', 'install.json']) {
|
|
581
|
+
const locator = path.join(root, name);
|
|
582
|
+
if (!await exists(locator)) continue;
|
|
583
|
+
const saved = JSON.parse((await readSmall(locator, 16384)).toString('utf8').replace(/^\uFEFF/, ''));
|
|
584
|
+
return saved && typeof saved.home === 'string' && path.isAbsolute(saved.home)
|
|
585
|
+
&& typeof saved.state === 'string' && path.isAbsolute(saved.state)
|
|
586
|
+
&& /^[a-f0-9]{32}$/.test(saved.installation_id) ? saved : null;
|
|
587
|
+
}
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
async function installationLocator(environment, platform) {
|
|
591
|
+
const root = installationRoot(environment, platform);
|
|
592
|
+
return await selectedDirectory(root, environment, platform) ? establishedLocator(root) : null;
|
|
593
|
+
}
|
|
594
|
+
// The image root when its portable identity is this release; null otherwise.
|
|
595
|
+
async function sameRelease(home, spec) {
|
|
596
|
+
let root;
|
|
597
|
+
try { root = await payloadRoot(home); } catch { return null; }
|
|
598
|
+
return portableMatches(await portableMetadata(root), spec) ? root : null;
|
|
599
|
+
}
|
|
600
|
+
// Another archive of this release (a re-signed build) carries its own valid
|
|
601
|
+
// checksum manifest that pins its portable.json. A damaged image does not.
|
|
602
|
+
async function anotherPin(root) {
|
|
603
|
+
try {
|
|
604
|
+
const hashes = JSON.parse((await readSmall(path.join(root, 'SHA256SUMS.json'))).toString('utf8'));
|
|
605
|
+
expectedMembers(hashes);
|
|
606
|
+
const metadata = await readSmall(path.join(root, 'portable.json'), 16384);
|
|
607
|
+
return hashes['portable.json'] === createHash('sha256').update(metadata).digest('hex');
|
|
608
|
+
} catch { return false; }
|
|
609
|
+
}
|
|
610
|
+
// An image the established installation already selects outside app/: an
|
|
611
|
+
// explicit STACKBITE_HOME, else the locator's home. Another release or pin is
|
|
612
|
+
// skipped; an image of this release must otherwise carry this package's pinned
|
|
613
|
+
// documents, and the caller then validates its whole payload, so a damaged image
|
|
614
|
+
// of this release is refused, not replaced. Publication keeps the locator's
|
|
615
|
+
// home, except that a cache image (this host's or a former host's) is never run
|
|
616
|
+
// in place.
|
|
617
|
+
async function selectedImage(environment, spec, cache) {
|
|
618
|
+
const saved = await installationLocator(environment, spec.platform);
|
|
619
|
+
// A previous-cache selection keeps its existing migration path.
|
|
620
|
+
if (!saved || previousCacheHome(saved.home, cache, spec.platform)) return null;
|
|
621
|
+
const former = await formerCache(saved.home, spec);
|
|
622
|
+
for (const home of [environment.STACKBITE_HOME, saved.home]) {
|
|
623
|
+
if (typeof home !== 'string' || !path.isAbsolute(home) || home === former) continue;
|
|
624
|
+
const root = await sameRelease(home, spec);
|
|
625
|
+
if (!root) continue;
|
|
626
|
+
try { await pinnedDocuments(root, spec); }
|
|
627
|
+
catch (error) { if (await anotherPin(root)) continue; throw error; }
|
|
628
|
+
return path.resolve(home);
|
|
629
|
+
}
|
|
630
|
+
return null;
|
|
631
|
+
}
|
|
632
|
+
// An image of this exact pinned identity, when it passes the full pinned
|
|
633
|
+
// validation. It is only read; a live process may use it.
|
|
634
|
+
async function verifiedImage(home, spec) {
|
|
635
|
+
if (!home) return null;
|
|
636
|
+
try { await validatePayload(home, spec); } catch { return null; }
|
|
637
|
+
return home;
|
|
638
|
+
}
|
|
538
639
|
async function previousImage(legacy, cache, spec) {
|
|
539
|
-
|
|
540
|
-
try { await validatePayload(legacy, spec); } catch { return null; }
|
|
541
|
-
return legacy;
|
|
640
|
+
return await existingCacheDirectory(cache) && await exists(legacy) ? verifiedImage(legacy, spec) : null;
|
|
542
641
|
}
|
|
543
642
|
async function ensureBinary(spec, { cache = cacheDirectory(), runtime = null, downloadImpl = download, extractImpl = extractArchive,
|
|
544
|
-
checkOnly = false, reportCached = false, includePhone = false, progress = () => {},
|
|
643
|
+
checkOnly = false, reportCached = false, includePhone = false, reuseSelected = false, progress = () => {},
|
|
545
644
|
environment = process.env, publishImpl = publishCache, registrationSource = null, ...options } = {}) {
|
|
546
645
|
cache = await resolveCacheDirectory(cache);
|
|
547
646
|
environment = { ...environment };
|
|
@@ -560,18 +659,25 @@ async function ensureBinary(spec, { cache = cacheDirectory(), runtime = null, do
|
|
|
560
659
|
if (registrationSource) await registrationSource.validate();
|
|
561
660
|
if (!checkOnly) await assertBaseline(environment, spec.platform);
|
|
562
661
|
// Check-only callers (removal, setup --check) never publish; they may still
|
|
563
|
-
// use an image of the previous cache layout.
|
|
564
|
-
const
|
|
565
|
-
|
|
662
|
+
// use an image of the previous cache layout, this host's or a former host's.
|
|
663
|
+
const published = await selectedDirectory(runtime, environment, spec.platform) && await exists(target);
|
|
664
|
+
const former = !published && reuseSelected
|
|
665
|
+
? await formerCache((await installationLocator(environment, spec.platform))?.home, spec) : null;
|
|
666
|
+
const home = published ? target
|
|
667
|
+
: reuseSelected && await selectedImage(environment, spec, cache)
|
|
668
|
+
|| (checkOnly ? (await existingCacheDirectory(cache) && await exists(legacy) ? legacy : former) : null);
|
|
566
669
|
if (home) {
|
|
567
670
|
if (checkOnly || reportCached) progress('validate');
|
|
568
671
|
const installed = { home, executable: await validatePayload(home, spec, { includePhone }) };
|
|
569
|
-
|
|
672
|
+
// A selected image outside app/ is its own maintenance engine.
|
|
673
|
+
if (!checkOnly) await publishImpl(home, target, spec, { ...options, environment, cache, existing: true });
|
|
570
674
|
if (checkOnly || reportCached) progress('ready');
|
|
571
675
|
return installed;
|
|
572
676
|
}
|
|
573
677
|
if (checkOnly) throw notCached();
|
|
574
|
-
|
|
678
|
+
// A verified previous-layout image is copied once into the durable target;
|
|
679
|
+
// the engine then moves a cache selection there.
|
|
680
|
+
const previous = await previousImage(legacy, cache, spec) || await verifiedImage(former, spec);
|
|
575
681
|
if (previous) {
|
|
576
682
|
progress('publish');
|
|
577
683
|
await admitPublication(environment, spec.platform);
|
|
@@ -615,9 +721,11 @@ async function stageRelease(spec, staging, { downloadImpl, extractImpl, progress
|
|
|
615
721
|
await fs.mkdir(extraction, { mode: 0o700 });
|
|
616
722
|
progress('download');
|
|
617
723
|
await downloadImpl(spec, archive);
|
|
618
|
-
//
|
|
724
|
+
// download() itself verifies the exact size and SHA-256 before returning.
|
|
725
|
+
// Recheck any other transport at the extraction boundary.
|
|
619
726
|
progress('archive');
|
|
620
|
-
if ((await fs.stat(archive)).size !== spec.size
|
|
727
|
+
if (downloadImpl !== download && ((await fs.stat(archive)).size !== spec.size
|
|
728
|
+
|| await hashFile(archive, LIMITS.download) !== spec.sha256)) fail('Pinned archive SHA-256 or size mismatch');
|
|
621
729
|
progress('extract');
|
|
622
730
|
await extractImpl(archive, extraction, spec);
|
|
623
731
|
const payload = path.join(extraction, spec.root);
|
|
@@ -718,7 +826,7 @@ async function main(argv = process.argv.slice(2), options = {}) {
|
|
|
718
826
|
try { installed = await timing.phase(environment, 'launcher.ensure', () => ensureBinary(spec, { ...options,
|
|
719
827
|
environment, registrationSource,
|
|
720
828
|
cache: options.cache || cacheDirectory(environment, platform),
|
|
721
|
-
checkOnly: removing || setup?.checkOnly || false, reportCached: Boolean(setup),
|
|
829
|
+
checkOnly: removing || setup?.checkOnly || false, reportCached: Boolean(setup), reuseSelected: !removing,
|
|
722
830
|
includePhone: removing || Boolean(setup) || argv[0] === 'phone',
|
|
723
831
|
progress: options.progress || stageReporter(options.stderr || process.stderr) })); }
|
|
724
832
|
catch (error) {
|
|
@@ -1,35 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": 1,
|
|
3
|
-
"version": "0.5.
|
|
4
|
-
"commit": "
|
|
3
|
+
"version": "0.5.108",
|
|
4
|
+
"commit": "bcd3e47ee369cdbf209688fb8333b7b582e292cb",
|
|
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.108-windows-x64-bcd3e47ee369.zip",
|
|
9
|
+
"url": "https://github.com/Shinick-Han/stackbite/releases/download/v0.5.108/stackbite-0.5.108-windows-x64-bcd3e47ee369.zip",
|
|
10
|
+
"sha256": "416b55e30a6b3670b74477e078515b927ffa61f01d1aadb255a41a30d5145c7a",
|
|
11
|
+
"size": 82803119,
|
|
12
|
+
"root": "stackbite-0.5.108-windows-x64-bcd3e47ee369",
|
|
13
|
+
"manifestSha256": "8d00d3cbe4ddb913c06392427be2e0b6a8162d88fdb3bcb2fa573fb55a95d34c",
|
|
14
|
+
"metadataSha256": "4fdcdaeeab139ac774f67c61c27bb32ba07a3151df059f7918cd042f6b89a72e"
|
|
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.108-linux-x86_64-bcd3e47ee369.tar.gz",
|
|
18
|
+
"url": "https://github.com/Shinick-Han/stackbite/releases/download/v0.5.108/stackbite-0.5.108-linux-x86_64-bcd3e47ee369.tar.gz",
|
|
19
|
+
"sha256": "83fba4c60508d2a4fcbaa276c51e8eb7db9f2f762e2f28228b9b6ece9ac10327",
|
|
20
|
+
"size": 82791962,
|
|
21
|
+
"root": "stackbite-0.5.108-linux-x86_64-bcd3e47ee369",
|
|
22
|
+
"manifestSha256": "36dfdbf9f1fc5dcdc882e438ff855065d3456f6674dea115fd219b6e7a7893fb",
|
|
23
|
+
"metadataSha256": "ee20086e1c78590a83dab038dce86e0b28d288fc6d0665f00c041644777340ad"
|
|
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.108-macos-arm64-bcd3e47ee369.tar.gz",
|
|
27
|
+
"url": "https://github.com/Shinick-Han/stackbite/releases/download/v0.5.108/stackbite-0.5.108-macos-arm64-bcd3e47ee369.tar.gz",
|
|
28
|
+
"sha256": "3d1afcb0b7c197cf76691bff3807f8b11bfecda2c93e70f67a7b44c71f312299",
|
|
29
|
+
"size": 38100942,
|
|
30
|
+
"root": "stackbite-0.5.108-macos-arm64-bcd3e47ee369",
|
|
31
|
+
"manifestSha256": "0e1c7f4958fe4852d2e25732e18ef85523fe2a2047647362837cacf8243bed45",
|
|
32
|
+
"metadataSha256": "cac6ffae5862aa0cb5f3b51c62c798c042ae0bcc3ed06172d1f74632c77848e1"
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
}
|