agnix 0.47.0 → 0.48.1

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.
Files changed (2) hide show
  1. package/install.js +87 -27
  2. package/package.json +1 -1
package/install.js CHANGED
@@ -3,7 +3,7 @@
3
3
  const https = require('https');
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
- const { execSync } = require('child_process');
6
+ const { execSync, spawnSync } = require('child_process');
7
7
  const os = require('os');
8
8
  const crypto = require('crypto');
9
9
 
@@ -35,12 +35,10 @@ function restoreWrapperBackup(wrapperPath, wrapperBackup, installCompleted) {
35
35
  }
36
36
 
37
37
  /**
38
- * Get platform-specific asset name and binary name.
38
+ * Get platform-specific asset name and binary name. Platform and arch are
39
+ * parameters so tests can cover mappings other than the host's.
39
40
  */
40
- function getPlatformInfo() {
41
- const platform = os.platform();
42
- const arch = os.arch();
43
-
41
+ function getPlatformInfo(platform = os.platform(), arch = os.arch()) {
44
42
  const mapping = {
45
43
  'darwin-arm64': {
46
44
  asset: 'agnix-aarch64-apple-darwin.tar.gz',
@@ -55,6 +53,9 @@ function getPlatformInfo() {
55
53
  },
56
54
  'linux-x64': {
57
55
  asset: 'agnix-x86_64-unknown-linux-gnu.tar.gz',
56
+ // Statically linked, so it runs on hosts whose glibc is older than the
57
+ // one the gnu build was linked against (issue #1371).
58
+ fallbackAsset: 'agnix-x86_64-unknown-linux-musl.tar.gz',
58
59
  extractedName: 'agnix',
59
60
  binary: 'agnix-binary',
60
61
  },
@@ -229,11 +230,58 @@ async function verifyChecksum(archivePath, checksumUrl, deps = { downloadFile })
229
230
  }
230
231
  }
231
232
 
233
+ /**
234
+ * Whether the installed binary can actually execute on this host. A binary
235
+ * built against a newer glibc than the host provides fails in the dynamic
236
+ * loader, which npm otherwise only surfaces on the user's first real run.
237
+ */
238
+ function binaryRuns(binaryPath, deps = { spawnSync }) {
239
+ try {
240
+ const result = deps.spawnSync(binaryPath, ['--version'], { stdio: 'ignore' });
241
+ return !result.error && result.status === 0;
242
+ } catch (_) {
243
+ return false;
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Download one release asset, verify it, and place the extracted binary at
249
+ * `binaryPath`. Removes the archive whether or not the download succeeded.
250
+ */
251
+ async function fetchBinary({ asset, extractedName, binDir, binaryPath }) {
252
+ const downloadUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${VERSION}/${asset}`;
253
+ const archivePath = path.join(binDir, asset);
254
+ const extractedPath = path.join(binDir, extractedName);
255
+
256
+ try {
257
+ await downloadFile(downloadUrl, archivePath);
258
+ await verifyChecksum(archivePath, `${downloadUrl}.sha256`);
259
+ console.log('Extracting...');
260
+ extractArchive(archivePath, binDir);
261
+
262
+ // Rename extracted binary to avoid conflict with wrapper script
263
+ if (fs.existsSync(extractedPath) && extractedPath !== binaryPath) {
264
+ fs.renameSync(extractedPath, binaryPath);
265
+ }
266
+
267
+ // Make binary executable on Unix
268
+ if (os.platform() !== 'win32') {
269
+ fs.chmodSync(binaryPath, 0o755);
270
+ }
271
+
272
+ if (!fs.existsSync(binaryPath)) {
273
+ throw new Error('Binary not found after extraction');
274
+ }
275
+ } finally {
276
+ // Best-effort cleanup so a failed install leaves no stale archive.
277
+ removeIfExists(archivePath);
278
+ }
279
+ }
280
+
232
281
  async function main() {
233
282
  const platformInfo = getPlatformInfo();
234
283
  const binDir = path.join(__dirname, 'bin');
235
284
  const binaryPath = path.join(binDir, platformInfo.binary);
236
- const extractedPath = path.join(binDir, platformInfo.extractedName);
237
285
  const wrapperPath = path.join(binDir, 'agnix');
238
286
  const wrapperBackup = path.join(binDir, 'agnix.backup');
239
287
  let installCompleted = false;
@@ -249,9 +297,6 @@ async function main() {
249
297
  fs.mkdirSync(binDir, { recursive: true });
250
298
  }
251
299
 
252
- const downloadUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${VERSION}/${platformInfo.asset}`;
253
- const archivePath = path.join(binDir, platformInfo.asset);
254
-
255
300
  console.log(`Downloading agnix v${VERSION} for ${os.platform()}-${os.arch()}...`);
256
301
 
257
302
  try {
@@ -260,14 +305,30 @@ async function main() {
260
305
  fs.copyFileSync(wrapperPath, wrapperBackup);
261
306
  }
262
307
 
263
- await downloadFile(downloadUrl, archivePath);
264
- await verifyChecksum(archivePath, `${downloadUrl}.sha256`);
265
- console.log('Extracting...');
266
- extractArchive(archivePath, binDir);
308
+ await fetchBinary({
309
+ asset: platformInfo.asset,
310
+ extractedName: platformInfo.extractedName,
311
+ binDir,
312
+ binaryPath,
313
+ });
267
314
 
268
- // Rename extracted binary to avoid conflict with wrapper script
269
- if (fs.existsSync(extractedPath) && extractedPath !== binaryPath) {
270
- fs.renameSync(extractedPath, binaryPath);
315
+ let runs = binaryRuns(binaryPath);
316
+
317
+ // The default Linux asset is glibc-linked, so on a host older than the
318
+ // build's glibc floor it cannot load at all. Retry with the static build
319
+ // rather than leaving behind a binary that dies in the dynamic loader.
320
+ if (!runs && platformInfo.fallbackAsset) {
321
+ console.log(
322
+ `Downloaded binary cannot run on this host, retrying with ${platformInfo.fallbackAsset}...`
323
+ );
324
+ removeIfExists(binaryPath);
325
+ await fetchBinary({
326
+ asset: platformInfo.fallbackAsset,
327
+ extractedName: platformInfo.extractedName,
328
+ binDir,
329
+ binaryPath,
330
+ });
331
+ runs = binaryRuns(binaryPath);
271
332
  }
272
333
 
273
334
  // Restore wrapper script if it was backed up
@@ -275,14 +336,11 @@ async function main() {
275
336
  fs.renameSync(wrapperBackup, wrapperPath);
276
337
  }
277
338
 
278
- // Make binary executable on Unix
279
- if (os.platform() !== 'win32') {
280
- fs.chmodSync(binaryPath, 0o755);
281
- }
282
-
283
- // Verify binary exists
284
- if (!fs.existsSync(binaryPath)) {
285
- throw new Error('Binary not found after extraction');
339
+ if (!runs) {
340
+ // Keep the install itself successful: some sandboxes forbid spawning
341
+ // during postinstall, and the binary is on disk either way.
342
+ console.warn('Warning: the installed agnix binary did not run on this host.');
343
+ console.warn('If `agnix --version` fails, build from source with: cargo install agnix-cli');
286
344
  }
287
345
 
288
346
  installCompleted = true;
@@ -294,8 +352,8 @@ async function main() {
294
352
  console.error(' cargo install agnix-cli');
295
353
  process.exitCode = 1;
296
354
  } finally {
297
- // Best-effort cleanup so a failed install leaves no stale archive or backup.
298
- removeIfExists(archivePath);
355
+ // Best-effort cleanup so a failed install leaves no stale backup. Archives
356
+ // are removed by fetchBinary itself.
299
357
  try {
300
358
  restoreWrapperBackup(wrapperPath, wrapperBackup, installCompleted);
301
359
  } catch (_) {
@@ -309,8 +367,10 @@ if (require.main === module) {
309
367
  }
310
368
 
311
369
  module.exports = {
370
+ binaryRuns,
312
371
  downloadFile,
313
372
  extractArchive,
373
+ fetchBinary,
314
374
  getPlatformInfo,
315
375
  main,
316
376
  parseSha256Sidecar,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agnix",
3
- "version": "0.47.0",
3
+ "version": "0.48.1",
4
4
  "description": "Linter for AI agent configurations. Validates SKILL.md, CLAUDE.md, hooks, MCP, and more.",
5
5
  "keywords": [
6
6
  "agent",