@usearete/a4 0.6.0 → 0.7.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.
- package/bin/a4.js +80 -34
- package/bin/a4.test.js +86 -0
- package/package.json +3 -2
package/bin/a4.js
CHANGED
|
@@ -7,54 +7,100 @@ const fs = require("fs");
|
|
|
7
7
|
const binName = process.platform === "win32" ? "a4.exe" : "a4";
|
|
8
8
|
const localBinPath = path.join(__dirname, binName);
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
const RECURSION_SENTINEL = "ARETE_A4_LAUNCHER_ACTIVE";
|
|
11
|
+
|
|
12
|
+
function canonicalPath(candidate) {
|
|
13
|
+
try {
|
|
14
|
+
return fs.realpathSync(candidate);
|
|
15
|
+
} catch {
|
|
16
|
+
return path.resolve(candidate);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function pathCandidates(env = process.env, platform = process.platform) {
|
|
21
|
+
const names = platform === "win32" ? ["a4.exe"] : ["a4"];
|
|
22
|
+
return (env.PATH || "")
|
|
23
|
+
.split(path.delimiter)
|
|
24
|
+
.filter(Boolean)
|
|
25
|
+
.flatMap((directory) => names.map((name) => path.join(directory, name)));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isExecutableFile(candidate, platform = process.platform) {
|
|
29
|
+
try {
|
|
30
|
+
if (!fs.statSync(candidate).isFile()) return false;
|
|
31
|
+
if (platform !== "win32") {
|
|
32
|
+
fs.accessSync(candidate, fs.constants.X_OK);
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Try the concrete postinstall destination first, then every PATH candidate
|
|
41
|
+
// except this JavaScript launcher (including symlinks in node_modules/.bin).
|
|
42
|
+
function getBinaryPath(env = process.env, platform = process.platform) {
|
|
43
|
+
const launcherPaths = new Set(
|
|
44
|
+
[__filename, process.argv[1]].filter(Boolean).map(canonicalPath)
|
|
45
|
+
);
|
|
46
|
+
|
|
12
47
|
// 1. Check for bundled binary (npm postinstall)
|
|
13
|
-
if (
|
|
48
|
+
if (
|
|
49
|
+
isExecutableFile(localBinPath, platform)
|
|
50
|
+
&& !launcherPaths.has(canonicalPath(localBinPath))
|
|
51
|
+
) {
|
|
14
52
|
return localBinPath;
|
|
15
53
|
}
|
|
16
54
|
|
|
17
55
|
// 2. Check system PATH (cargo install, manual install)
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (fs.existsSync(systemBin)) {
|
|
23
|
-
return systemBin;
|
|
24
|
-
}
|
|
56
|
+
for (const candidate of pathCandidates(env, platform)) {
|
|
57
|
+
if (!isExecutableFile(candidate, platform)) continue;
|
|
58
|
+
if (launcherPaths.has(canonicalPath(candidate))) continue;
|
|
59
|
+
return candidate;
|
|
25
60
|
}
|
|
26
61
|
|
|
27
62
|
return null;
|
|
28
63
|
}
|
|
29
64
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"Try reinstalling: npm install @usearete/a4\n" +
|
|
36
|
-
"\n" +
|
|
37
|
-
"If the problem persists, you can install the CLI via Cargo:\n" +
|
|
38
|
-
" cargo install a4-cli"
|
|
39
|
-
);
|
|
40
|
-
process.exit(1);
|
|
41
|
-
}
|
|
65
|
+
function main() {
|
|
66
|
+
if (process.env[RECURSION_SENTINEL] === "1") {
|
|
67
|
+
console.error("Refusing to recursively launch the Arete CLI shim.");
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
42
70
|
|
|
43
|
-
const
|
|
44
|
-
stdio: "inherit",
|
|
45
|
-
env: process.env,
|
|
46
|
-
});
|
|
71
|
+
const binPath = getBinaryPath();
|
|
47
72
|
|
|
48
|
-
if (
|
|
49
|
-
if (result.error.code === "EACCES") {
|
|
73
|
+
if (!binPath) {
|
|
50
74
|
console.error(
|
|
51
|
-
"
|
|
52
|
-
|
|
75
|
+
"Arete CLI binary not found. This usually means the postinstall script failed.\n" +
|
|
76
|
+
"Try reinstalling: npm install @usearete/a4\n" +
|
|
77
|
+
"\n" +
|
|
78
|
+
"If the problem persists, you can install the CLI via Cargo:\n" +
|
|
79
|
+
" cargo install a4-cli"
|
|
53
80
|
);
|
|
54
|
-
|
|
55
|
-
console.error("Failed to run Arete CLI:", result.error.message);
|
|
81
|
+
process.exit(1);
|
|
56
82
|
}
|
|
57
|
-
|
|
83
|
+
|
|
84
|
+
const result = spawnSync(binPath, process.argv.slice(2), {
|
|
85
|
+
stdio: "inherit",
|
|
86
|
+
env: { ...process.env, [RECURSION_SENTINEL]: "1" },
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (result.error) {
|
|
90
|
+
if (result.error.code === "EACCES") {
|
|
91
|
+
console.error(
|
|
92
|
+
"Permission denied. Try running:\n" +
|
|
93
|
+
` chmod +x "${binPath}"`
|
|
94
|
+
);
|
|
95
|
+
} else {
|
|
96
|
+
console.error("Failed to run Arete CLI:", result.error.message);
|
|
97
|
+
}
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
process.exit(result.status ?? 1);
|
|
58
102
|
}
|
|
59
103
|
|
|
60
|
-
|
|
104
|
+
if (require.main === module) main();
|
|
105
|
+
|
|
106
|
+
module.exports = { canonicalPath, getBinaryPath, pathCandidates };
|
package/bin/a4.test.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const assert = require("node:assert/strict");
|
|
2
|
+
const { spawnSync } = require("node:child_process");
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const os = require("node:os");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
const test = require("node:test");
|
|
7
|
+
|
|
8
|
+
const { canonicalPath, getBinaryPath } = require("./a4.js");
|
|
9
|
+
|
|
10
|
+
test("skips a symlink to the launcher and selects the next native binary", (t) => {
|
|
11
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "arete-a4-launcher-"));
|
|
12
|
+
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
13
|
+
const shimDir = path.join(root, "shim");
|
|
14
|
+
const nativeDir = path.join(root, "native");
|
|
15
|
+
fs.mkdirSync(shimDir);
|
|
16
|
+
fs.mkdirSync(nativeDir);
|
|
17
|
+
fs.symlinkSync(__dirname + "/a4.js", path.join(shimDir, "a4"));
|
|
18
|
+
const native = path.join(nativeDir, "a4");
|
|
19
|
+
fs.writeFileSync(native, "#!/bin/sh\nexit 0\n", { mode: 0o755 });
|
|
20
|
+
|
|
21
|
+
const selected = getBinaryPath({ PATH: [shimDir, nativeDir].join(path.delimiter) }, "darwin");
|
|
22
|
+
assert.equal(canonicalPath(selected), canonicalPath(native));
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("returns null when PATH only resolves to the launcher", (t) => {
|
|
26
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "arete-a4-launcher-"));
|
|
27
|
+
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
28
|
+
fs.symlinkSync(__dirname + "/a4.js", path.join(root, "a4"));
|
|
29
|
+
assert.equal(getBinaryPath({ PATH: root }, "darwin"), null);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test(
|
|
33
|
+
"skips non-executable files and directories before a native binary",
|
|
34
|
+
{ skip: process.platform === "win32" },
|
|
35
|
+
(t) => {
|
|
36
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "arete-a4-launcher-"));
|
|
37
|
+
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
38
|
+
const fileDir = path.join(root, "file");
|
|
39
|
+
const directoryDir = path.join(root, "directory");
|
|
40
|
+
const nativeDir = path.join(root, "native");
|
|
41
|
+
fs.mkdirSync(fileDir);
|
|
42
|
+
fs.mkdirSync(directoryDir);
|
|
43
|
+
fs.mkdirSync(nativeDir);
|
|
44
|
+
fs.writeFileSync(path.join(fileDir, "a4"), "not executable\n", { mode: 0o644 });
|
|
45
|
+
fs.mkdirSync(path.join(directoryDir, "a4"));
|
|
46
|
+
const native = path.join(nativeDir, "a4");
|
|
47
|
+
fs.writeFileSync(native, "#!/bin/sh\nexit 0\n", { mode: 0o755 });
|
|
48
|
+
|
|
49
|
+
const selected = getBinaryPath({
|
|
50
|
+
PATH: [fileDir, directoryDir, nativeDir].join(path.delimiter),
|
|
51
|
+
});
|
|
52
|
+
assert.equal(canonicalPath(selected), canonicalPath(native));
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
test("skips Windows command shims and directories before a native binary", (t) => {
|
|
57
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "arete-a4-launcher-"));
|
|
58
|
+
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
59
|
+
const shimDir = path.join(root, "shim");
|
|
60
|
+
const directoryDir = path.join(root, "directory");
|
|
61
|
+
const nativeDir = path.join(root, "native");
|
|
62
|
+
fs.mkdirSync(shimDir);
|
|
63
|
+
fs.mkdirSync(directoryDir);
|
|
64
|
+
fs.mkdirSync(nativeDir);
|
|
65
|
+
fs.writeFileSync(path.join(shimDir, "a4"), "#!/bin/sh\n");
|
|
66
|
+
fs.writeFileSync(path.join(shimDir, "a4.cmd"), "@echo off\n");
|
|
67
|
+
fs.writeFileSync(path.join(shimDir, "a4.bat"), "@echo off\n");
|
|
68
|
+
fs.mkdirSync(path.join(directoryDir, "a4.exe"));
|
|
69
|
+
const native = path.join(nativeDir, "a4.exe");
|
|
70
|
+
fs.writeFileSync(native, "native binary placeholder\n");
|
|
71
|
+
|
|
72
|
+
const selected = getBinaryPath({
|
|
73
|
+
PATH: [shimDir, directoryDir, nativeDir].join(path.delimiter),
|
|
74
|
+
}, "win32");
|
|
75
|
+
assert.equal(canonicalPath(selected), canonicalPath(native));
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("recursion sentinel stops the launcher before resolving another a4", () => {
|
|
79
|
+
const result = spawnSync(process.execPath, [path.join(__dirname, "a4.js")], {
|
|
80
|
+
encoding: "utf8",
|
|
81
|
+
env: { ...process.env, ARETE_A4_LAUNCHER_ACTIVE: "1" },
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
assert.equal(result.status, 1);
|
|
85
|
+
assert.match(result.stderr, /Refusing to recursively launch/);
|
|
86
|
+
});
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usearete/a4",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Arete CLI (a4) - Real-time Solana streaming pipelines",
|
|
5
5
|
"bin": {
|
|
6
6
|
"a4": "bin/a4.js"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
|
-
"postinstall": "node scripts/postinstall.js"
|
|
9
|
+
"postinstall": "node scripts/postinstall.js",
|
|
10
|
+
"test": "node --test bin/*.test.js"
|
|
10
11
|
},
|
|
11
12
|
"repository": {
|
|
12
13
|
"type": "git",
|