@hanzo/dev 0.6.101 → 0.6.103
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 +6 -6
- package/postinstall.js +51 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/dev",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.103",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Local AI coding agent for your terminal, powered by Hanzo AI",
|
|
6
6
|
"bin": {
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
"prettier": "^3.3.3"
|
|
38
38
|
},
|
|
39
39
|
"optionalDependencies": {
|
|
40
|
-
"@hanzo/dev-darwin-arm64": "0.6.
|
|
41
|
-
"@hanzo/dev-darwin-x64": "0.6.
|
|
42
|
-
"@hanzo/dev-linux-x64-musl": "0.6.
|
|
43
|
-
"@hanzo/dev-linux-arm64-musl": "0.6.
|
|
44
|
-
"@hanzo/dev-win32-x64": "0.6.
|
|
40
|
+
"@hanzo/dev-darwin-arm64": "0.6.103",
|
|
41
|
+
"@hanzo/dev-darwin-x64": "0.6.103",
|
|
42
|
+
"@hanzo/dev-linux-x64-musl": "0.6.103",
|
|
43
|
+
"@hanzo/dev-linux-arm64-musl": "0.6.103",
|
|
44
|
+
"@hanzo/dev-win32-x64": "0.6.103"
|
|
45
45
|
}
|
|
46
46
|
}
|
package/postinstall.js
CHANGED
|
@@ -8,6 +8,7 @@ import { get } from 'https';
|
|
|
8
8
|
import { platform, arch, tmpdir } from 'os';
|
|
9
9
|
import { execSync } from 'child_process';
|
|
10
10
|
import { createRequire } from 'module';
|
|
11
|
+
import { createHash } from 'crypto';
|
|
11
12
|
|
|
12
13
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
14
|
|
|
@@ -102,7 +103,7 @@ async function writeCacheAtomic(srcPath, cachePath) {
|
|
|
102
103
|
try {
|
|
103
104
|
if (existsSync(cachePath)) {
|
|
104
105
|
const ok = validateDownloadedBinary(cachePath).ok;
|
|
105
|
-
if (ok) return;
|
|
106
|
+
if (ok && stamped(cachePath)) return;
|
|
106
107
|
}
|
|
107
108
|
} catch {}
|
|
108
109
|
const dir = dirname(cachePath);
|
|
@@ -116,6 +117,7 @@ async function writeCacheAtomic(srcPath, cachePath) {
|
|
|
116
117
|
try {
|
|
117
118
|
if (existsSync(cachePath)) { try { unlinkSync(cachePath); } catch {} }
|
|
118
119
|
renameSync(tmp, cachePath);
|
|
120
|
+
stamp(cachePath);
|
|
119
121
|
return;
|
|
120
122
|
} catch {
|
|
121
123
|
await new Promise(r => setTimeout(r, delays[i]));
|
|
@@ -259,6 +261,52 @@ async function downloadBinary(url, dest, maxRedirects = 5, maxRetries = 3) {
|
|
|
259
261
|
}
|
|
260
262
|
}
|
|
261
263
|
|
|
264
|
+
// Every release asset ships a sibling `<name>.sha256` written by the release
|
|
265
|
+
// lane. This install downloads a binary and then runs it, so the file magic
|
|
266
|
+
// check below — which only proves the bytes are *an* executable — is not the
|
|
267
|
+
// last word. Read the published digest and compare.
|
|
268
|
+
async function publishedDigest(url, dest) {
|
|
269
|
+
await downloadBinary(`${url}.sha256`, dest);
|
|
270
|
+
// sha256sum format: "<64 hex> <filename>", possibly several lines.
|
|
271
|
+
const line = readFileSync(dest, 'utf8').split('\n').map(l => l.trim()).find(l => /^[0-9a-f]{64}\b/i.test(l));
|
|
272
|
+
try { unlinkSync(dest); } catch {}
|
|
273
|
+
if (!line) throw new Error('no checksum line in published .sha256');
|
|
274
|
+
return line.slice(0, 64).toLowerCase();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function digestOf(p) {
|
|
278
|
+
const h = createHash('sha256');
|
|
279
|
+
h.update(readFileSync(p));
|
|
280
|
+
return h.digest('hex');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Throws unless the archive matches what the release published. A checksum
|
|
284
|
+
// that is skipped when it cannot be fetched is not a checksum, so a missing
|
|
285
|
+
// or unreadable digest fails the install too.
|
|
286
|
+
async function matchPublished(url, archivePath) {
|
|
287
|
+
const want = await publishedDigest(url, stampOf(archivePath));
|
|
288
|
+
const got = digestOf(archivePath);
|
|
289
|
+
if (got !== want) {
|
|
290
|
+
throw new Error(`checksum mismatch for ${url}\n published: ${want}\n received: ${got}`);
|
|
291
|
+
}
|
|
292
|
+
console.log(`Verified sha256 ${want.slice(0, 16)}...`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// A cached binary is trusted only while a stamp beside it still matches its
|
|
296
|
+
// bytes. The stamp is written after the published checksum verified the archive
|
|
297
|
+
// it came from, so a cache hit carries that proof forward instead of resting on
|
|
298
|
+
// file magic alone. The cache is keyed by version number, so without this a bad
|
|
299
|
+
// binary under an already-cached version would survive every reinstall.
|
|
300
|
+
function stampOf(p) { return `${p}.sha256`; }
|
|
301
|
+
|
|
302
|
+
function stamp(p) {
|
|
303
|
+
try { writeFileSync(stampOf(p), digestOf(p)); } catch {}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function stamped(p) {
|
|
307
|
+
try { return readFileSync(stampOf(p), 'utf8').trim() === digestOf(p); } catch { return false; }
|
|
308
|
+
}
|
|
309
|
+
|
|
262
310
|
function validateDownloadedBinary(p) {
|
|
263
311
|
try {
|
|
264
312
|
const st = statSync(p);
|
|
@@ -361,7 +409,7 @@ export async function runPostinstall(options = {}) {
|
|
|
361
409
|
try {
|
|
362
410
|
if (existsSync(cachePath)) {
|
|
363
411
|
const valid = validateDownloadedBinary(cachePath);
|
|
364
|
-
if (valid.ok) {
|
|
412
|
+
if (valid.ok && stamped(cachePath)) {
|
|
365
413
|
// Avoid mirroring into node_modules on Windows or WSL-on-NTFS.
|
|
366
414
|
const wsl = isWSL();
|
|
367
415
|
const binDirReal = (() => { try { return realpathSync(binDir); } catch { return binDir; } })();
|
|
@@ -467,6 +515,7 @@ export async function runPostinstall(options = {}) {
|
|
|
467
515
|
}
|
|
468
516
|
const tmpPath = join(needsIsolation ? safeTempDir : binDir, `.${archiveName}.part`);
|
|
469
517
|
await downloadBinary(downloadUrl, tmpPath);
|
|
518
|
+
await matchPublished(downloadUrl, tmpPath);
|
|
470
519
|
|
|
471
520
|
if (isWin) {
|
|
472
521
|
// Unzip to a temp directory, then move into the per-user cache.
|