stackbite 0.5.90 → 0.5.92

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.90",
3
+ "version": "0.5.92",
4
4
  "description": "Verified native Stackbite CLI bootstrapper",
5
5
  "license": "UNLICENSED",
6
6
  "engines": {
@@ -82,10 +82,10 @@ function boundedRunner(limit) {
82
82
  }
83
83
  return task => new Promise((resolve, reject) => { queue.push({ task, resolve, reject }); next(); });
84
84
  }
85
- // Bounded concurrency overlaps file reads; every pinned member is still hashed in full.
85
+ // Bounded concurrency overlaps selected file reads; every selected member is hashed in full.
86
86
  const HASH_CONCURRENCY = 8;
87
87
  // Walks the payload; every file or in-root link must resolve to a bounded regular file and pinned members must match.
88
- async function payloadFiles(root, expected, hashes) {
88
+ async function payloadFiles(root, expected, hashes, includePhone) {
89
89
  const actual = new Set(), run = boundedRunner(HASH_CONCURRENCY), checks = [];
90
90
  let total = 0, entries = 0;
91
91
  async function checkHash(relative, resolved) {
@@ -93,6 +93,9 @@ async function payloadFiles(root, expected, hashes) {
93
93
  }
94
94
  async function visit(directoryPath) {
95
95
  for (const name of await fs.readdir(directoryPath)) {
96
+ // Optional Phone is checked at activation. Do not traverse an unused tree,
97
+ // including a missing or redirected tree; selected runtime links cannot use it.
98
+ if (!includePhone && directoryPath === root && name === 'phone') continue;
96
99
  if (++entries > LIMITS.members * 2) fail('Cached release has too many entries');
97
100
  const target = path.join(directoryPath, name), relative = path.relative(root, target).split(path.sep).join('/');
98
101
  memberPath(relative);
@@ -101,6 +104,7 @@ async function payloadFiles(root, expected, hashes) {
101
104
  if (!entry.isFile() && !entry.isSymbolicLink()) fail('Release contains a special file');
102
105
  const resolved = await fs.realpath(target);
103
106
  if (!inside(root, resolved)) fail('Release link escapes the cache root');
107
+ if (!includePhone && path.relative(root, resolved).split(path.sep)[0] === 'phone') fail('Runtime link resolves into optional Phone payload');
104
108
  const resolvedInfo = await fs.lstat(resolved);
105
109
  if (!resolvedInfo.isFile() || resolvedInfo.size > LIMITS.member) fail('Release link does not resolve to a bounded regular file');
106
110
  total += resolvedInfo.size;
@@ -137,8 +141,8 @@ async function checkProductIdentity(root, spec, metadata) {
137
141
  || ((spec.platform === 'darwin' || value.platform !== undefined) && value.platform !== spec.host)) fail('Release product/version/commit identity mismatch');
138
142
  }
139
143
  }
140
- async function nativeExecutable(root, spec) {
141
- const executable = path.join(root, spec.platform === 'win32' ? 'stackbite.exe' : 'stackbite');
144
+ async function nativeExecutable(root, spec, name = 'stackbite') {
145
+ const executable = path.join(root, name + (spec.platform === 'win32' ? '.exe' : ''));
142
146
  const image = await fs.lstat(executable);
143
147
  if (!image.isFile() || image.isSymbolicLink()) fail('Native release executable is missing or a link');
144
148
  const file = await fs.open(executable, 'r');
@@ -153,17 +157,40 @@ async function nativeExecutable(root, spec) {
153
157
  if (spec.platform !== 'win32' && process.platform !== 'win32' && !(image.mode & 0o111)) fail('Native release executable has no execute permission');
154
158
  return executable;
155
159
  }
156
- async function validatePayload(directory, spec) {
160
+ // The release pins the whole payload before this mapping. Old releases keep
161
+ // their original engine; a present native entry is never silently bypassed.
162
+ async function publicExecutable(root, spec, hashes) {
163
+ const engine = await nativeExecutable(root, spec);
164
+ const name = 'stackbite-launcher' + (spec.platform === 'win32' ? '.exe' : '');
165
+ if (!Object.hasOwn(hashes, name)) {
166
+ if (await exists(path.join(root, name))) fail('Native entry is present without a release pin');
167
+ return engine;
168
+ }
169
+ const manifest = JSON.parse((await readSmall(path.join(root, 'manifest.json'))).toString('utf8'));
170
+ const entry = manifest.binaries?.['stackbite-launcher'], info = entry?.build_info;
171
+ const target = { win32: 'x86_64-pc-windows-msvc', linux: 'x86_64-unknown-linux-gnu', darwin: 'aarch64-apple-darwin' }[spec.platform];
172
+ if (manifest.schema !== 'stackbite-rust-terminal-build-v1' || manifest.product !== 'Stackbite'
173
+ || manifest.version !== spec.version || manifest.commit !== spec.commit || manifest.target !== target || manifest.protocol !== 1
174
+ || !entry || Object.keys(entry).sort().join(',') !== 'build_info,path,sha256'
175
+ || entry.path !== name || entry.sha256 !== hashes[name] || manifest.artifacts?.[name]?.sha256 !== hashes[name]
176
+ || manifest.artifacts?.[name]?.kind !== 'binary' || !Number.isSafeInteger(manifest.artifacts?.[name]?.size) || manifest.artifacts[name].size < 1
177
+ || !info || Object.keys(info).sort().join(',') !== 'binary,commit,product,protocol,target,version'
178
+ || info.product !== 'Stackbite' || info.binary !== 'stackbite-launcher' || info.version !== spec.version
179
+ || info.commit !== spec.commit || info.target !== target || info.protocol !== 1) fail('Native entry package identity or pin is invalid');
180
+ return nativeExecutable(root, spec, 'stackbite-launcher');
181
+ }
182
+ async function validatePayload(directory, spec, { includePhone = true } = {}) {
157
183
  const root = await payloadRoot(directory);
158
184
  const { hashes, metadata } = await pinnedDocuments(root, spec);
159
185
  const expected = expectedMembers(hashes);
160
- const actual = await payloadFiles(root, expected, hashes);
186
+ if (!includePhone) for (const name of expected) if (name.startsWith('phone/')) expected.delete(name);
187
+ const actual = await payloadFiles(root, expected, hashes, includePhone);
161
188
  await checkMemberSet(root, spec, hashes, expected, actual);
162
189
  await checkProductIdentity(root, spec, metadata);
163
- return nativeExecutable(root, spec);
190
+ return publicExecutable(root, spec, hashes);
164
191
  }
165
192
  async function ensureBinary(spec, { cache = cacheDirectory(), downloadImpl = download, extractImpl = extractArchive,
166
- checkOnly = false, reportCached = false, progress = () => {} } = {}) {
193
+ checkOnly = false, reportCached = false, includePhone = false, progress = () => {} } = {}) {
167
194
  cache = path.resolve(cache);
168
195
  if (checkOnly) {
169
196
  if (!await existingCacheDirectory(cache)) fail('Pinned runtime is not cached; run stackbite setup.');
@@ -173,7 +200,7 @@ async function ensureBinary(spec, { cache = cacheDirectory(), downloadImpl = dow
173
200
  const target = path.join(cache, `v1-${cacheKey}`);
174
201
  if (await exists(target)) {
175
202
  if (checkOnly || reportCached) progress('validate');
176
- const installed = { home: target, executable: await validatePayload(target, spec) };
203
+ const installed = { home: target, executable: await validatePayload(target, spec, { includePhone }) };
177
204
  if (checkOnly || reportCached) progress('ready');
178
205
  return installed;
179
206
  }
@@ -231,10 +258,12 @@ async function main(argv = process.argv.slice(2), options = {}) {
231
258
  const platform = options.platform || process.platform;
232
259
  const environment = options.environment || process.env;
233
260
  const spec = releaseSpec(manifest, platform, options.arch || process.arch, options);
234
- // Every launch still verifies the full pinned payload; timing only observes it.
261
+ // First install always verifies everything. Cached TUI/runtime starts defer only
262
+ // Phone; explicit phone/setup/removal commands retain the full pinned check.
235
263
  const installed = await timing.phase(environment, 'launcher.ensure', () => ensureBinary(spec, { ...options,
236
264
  cache: options.cache || cacheDirectory(environment, platform),
237
265
  checkOnly: removing || setup?.checkOnly || false, reportCached: Boolean(setup),
266
+ includePhone: removing || Boolean(setup) || argv[0] === 'phone',
238
267
  progress: options.progress || stageReporter(options.stderr || process.stderr) }));
239
268
  if (setup) return { code: 0, signal: null };
240
269
  timing.mark(environment, 'launcher.exec');
@@ -1,35 +1,35 @@
1
1
  {
2
2
  "schema": 1,
3
- "version": "0.5.90",
4
- "commit": "56d4ca6cf772afab8755bb24be9d0b5d7acb9820",
3
+ "version": "0.5.92",
4
+ "commit": "cc476a31b6ff0464f623372fe15fff3ff311d942",
5
5
  "repository": "Shinick-Han/stackbite",
6
6
  "platforms": {
7
7
  "win32-x64": {
8
- "asset": "stackbite-0.5.90-windows-x64-56d4ca6cf772.zip",
9
- "url": "https://github.com/Shinick-Han/stackbite/releases/download/v0.5.90/stackbite-0.5.90-windows-x64-56d4ca6cf772.zip",
10
- "sha256": "e9ba7356d2b4ad2fbcd60e5280b5e66a9d67fdad7b6c3390e151df593b5b5eac",
11
- "size": 94778285,
12
- "root": "stackbite-0.5.90-windows-x64-56d4ca6cf772",
13
- "manifestSha256": "611a5a174ab0188b1f14ceaf267c99f443fa81244c66b636e27ec252a9e49e99",
14
- "metadataSha256": "c382fc7a1defe257d6415ba3085d141a5b83b8dcf1189d09937df7fc2084bf54"
8
+ "asset": "stackbite-0.5.92-windows-x64-cc476a31b6ff.zip",
9
+ "url": "https://github.com/Shinick-Han/stackbite/releases/download/v0.5.92/stackbite-0.5.92-windows-x64-cc476a31b6ff.zip",
10
+ "sha256": "e27e3f909e93965412cfae83a8eea6a2bdbe49126104f452464a7913f2aa03d9",
11
+ "size": 95304318,
12
+ "root": "stackbite-0.5.92-windows-x64-cc476a31b6ff",
13
+ "manifestSha256": "b784ca757ac89eb62c3226f81e71e5467b99259bfa46f1c785fff8f457b8e152",
14
+ "metadataSha256": "5414656b14f65921506b664123e9b0526cdd33f253c27ef122f0e335d6dda736"
15
15
  },
16
16
  "linux-x64": {
17
- "asset": "stackbite-0.5.90-linux-x86_64-56d4ca6cf772.tar.gz",
18
- "url": "https://github.com/Shinick-Han/stackbite/releases/download/v0.5.90/stackbite-0.5.90-linux-x86_64-56d4ca6cf772.tar.gz",
19
- "sha256": "6243c30e23b037e1449db96cf819b7c38cbc0e2b38b2d2ac8ff1b9d9237a83d7",
20
- "size": 74714723,
21
- "root": "stackbite-0.5.90-linux-x86_64-56d4ca6cf772",
22
- "manifestSha256": "a02b4e350870584b16e6bbd544abf7eaa75b24a6314eadad84252ab1b9c0b424",
23
- "metadataSha256": "f238756ab3e80411e7260cf55b224ee64d78bb34ee8c624bb8fa6b8e31582b34"
17
+ "asset": "stackbite-0.5.92-linux-x86_64-cc476a31b6ff.tar.gz",
18
+ "url": "https://github.com/Shinick-Han/stackbite/releases/download/v0.5.92/stackbite-0.5.92-linux-x86_64-cc476a31b6ff.tar.gz",
19
+ "sha256": "15b82d52a8e1b1636fb0d69af27c881259a7b8efe3a808789020adbe0090698c",
20
+ "size": 75399110,
21
+ "root": "stackbite-0.5.92-linux-x86_64-cc476a31b6ff",
22
+ "manifestSha256": "ac8ece054b13ab8b8dd4f4994fa18c6aee7de70e0bf5617346de90a3abc320df",
23
+ "metadataSha256": "61c66bab03bd7ee73ca425c814f02740c1f72c0d47391fe85bbb778a81aaf273"
24
24
  },
25
25
  "darwin-arm64": {
26
- "asset": "stackbite-0.5.90-macos-arm64-56d4ca6cf772.tar.gz",
27
- "url": "https://github.com/Shinick-Han/stackbite/releases/download/v0.5.90/stackbite-0.5.90-macos-arm64-56d4ca6cf772.tar.gz",
28
- "sha256": "3f8b9b59cad56e0b1b6b6f0ab265ce73c5a3b351a3e42e3684b697c86926205a",
29
- "size": 35916960,
30
- "root": "stackbite-0.5.90-macos-arm64-56d4ca6cf772",
31
- "manifestSha256": "ce23b7ea6725e31914a13e516942272a68885ab09798729bb9539c1af3f2a9e2",
32
- "metadataSha256": "845a416ab9f82bc0edcff61c2f971d279f5bb05342e284c45978d9e96891ad23"
26
+ "asset": "stackbite-0.5.92-macos-arm64-cc476a31b6ff.tar.gz",
27
+ "url": "https://github.com/Shinick-Han/stackbite/releases/download/v0.5.92/stackbite-0.5.92-macos-arm64-cc476a31b6ff.tar.gz",
28
+ "sha256": "c12210996e9ad9431f40db11abc57f4e14207285509164577da087b7e368f198",
29
+ "size": 30498979,
30
+ "root": "stackbite-0.5.92-macos-arm64-cc476a31b6ff",
31
+ "manifestSha256": "d4c4d72b78bd456de8fb9623cc8f658d79c0d75724a9250008bf0f2a0df0f025",
32
+ "metadataSha256": "d3d4f7903aab26562b91603c123d4817ebcb8a398e55c8063f756ca06ddf9a37"
33
33
  }
34
34
  }
35
35
  }