stackbite 0.5.105 → 0.5.107

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stackbite",
3
- "version": "0.5.105",
3
+ "version": "0.5.107",
4
4
  "description": "Verified native Stackbite CLI bootstrapper",
5
5
  "license": "UNLICENSED",
6
6
  "engines": {
@@ -484,8 +484,30 @@ 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
+ }
487
508
  async function publishCache(payload, target, spec, options) {
488
509
  await assertBaseline(options.environment, spec.platform);
510
+ let migrate = false;
489
511
  if (options.existing) {
490
512
  const root = installationRoot(options.environment, spec.platform);
491
513
  for (const name of ['default-install.json', 'install.json']) {
@@ -493,46 +515,74 @@ async function publishCache(payload, target, spec, options) {
493
515
  if (!await exists(locator)) continue;
494
516
  const saved = JSON.parse((await readSmall(locator, 16384)).toString('utf8').replace(/^\uFEFF/, ''));
495
517
  const descriptor = options.environment.STACKBITE_NPM_REGISTRATION;
496
- if (typeof saved.home === 'string' && path.isAbsolute(saved.home)
518
+ migrate = previousCacheHome(saved.home, options.cache, spec.platform);
519
+ if (!migrate && typeof saved.home === 'string' && path.isAbsolute(saved.home)
497
520
  && typeof saved.state === 'string' && path.isAbsolute(saved.state) && /^[a-f0-9]{32}$/.test(saved.installation_id)
498
521
  && (!descriptor || JSON.stringify(saved.npm_registration) === JSON.stringify(JSON.parse(descriptor)))) return;
499
522
  break;
500
523
  }
501
524
  }
502
525
  const engine = path.join(payload, 'stackbite' + (spec.platform === 'win32' ? '.exe' : ''));
503
- const argv = options.existing ? ['--record-install', target]
526
+ // A selection still in the previous cache layout moves to the durable image.
527
+ const argv = options.existing && !migrate ? ['--record-install', target]
504
528
  : ['--record-install', target, '-', payload, 'cache'];
505
529
  const result = await launchNative(engine, argv, { ...options.environment, STACKBITE_HOME: payload }, options);
506
530
  if (result.code !== 0 || result.signal) fail('Runtime publication was refused');
507
531
  }
508
532
 
509
- async function ensureBinary(spec, { cache = cacheDirectory(), downloadImpl = download, extractImpl = extractArchive,
533
+ function runtimeDirectory(environment, platform) {
534
+ return path.join(installationRoot(environment, platform), 'app');
535
+ }
536
+ // The previous layout's image of this exact pinned identity, when it still
537
+ // passes the full pinned validation. It is only read; a live process may use it.
538
+ async function previousImage(legacy, cache, spec) {
539
+ if (!await existingCacheDirectory(cache) || !await exists(legacy)) return null;
540
+ try { await validatePayload(legacy, spec); } catch { return null; }
541
+ return legacy;
542
+ }
543
+ async function ensureBinary(spec, { cache = cacheDirectory(), runtime = null, downloadImpl = download, extractImpl = extractArchive,
510
544
  checkOnly = false, reportCached = false, includePhone = false, progress = () => {},
511
545
  environment = process.env, publishImpl = publishCache, registrationSource = null, ...options } = {}) {
512
546
  cache = await resolveCacheDirectory(cache);
513
547
  environment = { ...environment };
548
+ runtime = path.resolve(runtime || runtimeDirectory(environment, spec.platform));
514
549
  if (!checkOnly) {
515
550
  await admitPublication(environment, spec.platform);
516
551
  await existingCacheDirectory(cache); // Validate existing ancestors without creating a cache.
517
552
  }
518
- if (checkOnly) {
519
- if (!await existingCacheDirectory(cache)) throw notCached();
520
- }
521
553
  // Full identity remains SHA-256 addressed while avoiding Windows MAX_PATH.
522
554
  const cacheKey = createHash('sha256').update(JSON.stringify([spec.version, spec.commit, spec.key, spec.sha256])).digest('hex');
523
- const target = path.join(cache, `v1-${cacheKey}`);
555
+ const legacy = path.join(cache, `v1-${cacheKey}`);
556
+ const target = await selectedDirectory(runtime, environment, spec.platform) ? await pinnedTarget(runtime, spec)
557
+ : path.join(runtime, runtimeName(spec));
524
558
  const preparation = !checkOnly ? await reservePreparation(environment, spec.platform, target, registrationSource) : null;
525
559
  try {
526
560
  if (registrationSource) await registrationSource.validate();
527
561
  if (!checkOnly) await assertBaseline(environment, spec.platform);
528
- if (await exists(target)) {
562
+ // Check-only callers (removal, setup --check) never publish; they may still
563
+ // use an image of the previous cache layout.
564
+ const home = await selectedDirectory(runtime, environment, spec.platform) && await exists(target) ? target
565
+ : checkOnly && await existingCacheDirectory(cache) && await exists(legacy) ? legacy : null;
566
+ if (home) {
529
567
  if (checkOnly || reportCached) progress('validate');
530
- const installed = { home: target, executable: await validatePayload(target, spec, { includePhone }) };
531
- if (!checkOnly) await publishImpl(target, target, spec, { ...options, environment, existing: true });
568
+ const installed = { home, executable: await validatePayload(home, spec, { includePhone }) };
569
+ if (!checkOnly) await publishImpl(target, target, spec, { ...options, environment, cache, existing: true });
532
570
  if (checkOnly || reportCached) progress('ready');
533
571
  return installed;
534
572
  }
535
573
  if (checkOnly) throw notCached();
574
+ const previous = await previousImage(legacy, cache, spec);
575
+ if (previous) {
576
+ progress('publish');
577
+ await admitPublication(environment, spec.platform);
578
+ await assertBaseline(environment, spec.platform);
579
+ if (registrationSource) await registrationSource.validate();
580
+ await publishImpl(previous, target, spec, { ...options, environment, cache, existing: false });
581
+ progress('validate-published');
582
+ const installed = { home: target, executable: await validatePayload(target, spec) };
583
+ progress('ready');
584
+ return installed;
585
+ }
536
586
  // An owned system-temp stage keeps downloads out of product caches. Only the
537
587
  // verified engine below can create/publish a canonical cache installation.
538
588
  await admitPublication(environment, spec.platform);
@@ -545,7 +595,7 @@ async function ensureBinary(spec, { cache = cacheDirectory(), downloadImpl = dow
545
595
  await admitPublication(environment, spec.platform);
546
596
  await assertBaseline(environment, spec.platform);
547
597
  if (registrationSource) await registrationSource.validate();
548
- await publishImpl(payload, target, spec, { ...options, environment, existing: false });
598
+ await publishImpl(payload, target, spec, { ...options, environment, cache, existing: false });
549
599
  // Validate at the final path, including a concurrent publisher's payload.
550
600
  progress('validate-published');
551
601
  const installed = { home: target, executable: await validatePayload(target, spec) };
@@ -630,6 +680,21 @@ async function launchNative(executable, argv, environment, { spawnImpl = spawn,
630
680
  child.once('exit', (code, signal) => { cleanup(); resolve({ code, signal }); });
631
681
  });
632
682
  }
683
+ // Before the first install, --help/--version answer from this package's pin
684
+ // instead of downloading a runtime; the launcher only ever runs that build.
685
+ async function coldAnswer(argv, spec, runtime, { stdout = process.stdout, stderr = process.stderr }) {
686
+ if (argv.length !== 1 || !['--version', '--help', '-h'].includes(argv[0])) return null;
687
+ try { if (await exists(await pinnedTarget(runtime, spec))) return null; } catch { return null; }
688
+ if (argv[0] === '--version') {
689
+ stdout.write(`Stackbite ${spec.version} (${spec.commit})\n`);
690
+ stderr.write('Stackbite: this is the pinned release; its runtime is not installed yet. Run stackbite setup to install it.\n');
691
+ } else {
692
+ stdout.write('usage: stackbite [--help | --version] | stackbite setup [--check] | stackbite [COMMAND ...]\n\n'
693
+ + `The pinned Stackbite ${spec.version} runtime is not installed yet. Run stackbite setup to\n`
694
+ + 'install it (or run any command); stackbite --help then shows the full reference.\n');
695
+ }
696
+ return { code: 0, signal: null };
697
+ }
633
698
  async function main(argv = process.argv.slice(2), options = {}) {
634
699
  const setup = parseSetup(argv);
635
700
  const removing = ['uninstall', 'delete'].includes(argv[0]);
@@ -645,6 +710,8 @@ async function main(argv = process.argv.slice(2), options = {}) {
645
710
  try {
646
711
  if (npm) environment.STACKBITE_NPM_REGISTRATION = JSON.stringify(npm);
647
712
  const spec = releaseSpec(manifest, platform, options.arch || process.arch, options);
713
+ const cold = await coldAnswer(argv, spec, options.runtime || runtimeDirectory(environment, platform), options);
714
+ if (cold) return cold;
648
715
  // First install always verifies everything. Cached TUI/runtime starts defer only
649
716
  // Phone; explicit phone/setup/removal commands retain the full pinned check.
650
717
  let installed;
@@ -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 cache...',
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.105",
4
- "commit": "235f6efe68c2db92ecbda776f27ffc55c28bbac7",
3
+ "version": "0.5.107",
4
+ "commit": "506288e3f01c20dd709905c95eaa46ad04471b17",
5
5
  "repository": "Shinick-Han/stackbite",
6
6
  "platforms": {
7
7
  "win32-x64": {
8
- "asset": "stackbite-0.5.105-windows-x64-235f6efe68c2.zip",
9
- "url": "https://github.com/Shinick-Han/stackbite/releases/download/v0.5.105/stackbite-0.5.105-windows-x64-235f6efe68c2.zip",
10
- "sha256": "58b55180386fec481c87f8f95f3b12b066c7cd552f26c2d97908626648fdfb0f",
11
- "size": 96306228,
12
- "root": "stackbite-0.5.105-windows-x64-235f6efe68c2",
13
- "manifestSha256": "90065bde2ddb6092a5f70b6f2d6c263e84db201847e17bd22498d717f2b8b00a",
14
- "metadataSha256": "fd100b25335a5bd372bca12c30d9077a12571281bc38b98e614cda19aad277b7"
8
+ "asset": "stackbite-0.5.107-windows-x64-506288e3f01c.zip",
9
+ "url": "https://github.com/Shinick-Han/stackbite/releases/download/v0.5.107/stackbite-0.5.107-windows-x64-506288e3f01c.zip",
10
+ "sha256": "6da273fbd3805fff41c3dc1346c39eb7290f4969b30e5f14124a9f9948659701",
11
+ "size": 96379204,
12
+ "root": "stackbite-0.5.107-windows-x64-506288e3f01c",
13
+ "manifestSha256": "fb81b39e291be6bd012b2d1e481f38aa506e38681ea463ddb55dd21bfeab309a",
14
+ "metadataSha256": "871eed20e507d284102373ce50e586919cf0ed3b5a791b6afa9d8cc7df762604"
15
15
  },
16
16
  "linux-x64": {
17
- "asset": "stackbite-0.5.105-linux-x86_64-235f6efe68c2.tar.gz",
18
- "url": "https://github.com/Shinick-Han/stackbite/releases/download/v0.5.105/stackbite-0.5.105-linux-x86_64-235f6efe68c2.tar.gz",
19
- "sha256": "26c730fad6c9288d897df8613aa554f0c6e0551e1247bc1caa247335a7f31b8e",
20
- "size": 77521818,
21
- "root": "stackbite-0.5.105-linux-x86_64-235f6efe68c2",
22
- "manifestSha256": "3f22af3a8ee9cf7bf1bc80a2e01c07ba74b1836059309cb2ac063289e5194a2d",
23
- "metadataSha256": "47feca72039402ff23d697778d24f8b763015a3cda62c1b2e978f7cd8c169dd2"
17
+ "asset": "stackbite-0.5.107-linux-x86_64-506288e3f01c.tar.gz",
18
+ "url": "https://github.com/Shinick-Han/stackbite/releases/download/v0.5.107/stackbite-0.5.107-linux-x86_64-506288e3f01c.tar.gz",
19
+ "sha256": "74e451669bd460b690dc45d4abe9f7bbe6af10d0ab56519c5c4a7c0ff55b2350",
20
+ "size": 77586426,
21
+ "root": "stackbite-0.5.107-linux-x86_64-506288e3f01c",
22
+ "manifestSha256": "a3030ce79fe2b84314064f7a5f5794e744fe8d16cfa223d467a8670cdc957e15",
23
+ "metadataSha256": "9df20d2f675d4e4457a060d07b1b821146d9dcfaa92a532960cb8c6b39239d35"
24
24
  },
25
25
  "darwin-arm64": {
26
- "asset": "stackbite-0.5.105-macos-arm64-235f6efe68c2.tar.gz",
27
- "url": "https://github.com/Shinick-Han/stackbite/releases/download/v0.5.105/stackbite-0.5.105-macos-arm64-235f6efe68c2.tar.gz",
28
- "sha256": "568e50444ae47669b379aefc8b0c45e3d284ea1db456a344701e2a9ad87e4bd8",
29
- "size": 33495757,
30
- "root": "stackbite-0.5.105-macos-arm64-235f6efe68c2",
31
- "manifestSha256": "632b645611bed771f4d8a47f1a22e9a5952b9ac27c6741dff150409256349083",
32
- "metadataSha256": "b411b8de8f99ec000c751278dbbd1d5b58193086e4876343e931ab91e1411277"
26
+ "asset": "stackbite-0.5.107-macos-arm64-506288e3f01c.tar.gz",
27
+ "url": "https://github.com/Shinick-Han/stackbite/releases/download/v0.5.107/stackbite-0.5.107-macos-arm64-506288e3f01c.tar.gz",
28
+ "sha256": "ddf4c956c7657df6bb4684aa3f7a9d677144cca2deb4e425e3a6442b88671262",
29
+ "size": 33557501,
30
+ "root": "stackbite-0.5.107-macos-arm64-506288e3f01c",
31
+ "manifestSha256": "4c2ab4d509a2fb1a48696c820b8c3f8cadea9441817e314a595ab8b08f34cef3",
32
+ "metadataSha256": "e07aedd09b464458bef021518136bbcfb3d9b521f72e1da991b6d1091cccaed3"
33
33
  }
34
34
  }
35
35
  }