stackbite 0.5.106 → 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 +190 -15
- package/packaging/npm/lib/setup.js +1 -1
- package/packaging/npm/release.json +23 -23
package/package.json
CHANGED
|
@@ -484,8 +484,70 @@ async function reservePreparation(environment, platform, target, source) {
|
|
|
484
484
|
}
|
|
485
485
|
}
|
|
486
486
|
|
|
487
|
+
// Durable runtime images share the standalone installer's versioned app root,
|
|
488
|
+
// with its live-reference retention and removal inventory. The disposable
|
|
489
|
+
// cache only holds images of the previous layout, which may be adopted.
|
|
490
|
+
// A different archive of the same version and commit (for example a signed
|
|
491
|
+
// rebuild) gets a sibling qualified by its pinned archive digest, so an image
|
|
492
|
+
// another installer or pin published is never replaced or run under this pin.
|
|
493
|
+
function runtimeName(spec, qualified = false) {
|
|
494
|
+
const commit = spec.commit.slice(0, 12), version = spec.version + (qualified ? `-pin${spec.sha256.slice(0, 10)}` : '');
|
|
495
|
+
return spec.platform === 'win32' ? `${version}-${commit}` : `stackbite-${version}-${spec.host}-${commit}`;
|
|
496
|
+
}
|
|
497
|
+
async function pinnedTarget(runtime, spec) {
|
|
498
|
+
const canonical = path.join(runtime, runtimeName(spec));
|
|
499
|
+
if (!await exists(canonical)) return canonical;
|
|
500
|
+
try { await pinnedDocuments(await payloadRoot(canonical), spec); return canonical; }
|
|
501
|
+
catch (error) { if (/manifest or metadata SHA-256 mismatch/.test(error.message)) return path.join(runtime, runtimeName(spec, true)); throw error; }
|
|
502
|
+
}
|
|
503
|
+
function previousCacheHome(home, cache, platform) {
|
|
504
|
+
const spelling = value => platform === 'win32' ? value.toLowerCase() : value;
|
|
505
|
+
return typeof home === 'string' && path.isAbsolute(home) && /^v1-[a-f0-9]{64}$/.test(path.basename(home))
|
|
506
|
+
&& !!cache && spelling(path.dirname(path.resolve(home))) === spelling(cache);
|
|
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
|
+
}
|
|
487
548
|
async function publishCache(payload, target, spec, options) {
|
|
488
549
|
await assertBaseline(options.environment, spec.platform);
|
|
550
|
+
let migrate = false;
|
|
489
551
|
if (options.existing) {
|
|
490
552
|
const root = installationRoot(options.environment, spec.platform);
|
|
491
553
|
for (const name of ['default-install.json', 'install.json']) {
|
|
@@ -493,46 +555,140 @@ async function publishCache(payload, target, spec, options) {
|
|
|
493
555
|
if (!await exists(locator)) continue;
|
|
494
556
|
const saved = JSON.parse((await readSmall(locator, 16384)).toString('utf8').replace(/^\uFEFF/, ''));
|
|
495
557
|
const descriptor = options.environment.STACKBITE_NPM_REGISTRATION;
|
|
496
|
-
|
|
558
|
+
migrate = previousCacheHome(saved.home, options.cache, spec.platform)
|
|
559
|
+
|| !!await formerSelection(saved, options.environment, spec);
|
|
560
|
+
if (!migrate && typeof saved.home === 'string' && path.isAbsolute(saved.home)
|
|
497
561
|
&& typeof saved.state === 'string' && path.isAbsolute(saved.state) && /^[a-f0-9]{32}$/.test(saved.installation_id)
|
|
498
562
|
&& (!descriptor || JSON.stringify(saved.npm_registration) === JSON.stringify(JSON.parse(descriptor)))) return;
|
|
499
563
|
break;
|
|
500
564
|
}
|
|
501
565
|
}
|
|
502
566
|
const engine = path.join(payload, 'stackbite' + (spec.platform === 'win32' ? '.exe' : ''));
|
|
503
|
-
|
|
567
|
+
// A selection still in the previous cache layout moves to the durable image.
|
|
568
|
+
const argv = options.existing && !migrate ? ['--record-install', target]
|
|
504
569
|
: ['--record-install', target, '-', payload, 'cache'];
|
|
505
570
|
const result = await launchNative(engine, argv, { ...options.environment, STACKBITE_HOME: payload }, options);
|
|
506
571
|
if (result.code !== 0 || result.signal) fail('Runtime publication was refused');
|
|
507
572
|
}
|
|
508
573
|
|
|
509
|
-
|
|
510
|
-
|
|
574
|
+
function runtimeDirectory(environment, platform) {
|
|
575
|
+
return path.join(installationRoot(environment, platform), 'app');
|
|
576
|
+
}
|
|
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
|
+
}
|
|
639
|
+
async function previousImage(legacy, cache, spec) {
|
|
640
|
+
return await existingCacheDirectory(cache) && await exists(legacy) ? verifiedImage(legacy, spec) : null;
|
|
641
|
+
}
|
|
642
|
+
async function ensureBinary(spec, { cache = cacheDirectory(), runtime = null, downloadImpl = download, extractImpl = extractArchive,
|
|
643
|
+
checkOnly = false, reportCached = false, includePhone = false, reuseSelected = false, progress = () => {},
|
|
511
644
|
environment = process.env, publishImpl = publishCache, registrationSource = null, ...options } = {}) {
|
|
512
645
|
cache = await resolveCacheDirectory(cache);
|
|
513
646
|
environment = { ...environment };
|
|
647
|
+
runtime = path.resolve(runtime || runtimeDirectory(environment, spec.platform));
|
|
514
648
|
if (!checkOnly) {
|
|
515
649
|
await admitPublication(environment, spec.platform);
|
|
516
650
|
await existingCacheDirectory(cache); // Validate existing ancestors without creating a cache.
|
|
517
651
|
}
|
|
518
|
-
if (checkOnly) {
|
|
519
|
-
if (!await existingCacheDirectory(cache)) throw notCached();
|
|
520
|
-
}
|
|
521
652
|
// Full identity remains SHA-256 addressed while avoiding Windows MAX_PATH.
|
|
522
653
|
const cacheKey = createHash('sha256').update(JSON.stringify([spec.version, spec.commit, spec.key, spec.sha256])).digest('hex');
|
|
523
|
-
const
|
|
654
|
+
const legacy = path.join(cache, `v1-${cacheKey}`);
|
|
655
|
+
const target = await selectedDirectory(runtime, environment, spec.platform) ? await pinnedTarget(runtime, spec)
|
|
656
|
+
: path.join(runtime, runtimeName(spec));
|
|
524
657
|
const preparation = !checkOnly ? await reservePreparation(environment, spec.platform, target, registrationSource) : null;
|
|
525
658
|
try {
|
|
526
659
|
if (registrationSource) await registrationSource.validate();
|
|
527
660
|
if (!checkOnly) await assertBaseline(environment, spec.platform);
|
|
528
|
-
|
|
661
|
+
// Check-only callers (removal, setup --check) never publish; they may still
|
|
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);
|
|
669
|
+
if (home) {
|
|
529
670
|
if (checkOnly || reportCached) progress('validate');
|
|
530
|
-
const installed = { home
|
|
531
|
-
|
|
671
|
+
const installed = { home, executable: await validatePayload(home, spec, { includePhone }) };
|
|
672
|
+
// A selected image outside app/ is its own maintenance engine.
|
|
673
|
+
if (!checkOnly) await publishImpl(home, target, spec, { ...options, environment, cache, existing: true });
|
|
532
674
|
if (checkOnly || reportCached) progress('ready');
|
|
533
675
|
return installed;
|
|
534
676
|
}
|
|
535
677
|
if (checkOnly) throw notCached();
|
|
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);
|
|
681
|
+
if (previous) {
|
|
682
|
+
progress('publish');
|
|
683
|
+
await admitPublication(environment, spec.platform);
|
|
684
|
+
await assertBaseline(environment, spec.platform);
|
|
685
|
+
if (registrationSource) await registrationSource.validate();
|
|
686
|
+
await publishImpl(previous, target, spec, { ...options, environment, cache, existing: false });
|
|
687
|
+
progress('validate-published');
|
|
688
|
+
const installed = { home: target, executable: await validatePayload(target, spec) };
|
|
689
|
+
progress('ready');
|
|
690
|
+
return installed;
|
|
691
|
+
}
|
|
536
692
|
// An owned system-temp stage keeps downloads out of product caches. Only the
|
|
537
693
|
// verified engine below can create/publish a canonical cache installation.
|
|
538
694
|
await admitPublication(environment, spec.platform);
|
|
@@ -545,7 +701,7 @@ async function ensureBinary(spec, { cache = cacheDirectory(), downloadImpl = dow
|
|
|
545
701
|
await admitPublication(environment, spec.platform);
|
|
546
702
|
await assertBaseline(environment, spec.platform);
|
|
547
703
|
if (registrationSource) await registrationSource.validate();
|
|
548
|
-
await publishImpl(payload, target, spec, { ...options, environment, existing: false });
|
|
704
|
+
await publishImpl(payload, target, spec, { ...options, environment, cache, existing: false });
|
|
549
705
|
// Validate at the final path, including a concurrent publisher's payload.
|
|
550
706
|
progress('validate-published');
|
|
551
707
|
const installed = { home: target, executable: await validatePayload(target, spec) };
|
|
@@ -565,9 +721,11 @@ async function stageRelease(spec, staging, { downloadImpl, extractImpl, progress
|
|
|
565
721
|
await fs.mkdir(extraction, { mode: 0o700 });
|
|
566
722
|
progress('download');
|
|
567
723
|
await downloadImpl(spec, archive);
|
|
568
|
-
//
|
|
724
|
+
// download() itself verifies the exact size and SHA-256 before returning.
|
|
725
|
+
// Recheck any other transport at the extraction boundary.
|
|
569
726
|
progress('archive');
|
|
570
|
-
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');
|
|
571
729
|
progress('extract');
|
|
572
730
|
await extractImpl(archive, extraction, spec);
|
|
573
731
|
const payload = path.join(extraction, spec.root);
|
|
@@ -630,6 +788,21 @@ async function launchNative(executable, argv, environment, { spawnImpl = spawn,
|
|
|
630
788
|
child.once('exit', (code, signal) => { cleanup(); resolve({ code, signal }); });
|
|
631
789
|
});
|
|
632
790
|
}
|
|
791
|
+
// Before the first install, --help/--version answer from this package's pin
|
|
792
|
+
// instead of downloading a runtime; the launcher only ever runs that build.
|
|
793
|
+
async function coldAnswer(argv, spec, runtime, { stdout = process.stdout, stderr = process.stderr }) {
|
|
794
|
+
if (argv.length !== 1 || !['--version', '--help', '-h'].includes(argv[0])) return null;
|
|
795
|
+
try { if (await exists(await pinnedTarget(runtime, spec))) return null; } catch { return null; }
|
|
796
|
+
if (argv[0] === '--version') {
|
|
797
|
+
stdout.write(`Stackbite ${spec.version} (${spec.commit})\n`);
|
|
798
|
+
stderr.write('Stackbite: this is the pinned release; its runtime is not installed yet. Run stackbite setup to install it.\n');
|
|
799
|
+
} else {
|
|
800
|
+
stdout.write('usage: stackbite [--help | --version] | stackbite setup [--check] | stackbite [COMMAND ...]\n\n'
|
|
801
|
+
+ `The pinned Stackbite ${spec.version} runtime is not installed yet. Run stackbite setup to\n`
|
|
802
|
+
+ 'install it (or run any command); stackbite --help then shows the full reference.\n');
|
|
803
|
+
}
|
|
804
|
+
return { code: 0, signal: null };
|
|
805
|
+
}
|
|
633
806
|
async function main(argv = process.argv.slice(2), options = {}) {
|
|
634
807
|
const setup = parseSetup(argv);
|
|
635
808
|
const removing = ['uninstall', 'delete'].includes(argv[0]);
|
|
@@ -645,13 +818,15 @@ async function main(argv = process.argv.slice(2), options = {}) {
|
|
|
645
818
|
try {
|
|
646
819
|
if (npm) environment.STACKBITE_NPM_REGISTRATION = JSON.stringify(npm);
|
|
647
820
|
const spec = releaseSpec(manifest, platform, options.arch || process.arch, options);
|
|
821
|
+
const cold = await coldAnswer(argv, spec, options.runtime || runtimeDirectory(environment, platform), options);
|
|
822
|
+
if (cold) return cold;
|
|
648
823
|
// First install always verifies everything. Cached TUI/runtime starts defer only
|
|
649
824
|
// Phone; explicit phone/setup/removal commands retain the full pinned check.
|
|
650
825
|
let installed;
|
|
651
826
|
try { installed = await timing.phase(environment, 'launcher.ensure', () => ensureBinary(spec, { ...options,
|
|
652
827
|
environment, registrationSource,
|
|
653
828
|
cache: options.cache || cacheDirectory(environment, platform),
|
|
654
|
-
checkOnly: removing || setup?.checkOnly || false, reportCached: Boolean(setup),
|
|
829
|
+
checkOnly: removing || setup?.checkOnly || false, reportCached: Boolean(setup), reuseSelected: !removing,
|
|
655
830
|
includePhone: removing || Boolean(setup) || argv[0] === 'phone',
|
|
656
831
|
progress: options.progress || stageReporter(options.stderr || process.stderr) })); }
|
|
657
832
|
catch (error) {
|
|
@@ -14,7 +14,7 @@ function stageReporter(stderr) {
|
|
|
14
14
|
archive: 'Verifying archive...',
|
|
15
15
|
extract: 'Extracting runtime...',
|
|
16
16
|
validate: 'Validating runtime files...',
|
|
17
|
-
publish: 'Publishing runtime
|
|
17
|
+
publish: 'Publishing runtime...',
|
|
18
18
|
'validate-published': 'Validating published runtime...',
|
|
19
19
|
ready: 'Pinned runtime is ready.'
|
|
20
20
|
};
|
|
@@ -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
|
}
|