@spuentesp/zoanthrope 0.1.4 → 0.1.6

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/README.md CHANGED
@@ -18,7 +18,8 @@ Environment knobs:
18
18
  (defaults to this package's version).
19
19
  - `ZOANTHROPE_CACHE_DIR` — override the binary cache directory.
20
20
 
21
- Prebuilt targets: Linux and macOS, x86_64 and aarch64. Anything else
21
+ Prebuilt targets: Linux and macOS (x86_64, aarch64) and Windows
22
+ (x86_64). Anything else
22
23
  (or a failed download) gets a build-from-source message:
23
24
  `cargo install --path .` from a clone of the repository.
24
25
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spuentesp/zoanthrope",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "mcpName": "io.github.spuentesp/zoanthrope",
5
5
  "description": "Swarm coordination: file claims, agent presence, postboard intentions, filesystem leases, audit. MCP server + hook CLI.",
6
6
  "keywords": [
@@ -26,7 +26,8 @@
26
26
  "README.md"
27
27
  ],
28
28
  "scripts": {
29
- "postinstall": "node scripts/install.js"
29
+ "postinstall": "node scripts/install.js",
30
+ "test": "node --test scripts/install.test.js"
30
31
  },
31
32
  "engines": {
32
33
  "node": ">=18.0.0"
@@ -0,0 +1,65 @@
1
+ 'use strict';
2
+
3
+ // Pure unit tests for the installer's platform mapping — no download,
4
+ // no OS dependency. Run with: node --test scripts/ (or `npm test`).
5
+
6
+ const test = require('node:test');
7
+ const assert = require('node:assert');
8
+ const path = require('path');
9
+
10
+ const { assetName, binaryPath, targetFor, tarExecutable } = require('./runtime');
11
+
12
+ test('targetFor maps win32-x64 to the MSVC target', () => {
13
+ assert.strictEqual(targetFor('win32', 'x64'), 'x86_64-pc-windows-msvc');
14
+ });
15
+
16
+ test('targetFor maps win32-arm64 to the MSVC target', () => {
17
+ assert.strictEqual(targetFor('win32', 'arm64'), 'aarch64-pc-windows-msvc');
18
+ });
19
+
20
+ test('assetName resolves the windows x86_64 tarball the release ships', () => {
21
+ assert.strictEqual(
22
+ assetName(targetFor('win32', 'x64')),
23
+ 'zoanthrope-x86_64-pc-windows-msvc.tar.gz'
24
+ );
25
+ });
26
+
27
+ test('assetName resolves the windows arm64 tarball name', () => {
28
+ assert.strictEqual(
29
+ assetName(targetFor('win32', 'arm64')),
30
+ 'zoanthrope-aarch64-pc-windows-msvc.tar.gz'
31
+ );
32
+ });
33
+
34
+ test('binaryPath uses zoanthrope.exe on win32', () => {
35
+ const target = targetFor('win32', 'x64');
36
+ const file = binaryPath('0.1.5', target, {}, 'win32');
37
+ assert.strictEqual(path.basename(file), 'zoanthrope.exe');
38
+ assert.ok(file.includes(target));
39
+ });
40
+
41
+ test('binaryPath stays extension-less on unix', () => {
42
+ const target = targetFor('linux', 'x64');
43
+ const file = binaryPath('0.1.5', target, {}, 'linux');
44
+ assert.strictEqual(path.basename(file), 'zoanthrope');
45
+ });
46
+
47
+ test('targetFor keeps the pre-existing unix/darwin mappings', () => {
48
+ assert.strictEqual(targetFor('linux', 'x64'), 'x86_64-unknown-linux-gnu');
49
+ assert.strictEqual(targetFor('linux', 'arm64'), 'aarch64-unknown-linux-gnu');
50
+ assert.strictEqual(targetFor('darwin', 'x64'), 'x86_64-apple-darwin');
51
+ assert.strictEqual(targetFor('darwin', 'arm64'), 'aarch64-apple-darwin');
52
+ });
53
+
54
+ test('targetFor rejects platforms with no prebuilt binary', () => {
55
+ assert.throws(() => targetFor('win32', 'ia32'), /No prebuilt zoanthrope binary/);
56
+ assert.throws(() => targetFor('freebsd', 'x64'), /No prebuilt zoanthrope binary/);
57
+ });
58
+
59
+ test('Windows extraction uses System32 bsdtar, not the first tar on PATH', () => {
60
+ const env = { SystemRoot: 'C:\\Windows' };
61
+ assert.strictEqual(tarExecutable('win32', env, () => true), 'C:\\Windows\\System32\\tar.exe');
62
+ assert.strictEqual(tarExecutable('win32', env, () => false), 'tar');
63
+ assert.strictEqual(tarExecutable('win32', {}, () => true), 'tar');
64
+ assert.strictEqual(tarExecutable('linux', env, () => true), 'tar');
65
+ });
@@ -3,8 +3,9 @@
3
3
  // Download-and-verify installer for the zoanthrope binary.
4
4
  // Node builtins only (https, crypto, fs, child_process) — no runtime
5
5
  // dependencies. Release assets are `zoanthrope-{target}.tar.gz` with
6
- // the bare `zoanthrope` binary at the archive root; `SHA256SUMS` in the
7
- // same release carries one checksum line per tarball.
6
+ // the platform binary at the archive root — bare `zoanthrope` on
7
+ // Linux/macOS, `zoanthrope.exe` on Windows; `SHA256SUMS` in the same
8
+ // release carries one checksum line per tarball.
8
9
 
9
10
  const crypto = require('crypto');
10
11
  const fs = require('fs');
@@ -25,6 +26,10 @@ function targetFor(platform = process.platform, arch = process.arch) {
25
26
  'darwin-x64': 'x86_64-apple-darwin',
26
27
  'linux-arm64': 'aarch64-unknown-linux-gnu',
27
28
  'linux-x64': 'x86_64-unknown-linux-gnu',
29
+ // process.arch spells x86-64 `x64`, not `x86_64` — the tarball
30
+ // name comes from the rustc target triple, not the arch.
31
+ 'win32-x64': 'x86_64-pc-windows-msvc',
32
+ 'win32-arm64': 'aarch64-pc-windows-msvc',
28
33
  };
29
34
  const target = targets[`${platform}-${arch}`];
30
35
  if (!target) throw new Error(`No prebuilt zoanthrope binary for ${platform}-${arch}. ${BUILD_FROM_SOURCE}`);
@@ -93,6 +98,21 @@ function verifyBinary(file, version) {
93
98
  }
94
99
  }
95
100
 
101
+
102
+ // On Windows the first `tar` on PATH (Git Bash's GNU tar on the shells
103
+ // GitHub runners use) fails on these archives with `gzip: stdin:
104
+ // unexpected end of file`. Windows ships bsdtar at System32\tar.exe;
105
+ // use it when present — same fix as the lain shim.
106
+ function tarExecutable(platform = process.platform, env = process.env, exists = fs.existsSync) {
107
+ if (platform !== 'win32') return 'tar';
108
+ const root = env.SystemRoot || env.SYSTEMROOT || env.windir || env.WINDIR;
109
+ if (root) {
110
+ const system = path.win32.join(root, 'System32', 'tar.exe');
111
+ if (exists(system)) return system;
112
+ }
113
+ return 'tar';
114
+ }
115
+
96
116
  function download(url, destination, redirects = 5) {
97
117
  return new Promise((resolve, reject) => {
98
118
  const request = https.get(url, { headers: { 'User-Agent': '@spuentesp/zoanthrope' } }, (response) => {
@@ -153,9 +173,13 @@ async function ensureBinary(options = {}) {
153
173
  await fetch(releaseUrl(version, 'SHA256SUMS'), sums);
154
174
  await fetch(releaseUrl(version, asset), archive);
155
175
  verifyArchive(archive, parseChecksum(fs.readFileSync(sums, 'utf8'), asset));
156
- execFileSync('tar', ['xzf', archive, '-C', temporary], { stdio: ['ignore', 'ignore', 'inherit'] });
157
- const extracted = path.join(temporary, 'zoanthrope');
158
- if (!fs.existsSync(extracted)) throw new Error(`${asset} does not contain the zoanthrope executable at its root`);
176
+ execFileSync(tarExecutable(platform), ['xzf', archive, '-C', temporary], { stdio: ['ignore', 'ignore', 'inherit'] });
177
+ // On win32 the archive root carries `zoanthrope.exe` (spawning an
178
+ // extension-less file makes CreateProcess append .exe and miss the
179
+ // real file); everywhere else the bare name.
180
+ const extractedName = platform === 'win32' ? 'zoanthrope.exe' : 'zoanthrope';
181
+ const extracted = path.join(temporary, extractedName);
182
+ if (!fs.existsSync(extracted)) throw new Error(`${asset} does not contain the ${extractedName} executable at its root`);
159
183
  if (platform !== 'win32') fs.chmodSync(extracted, 0o755);
160
184
  verifyBinary(extracted, version);
161
185
  try {
@@ -190,6 +214,7 @@ module.exports = {
190
214
  releaseUrl,
191
215
  selectedVersion,
192
216
  sha256,
217
+ tarExecutable,
193
218
  targetFor,
194
219
  verifyArchive,
195
220
  verifyBinary,