@vibecook/truffle 0.4.6 → 0.4.8

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.
@@ -36,7 +36,7 @@ export interface CreateMeshNodeOptions {
36
36
  authKey?: string;
37
37
  /** Whether this node is ephemeral (removed from tailnet on stop). */
38
38
  ephemeral?: boolean;
39
- /** WebSocket listener port (0 = auto). */
39
+ /** WebSocket listener port. Defaults to 9417 when omitted. */
40
40
  wsPort?: number;
41
41
  /**
42
42
  * Auto-open the Tailscale auth URL in the default browser.
@@ -1 +1 @@
1
- {"version":3,"file":"create-mesh-node.d.ts","sourceRoot":"","sources":["../src/create-mesh-node.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAuB,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAG7F;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,qEAAqE;IACrE,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,+EAA+E;IAC/E,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CAC/C;AAmBD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAuDtF"}
1
+ {"version":3,"file":"create-mesh-node.d.ts","sourceRoot":"","sources":["../src/create-mesh-node.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAuB,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAG7F;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,qEAAqE;IACrE,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,+EAA+E;IAC/E,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CAC/C;AAsBD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,QAAQ,CAAC,CA2EtF"}
@@ -1,24 +1,24 @@
1
- import { execSync } from 'node:child_process';
1
+ import { execFile } from 'node:child_process';
2
2
  import { NapiNode } from '@vibecook/truffle-native';
3
3
  import { resolveSidecarPath } from './sidecar.js';
4
4
  /**
5
5
  * Open a URL in the system's default browser.
6
6
  */
7
7
  function defaultOpenUrl(url) {
8
- try {
9
- if (process.platform === 'darwin') {
10
- execSync(`open "${url}"`);
11
- }
12
- else if (process.platform === 'win32') {
13
- execSync(`start "" "${url}"`);
14
- }
15
- else {
16
- execSync(`xdg-open "${url}"`);
17
- }
18
- }
19
- catch {
20
- // Silently fail
21
- }
8
+ // Only ever open http(s) URLs, and pass the URL as an argv element (never
9
+ // through a shell) so it cannot be interpreted as a command. The previous
10
+ // `execSync(`open "${url}"`)` form let shell metacharacters ($(), backticks)
11
+ // in the URL execute arbitrary commands.
12
+ if (!/^https?:\/\//i.test(url))
13
+ return;
14
+ const [cmd, args] = process.platform === 'darwin'
15
+ ? ['open', [url]]
16
+ : process.platform === 'win32'
17
+ ? ['explorer', [url]]
18
+ : ['xdg-open', [url]];
19
+ execFile(cmd, args, () => {
20
+ // Best-effort; ignore failures (no browser, headless environment, etc.).
21
+ });
22
22
  }
23
23
  /**
24
24
  * Create and start a Truffle node with sensible defaults.
@@ -41,6 +41,12 @@ function defaultOpenUrl(url) {
41
41
  */
42
42
  export async function createMeshNode(options) {
43
43
  const { appId, deviceName, deviceId, autoAuth = true, openUrl: customOpenUrl, onAuthRequired, onPeerChange, sidecarPath, stateDir, authKey, ephemeral, wsPort, } = options;
44
+ // Validate appId synchronously (mirrors the Rust-side rule) so a bad value
45
+ // fails here with a clear error instead of asynchronously across the FFI.
46
+ if (!/^[a-z][a-z0-9-]{1,31}$/.test(appId)) {
47
+ throw new Error(`[truffle] Invalid appId ${JSON.stringify(appId)}: must match ^[a-z][a-z0-9-]{1,31}$ ` +
48
+ `(2–32 chars; lowercase letters, digits, hyphens; must start with a letter).`);
49
+ }
44
50
  const resolvedSidecarPath = sidecarPath ?? resolveSidecarPath();
45
51
  const node = new NapiNode();
46
52
  // IMPORTANT: install the auth-required callback BEFORE calling `start()`.
@@ -68,7 +74,20 @@ export async function createMeshNode(options) {
68
74
  ephemeral,
69
75
  wsPort,
70
76
  };
71
- await node.start(config);
77
+ try {
78
+ await node.start(config);
79
+ }
80
+ catch (err) {
81
+ // Best-effort cleanup so a failed start doesn't leak the spawned sidecar
82
+ // process. `stop()` is safe to call even if start() failed early.
83
+ try {
84
+ await node.stop();
85
+ }
86
+ catch {
87
+ // ignore — nothing to clean up, or already torn down
88
+ }
89
+ throw err;
90
+ }
72
91
  // Post-start: forward ongoing peer lifecycle events to the user callback.
73
92
  if (onPeerChange) {
74
93
  node.onPeerChange(onPeerChange);
@@ -1 +1 @@
1
- {"version":3,"file":"create-mesh-node.js","sourceRoot":"","sources":["../src/create-mesh-node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAA2C,MAAM,0BAA0B,CAAC;AAC7F,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAyDlD;;GAEG;AACH,SAAS,cAAc,CAAC,GAAW;IACjC,IAAI,CAAC;QACH,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAClC,QAAQ,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC;QAC5B,CAAC;aAAM,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YACxC,QAAQ,CAAC,aAAa,GAAG,GAAG,CAAC,CAAC;QAChC,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,aAAa,GAAG,GAAG,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,gBAAgB;IAClB,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAA8B;IACjE,MAAM,EACJ,KAAK,EACL,UAAU,EACV,QAAQ,EACR,QAAQ,GAAG,IAAI,EACf,OAAO,EAAE,aAAa,EACtB,cAAc,EACd,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,OAAO,EACP,SAAS,EACT,MAAM,GACP,GAAG,OAAO,CAAC;IAEZ,MAAM,mBAAmB,GAAG,WAAW,IAAI,kBAAkB,EAAE,CAAC;IAEhE,MAAM,IAAI,GAAG,IAAI,QAAQ,EAAE,CAAC;IAE5B,0EAA0E;IAC1E,wEAAwE;IACxE,iEAAiE;IACjE,wEAAwE;IACxE,gEAAgE;IAChE,EAAE;IACF,0EAA0E;IAC1E,4DAA4D;IAC5D,IAAI,CAAC,cAAc,CAAC,CAAC,GAAW,EAAE,EAAE;QAClC,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,aAAa,IAAI,cAAc,CAAC;YAC/C,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,CAAC;QACD,cAAc,EAAE,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAmB;QAC7B,KAAK;QACL,UAAU;QACV,QAAQ;QACR,WAAW,EAAE,mBAAmB;QAChC,QAAQ;QACR,OAAO;QACP,SAAS;QACT,MAAM;KACP,CAAC;IAEF,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAEzB,0EAA0E;IAC1E,IAAI,YAAY,EAAE,CAAC;QACjB,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;IAClC,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"create-mesh-node.js","sourceRoot":"","sources":["../src/create-mesh-node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAA2C,MAAM,0BAA0B,CAAC;AAC7F,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAyDlD;;GAEG;AACH,SAAS,cAAc,CAAC,GAAW;IACjC,0EAA0E;IAC1E,0EAA0E;IAC1E,6EAA6E;IAC7E,yCAAyC;IACzC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO;IACvC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,GACf,OAAO,CAAC,QAAQ,KAAK,QAAQ;QAC3B,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC;QACjB,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO;YAC5B,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC;YACrB,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5B,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE;QACvB,yEAAyE;IAC3E,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAA8B;IACjE,MAAM,EACJ,KAAK,EACL,UAAU,EACV,QAAQ,EACR,QAAQ,GAAG,IAAI,EACf,OAAO,EAAE,aAAa,EACtB,cAAc,EACd,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,OAAO,EACP,SAAS,EACT,MAAM,GACP,GAAG,OAAO,CAAC;IAEZ,2EAA2E;IAC3E,0EAA0E;IAC1E,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CACb,2BAA2B,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,sCAAsC;YACpF,6EAA6E,CAChF,CAAC;IACJ,CAAC;IAED,MAAM,mBAAmB,GAAG,WAAW,IAAI,kBAAkB,EAAE,CAAC;IAEhE,MAAM,IAAI,GAAG,IAAI,QAAQ,EAAE,CAAC;IAE5B,0EAA0E;IAC1E,wEAAwE;IACxE,iEAAiE;IACjE,wEAAwE;IACxE,gEAAgE;IAChE,EAAE;IACF,0EAA0E;IAC1E,4DAA4D;IAC5D,IAAI,CAAC,cAAc,CAAC,CAAC,GAAW,EAAE,EAAE;QAClC,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,aAAa,IAAI,cAAc,CAAC;YAC/C,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,CAAC;QACD,cAAc,EAAE,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAmB;QAC7B,KAAK;QACL,UAAU;QACV,QAAQ;QACR,WAAW,EAAE,mBAAmB;QAChC,QAAQ;QACR,OAAO;QACP,SAAS;QACT,MAAM;KACP,CAAC;IAEF,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,yEAAyE;QACzE,kEAAkE;QAClE,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QACpB,CAAC;QAAC,MAAM,CAAC;YACP,qDAAqD;QACvD,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,0EAA0E;IAC1E,IAAI,YAAY,EAAE,CAAC;QACjB,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;IAClC,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"sidecar.d.ts","sourceRoot":"","sources":["../src/sidecar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAiBH;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CA8B3C"}
1
+ {"version":3,"file":"sidecar.d.ts","sourceRoot":"","sources":["../src/sidecar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAyDH;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CA+B3C"}
package/dist/sidecar.js CHANGED
@@ -8,11 +8,56 @@
8
8
  *
9
9
  * Follows the esbuild pattern for distributing platform-specific binaries.
10
10
  */
11
- import { existsSync } from 'node:fs';
11
+ import { closeSync, existsSync, openSync, readSync } from 'node:fs';
12
12
  import { createRequire } from 'node:module';
13
13
  import { dirname, join } from 'node:path';
14
14
  import { fileURLToPath } from 'node:url';
15
15
  const require = createRequire(import.meta.url);
16
+ /**
17
+ * Best-effort check that a local binary's architecture matches this process.
18
+ *
19
+ * The `bin/` fallback is populated by postinstall for the *current* platform,
20
+ * but a stray/leftover binary of the wrong arch would otherwise be returned and
21
+ * fail later with a cryptic `ENOEXEC`. We sniff the executable's magic bytes and
22
+ * only reject on a *positive* mismatch; when we can't tell, we allow it.
23
+ */
24
+ function binaryMatchesPlatform(path) {
25
+ let fd;
26
+ try {
27
+ fd = openSync(path, 'r');
28
+ }
29
+ catch {
30
+ return false;
31
+ }
32
+ try {
33
+ const buf = Buffer.alloc(20);
34
+ if (readSync(fd, buf, 0, 20, 0) < 20)
35
+ return true; // too small to judge
36
+ // Mach-O 64-bit (macOS): LE magic 0xFEEDFACF, cputype at offset 4.
37
+ if (buf[0] === 0xcf && buf[1] === 0xfa && buf[2] === 0xed && buf[3] === 0xfe) {
38
+ const cpuType = buf.readUInt32LE(4);
39
+ if (process.arch === 'arm64')
40
+ return cpuType === 0x0100000c; // CPU_TYPE_ARM64
41
+ if (process.arch === 'x64')
42
+ return cpuType === 0x01000007; // CPU_TYPE_X86_64
43
+ return true;
44
+ }
45
+ // ELF (Linux): magic 0x7F 'E' 'L' 'F', e_machine at offset 18 (LE).
46
+ if (buf[0] === 0x7f && buf[1] === 0x45 && buf[2] === 0x4c && buf[3] === 0x46) {
47
+ const machine = buf.readUInt16LE(18);
48
+ if (process.arch === 'x64')
49
+ return machine === 0x3e; // EM_X86_64
50
+ if (process.arch === 'arm64')
51
+ return machine === 0xb7; // EM_AARCH64
52
+ return true;
53
+ }
54
+ // PE (Windows) / unknown format — don't block.
55
+ return true;
56
+ }
57
+ finally {
58
+ closeSync(fd);
59
+ }
60
+ }
16
61
  const PLATFORM_PACKAGES = {
17
62
  'darwin-arm64': '@vibecook/truffle-sidecar-darwin-arm64',
18
63
  'darwin-x64': '@vibecook/truffle-sidecar-darwin-x64',
@@ -46,10 +91,11 @@ export function resolveSidecarPath() {
46
91
  // Package not installed — fall through
47
92
  }
48
93
  }
49
- // 2. Try postinstall-downloaded binary (fallback)
94
+ // 2. Try postinstall-downloaded binary (fallback), but only if its arch
95
+ // matches this process — a wrong-arch leftover would fail with ENOEXEC.
50
96
  const __dirname = dirname(fileURLToPath(import.meta.url));
51
97
  const localBin = join(__dirname, '..', 'bin', binName);
52
- if (existsSync(localBin))
98
+ if (existsSync(localBin) && binaryMatchesPlatform(localBin))
53
99
  return localBin;
54
100
  // 3. Nothing found
55
101
  const supported = Object.keys(PLATFORM_PACKAGES).join(', ');
@@ -1 +1 @@
1
- {"version":3,"file":"sidecar.js","sourceRoot":"","sources":["../src/sidecar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C,MAAM,iBAAiB,GAA2B;IAChD,cAAc,EAAE,wCAAwC;IACxD,YAAY,EAAE,sCAAsC;IACpD,WAAW,EAAE,qCAAqC;IAClD,aAAa,EAAE,uCAAuC;IACtD,WAAW,EAAE,qCAAqC;CACnD,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;IAClD,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACvD,MAAM,OAAO,GAAG,eAAe,GAAG,EAAE,CAAC;IAErC,4DAA4D;IAC5D,MAAM,GAAG,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,GAAG,EAAE,CAAC;QACR,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,GAAG,eAAe,CAAC,CAAC;YAC3D,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;YAC3D,IAAI,UAAU,CAAC,OAAO,CAAC;gBAAE,OAAO,OAAO,CAAC;QAC1C,CAAC;QAAC,MAAM,CAAC;YACP,uCAAuC;QACzC,CAAC;IACH,CAAC;IAED,kDAAkD;IAClD,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1D,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IACvD,IAAI,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC;IAE1C,mBAAmB;IACnB,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5D,MAAM,IAAI,KAAK,CACb,oDAAoD,GAAG,MAAM;QAC3D,wBAAwB,SAAS,IAAI;QACrC,mDAAmD;QACnD,gFAAgF,CACnF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"sidecar.js","sourceRoot":"","sources":["../src/sidecar.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibecook/truffle",
3
- "version": "0.4.6",
3
+ "version": "0.4.8",
4
4
  "description": "Mesh networking for local-first apps, built on Tailscale",
5
5
  "license": "MIT",
6
6
  "author": "James Yong",
@@ -40,15 +40,15 @@
40
40
  },
41
41
  "files": [
42
42
  "dist",
43
- "bin",
44
- "scripts"
43
+ "scripts",
44
+ "sidecar-checksums.json"
45
45
  ],
46
46
  "dependencies": {
47
- "@vibecook/truffle-native": "0.4.6"
47
+ "@vibecook/truffle-native": "0.4.8"
48
48
  },
49
49
  "optionalDependencies": {
50
- "@vibecook/truffle-sidecar-darwin-arm64": "^0.3.0",
51
- "@vibecook/truffle-sidecar-darwin-x64": "^0.4.3",
50
+ "@vibecook/truffle-sidecar-darwin-arm64": "^0.4.6",
51
+ "@vibecook/truffle-sidecar-darwin-x64": "^0.4.6",
52
52
  "@vibecook/truffle-sidecar-linux-x64": "^0.4.5",
53
53
  "@vibecook/truffle-sidecar-linux-arm64": "^0.4.5",
54
54
  "@vibecook/truffle-sidecar-win32-x64": "^0.4.5"
@@ -3,19 +3,34 @@
3
3
  /**
4
4
  * Postinstall script for @vibecook/truffle.
5
5
  *
6
- * Verifies the sidecar binary is available via optionalDependencies.
7
- * If missing (e.g., --no-optional), falls back to downloading from GitHub Releases.
6
+ * Primary path: the platform-specific sidecar arrives via optionalDependencies
7
+ * (npm integrity-protected). Fallback (e.g. `--no-optional`): download the
8
+ * binary from GitHub Releases over HTTPS, then verify its SHA-256 against the
9
+ * checksums shipped *inside this package* (`sidecar-checksums.json`). When a
10
+ * checksum is present we fail closed on mismatch; when absent we warn loudly
11
+ * that integrity could not be verified.
8
12
  *
9
- * This follows the esbuild pattern: optionalDependencies as primary, HTTP download as fallback.
13
+ * This replaces the previous unverified `curl | chmod +x` fallback.
10
14
  */
11
15
 
12
16
  /* eslint-disable @typescript-eslint/no-require-imports */
13
17
 
14
- const { existsSync, mkdirSync, chmodSync } = require('fs');
18
+ const {
19
+ existsSync,
20
+ mkdirSync,
21
+ chmodSync,
22
+ createWriteStream,
23
+ createReadStream,
24
+ readFileSync,
25
+ unlinkSync,
26
+ } = require('fs');
15
27
  const { join, dirname } = require('path');
16
- const { execSync } = require('child_process');
28
+ const https = require('https');
29
+ const crypto = require('crypto');
17
30
 
18
31
  const GITHUB_REPO = 'jamesyong-42/truffle';
32
+ const DOWNLOAD_TIMEOUT_MS = 60_000;
33
+ const MAX_ATTEMPTS = 3;
19
34
 
20
35
  const PLATFORM_PACKAGES = {
21
36
  'darwin-arm64': '@vibecook/truffle-sidecar-darwin-arm64',
@@ -34,18 +49,74 @@ const GITHUB_ASSETS = {
34
49
  'win32-x64': 'tsnet-sidecar-windows-amd64.exe',
35
50
  };
36
51
 
37
- function main() {
52
+ /** Download `url` to `dest` over HTTPS, following redirects, with a hard timeout. */
53
+ function httpsDownload(url, dest, timeoutMs) {
54
+ return new Promise((resolve, reject) => {
55
+ const req = https.get(url, { headers: { 'User-Agent': 'truffle-postinstall' } }, (res) => {
56
+ // GitHub release downloads redirect to a CDN — follow 3xx.
57
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
58
+ res.resume();
59
+ httpsDownload(res.headers.location, dest, timeoutMs).then(resolve, reject);
60
+ return;
61
+ }
62
+ if (res.statusCode !== 200) {
63
+ res.resume();
64
+ reject(new Error(`HTTP ${res.statusCode}`));
65
+ return;
66
+ }
67
+ const file = createWriteStream(dest);
68
+ res.pipe(file);
69
+ file.on('finish', () => file.close((err) => (err ? reject(err) : resolve())));
70
+ file.on('error', (err) => {
71
+ try {
72
+ unlinkSync(dest);
73
+ } catch {
74
+ /* ignore */
75
+ }
76
+ reject(err);
77
+ });
78
+ });
79
+ req.setTimeout(timeoutMs, () => req.destroy(new Error(`timed out after ${timeoutMs}ms`)));
80
+ req.on('error', reject);
81
+ });
82
+ }
83
+
84
+ /** Compute the hex SHA-256 of a file. */
85
+ function sha256File(path) {
86
+ return new Promise((resolve, reject) => {
87
+ const hash = crypto.createHash('sha256');
88
+ const s = createReadStream(path);
89
+ s.on('data', (d) => hash.update(d));
90
+ s.on('end', () => resolve(hash.digest('hex')));
91
+ s.on('error', reject);
92
+ });
93
+ }
94
+
95
+ /** Look up the expected SHA-256 for a given version/asset from the shipped checksum map. */
96
+ function loadExpectedChecksum(version, asset) {
97
+ try {
98
+ const map = JSON.parse(readFileSync(join(__dirname, '..', 'sidecar-checksums.json'), 'utf8'));
99
+ const byVersion = map[version] || map[`v${version}`];
100
+ return byVersion ? byVersion[asset] : undefined;
101
+ } catch {
102
+ return undefined;
103
+ }
104
+ }
105
+
106
+ async function main() {
38
107
  const key = `${process.platform}-${process.arch}`;
39
108
  const pkg = PLATFORM_PACKAGES[key];
40
109
  const ext = process.platform === 'win32' ? '.exe' : '';
41
110
  const binName = `sidecar-slim${ext}`;
42
111
 
43
112
  if (!pkg) {
44
- console.warn(`[truffle] No prebuilt sidecar for ${key}. Build from source: cd packages/sidecar-slim && go build`);
113
+ console.warn(
114
+ `[truffle] No prebuilt sidecar for ${key}. Build from source: cd packages/sidecar-slim && go build`,
115
+ );
45
116
  return;
46
117
  }
47
118
 
48
- // Check if the optionalDependency was installed
119
+ // Primary: was the platform optionalDependency installed?
49
120
  try {
50
121
  const pkgJson = require.resolve(`${pkg}/package.json`);
51
122
  const binPath = join(dirname(pkgJson), 'bin', binName);
@@ -66,32 +137,69 @@ function main() {
66
137
  // Not installed — fall through to download
67
138
  }
68
139
 
69
- // Fallback: download from GitHub Releases
140
+ // Fallback: download from GitHub Releases (verified).
70
141
  const asset = GITHUB_ASSETS[key];
71
142
  if (!asset) return;
72
143
 
73
144
  const version = require('../package.json').version;
74
145
  const url = `https://github.com/${GITHUB_REPO}/releases/download/v${version}/${asset}`;
146
+ const expected = loadExpectedChecksum(version, asset);
75
147
 
76
148
  console.log(`[truffle] Sidecar not found via npm. Downloading from GitHub Releases...`);
77
149
 
78
- try {
79
- const binDir = join(__dirname, '..', 'bin');
80
- mkdirSync(binDir, { recursive: true });
81
- const dest = join(binDir, binName);
82
-
83
- execSync(`curl -fsSL "${url}" -o "${dest}"`, { stdio: 'pipe' });
84
-
85
- if (process.platform !== 'win32') {
86
- chmodSync(dest, 0o755);
150
+ const binDir = join(__dirname, '..', 'bin');
151
+ const dest = join(binDir, binName);
152
+
153
+ // Retry only the network download; a checksum mismatch is deterministic and
154
+ // must NOT be retried (it would just re-download the same bad bytes).
155
+ let downloaded = false;
156
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
157
+ try {
158
+ mkdirSync(binDir, { recursive: true });
159
+ await httpsDownload(url, dest, DOWNLOAD_TIMEOUT_MS);
160
+ downloaded = true;
161
+ break;
162
+ } catch (err) {
163
+ console.warn(`[truffle] Download attempt ${attempt}/${MAX_ATTEMPTS} failed: ${err.message}`);
87
164
  }
165
+ }
88
166
 
89
- console.log(`[truffle] Sidecar downloaded successfully.`);
90
- } catch {
167
+ if (!downloaded) {
91
168
  console.warn(`[truffle] Could not download sidecar binary.`);
92
169
  console.warn(`[truffle] Download manually: ${url}`);
93
- console.warn(`[truffle] Or build from source: cd packages/sidecar-slim && go build -o bin/sidecar-slim`);
170
+ console.warn(
171
+ `[truffle] Or build from source: cd packages/sidecar-slim && go build -o bin/sidecar-slim`,
172
+ );
173
+ return;
94
174
  }
175
+
176
+ // Integrity check.
177
+ if (expected) {
178
+ const actual = await sha256File(dest);
179
+ if (actual.toLowerCase() !== expected.toLowerCase()) {
180
+ try {
181
+ unlinkSync(dest);
182
+ } catch {
183
+ /* ignore */
184
+ }
185
+ console.error(
186
+ `[truffle] SECURITY: sidecar checksum mismatch for v${version}/${asset}. ` +
187
+ `Deleted the download and refusing to install it. Expected ${expected}, got ${actual}.`,
188
+ );
189
+ return;
190
+ }
191
+ } else {
192
+ console.warn(
193
+ `[truffle] WARNING: no pinned checksum for v${version}/${asset}; integrity NOT verified. ` +
194
+ `Prefer installing with optionalDependencies enabled.`,
195
+ );
196
+ }
197
+
198
+ if (process.platform !== 'win32') {
199
+ chmodSync(dest, 0o755);
200
+ }
201
+
202
+ console.log(`[truffle] Sidecar downloaded${expected ? ' and verified' : ''}.`);
95
203
  }
96
204
 
97
205
  main();
@@ -0,0 +1,12 @@
1
+ {
2
+ "_comment": "SHA-256 of each GitHub Release sidecar asset, keyed by release version then by asset filename. This file ships inside the npm package (which npm integrity-protects), so it is the trust anchor for the postinstall HTTPS download fallback. When an entry for the installing version/asset is present, postinstall FAILS CLOSED on a checksum mismatch; when absent it warns that integrity was not verified. Populate this at release time, e.g. `shasum -a 256 tsnet-sidecar-*` over the built assets. Keys must match GITHUB_ASSETS in scripts/postinstall.cjs.",
3
+ "_example": {
4
+ "0.4.7": {
5
+ "tsnet-sidecar-darwin-arm64": "<sha256-hex>",
6
+ "tsnet-sidecar-darwin-amd64": "<sha256-hex>",
7
+ "tsnet-sidecar-linux-amd64": "<sha256-hex>",
8
+ "tsnet-sidecar-linux-arm64": "<sha256-hex>",
9
+ "tsnet-sidecar-windows-amd64.exe": "<sha256-hex>"
10
+ }
11
+ }
12
+ }