@axionorbital/tamarind 1.6.1 → 1.6.2
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/tamarind +117 -5
- package/package.json +6 -5
- package/postinstall.mjs +16 -55
package/bin/tamarind
CHANGED
|
@@ -1,5 +1,117 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Cross-platform launcher for the `tamarind` command (the resolver package's bin).
|
|
3
|
+
//
|
|
4
|
+
// WHY A NODE LAUNCHER (not the native binary directly): npm generates the platform
|
|
5
|
+
// command shims (tamarind / tamarind.cmd / tamarind.ps1) from THIS file's shebang at
|
|
6
|
+
// INSTALL time. A `#!/usr/bin/env sh` stub made npm bake `sh` into tamarind.cmd on
|
|
7
|
+
// Windows → `'"sh"' is not recognized` even after postinstall swapped the file. And a
|
|
8
|
+
// raw .exe can't be a static `bin` target across OSes (the field can't be per-platform,
|
|
9
|
+
// and Windows needs the .exe extension). A Node launcher with `#!/usr/bin/env node`
|
|
10
|
+
// makes npm bake `node` into every shim, and we resolve + spawn the correct platform
|
|
11
|
+
// binary at runtime — so `npm i -g @axionorbital/tamarind` + `tamarind` works uniformly
|
|
12
|
+
// on macOS, Linux (incl. musl/Alpine), and Windows.
|
|
13
|
+
"use strict"
|
|
14
|
+
const { spawnSync } = require("child_process")
|
|
15
|
+
const fs = require("fs")
|
|
16
|
+
const os = require("os")
|
|
17
|
+
const path = require("path")
|
|
18
|
+
|
|
19
|
+
const SCOPE = "@axionorbital/tamarind"
|
|
20
|
+
const platform = { darwin: "darwin", linux: "linux", win32: "windows" }[os.platform()] || os.platform()
|
|
21
|
+
const arch = { x64: "x64", arm64: "arm64", arm: "arm" }[os.arch()] || os.arch()
|
|
22
|
+
const base = `${SCOPE}-${platform}-${arch}`
|
|
23
|
+
// build.ts compiles the Windows target to `tamarind.exe` (Bun appends .exe); unix is `tamarind`.
|
|
24
|
+
const exe = platform === "windows" ? "tamarind.exe" : "tamarind"
|
|
25
|
+
|
|
26
|
+
function isMusl() {
|
|
27
|
+
if (platform !== "linux") return false
|
|
28
|
+
try {
|
|
29
|
+
if (fs.existsSync("/etc/alpine-release")) return true
|
|
30
|
+
} catch {}
|
|
31
|
+
try {
|
|
32
|
+
const r = spawnSync("ldd", ["--version"], { encoding: "utf8" })
|
|
33
|
+
return `${r.stdout || ""}${r.stderr || ""}`.toLowerCase().includes("musl")
|
|
34
|
+
} catch {
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Candidate platform packages, most-preferred first. Mirrors postinstall.tamarind.mjs
|
|
40
|
+
// (baseline = a build for x64 CPUs without AVX2; musl = Alpine/musl libc). We try the
|
|
41
|
+
// full build first and fall back, so a missing baseline/musl variant still resolves.
|
|
42
|
+
function candidates() {
|
|
43
|
+
if (platform === "linux") {
|
|
44
|
+
if (isMusl()) return arch === "x64" ? [`${base}-musl`, `${base}-baseline-musl`, base] : [`${base}-musl`, base]
|
|
45
|
+
return arch === "x64" ? [base, `${base}-baseline`, `${base}-musl`] : [base, `${base}-musl`]
|
|
46
|
+
}
|
|
47
|
+
return arch === "x64" ? [base, `${base}-baseline`] : [base]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Resolve the platform binary. Prefer require.resolve (finds the optionalDependency npm
|
|
51
|
+
// installed alongside this resolver), then fall back to scanning sibling node_modules
|
|
52
|
+
// (covers odd hoisting layouts).
|
|
53
|
+
function resolveBinary() {
|
|
54
|
+
const req = require
|
|
55
|
+
for (const name of candidates()) {
|
|
56
|
+
try {
|
|
57
|
+
const pkgJson = req.resolve(`${name}/package.json`)
|
|
58
|
+
const bin = path.join(path.dirname(pkgJson), "bin", exe)
|
|
59
|
+
if (fs.existsSync(bin)) return bin
|
|
60
|
+
} catch {}
|
|
61
|
+
}
|
|
62
|
+
// Fallback: walk up from here looking for node_modules/<name>/bin/<exe>.
|
|
63
|
+
let dir = __dirname
|
|
64
|
+
for (let i = 0; i < 6; i++) {
|
|
65
|
+
for (const name of candidates()) {
|
|
66
|
+
const bin = path.join(dir, "node_modules", ...name.split("/"), "bin", exe)
|
|
67
|
+
if (fs.existsSync(bin)) return bin
|
|
68
|
+
}
|
|
69
|
+
dir = path.dirname(dir)
|
|
70
|
+
}
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let binary = resolveBinary()
|
|
75
|
+
|
|
76
|
+
// If the platform package isn't present (e.g. installed with --no-optional, or a stale
|
|
77
|
+
// install), try a one-shot best-effort install of the matching package, then re-resolve.
|
|
78
|
+
if (!binary) {
|
|
79
|
+
const version = (() => {
|
|
80
|
+
try {
|
|
81
|
+
return require(path.join(__dirname, "..", "package.json")).version
|
|
82
|
+
} catch {
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
85
|
+
})()
|
|
86
|
+
for (const name of candidates()) {
|
|
87
|
+
const spec = version ? `${name}@${version}` : name
|
|
88
|
+
const r = spawnSync("npm", ["install", "-g", "--no-save", "--loglevel=error", spec], {
|
|
89
|
+
stdio: "ignore",
|
|
90
|
+
windowsHide: true,
|
|
91
|
+
})
|
|
92
|
+
if (r.status === 0) {
|
|
93
|
+
binary = resolveBinary()
|
|
94
|
+
if (binary) break
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!binary) {
|
|
100
|
+
process.stderr.write(
|
|
101
|
+
`tamarind: could not find a binary for your platform (${platform}-${arch}).\n` +
|
|
102
|
+
`Reinstall with: npm i -g @axionorbital/tamarind\n` +
|
|
103
|
+
`or install the platform package directly: ${candidates()
|
|
104
|
+
.map((n) => `npm i -g ${n}`)
|
|
105
|
+
.join("\n ")}\n`,
|
|
106
|
+
)
|
|
107
|
+
process.exit(1)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const result = spawnSync(binary, process.argv.slice(2), { stdio: "inherit", windowsHide: false })
|
|
111
|
+
if (result.error) {
|
|
112
|
+
process.stderr.write(`tamarind: failed to launch (${result.error.message})\n`)
|
|
113
|
+
process.exit(1)
|
|
114
|
+
}
|
|
115
|
+
// Forward the child's signal/exit code faithfully.
|
|
116
|
+
if (result.signal) process.kill(process.pid, result.signal)
|
|
117
|
+
process.exit(result.status == null ? 1 : result.status)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axionorbital/tamarind",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.2",
|
|
4
4
|
"description": "Tamarind — verification-first coding agent for the terminal.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"tamarind": "bin/tamarind"
|
|
@@ -24,10 +24,11 @@
|
|
|
24
24
|
"x64"
|
|
25
25
|
],
|
|
26
26
|
"optionalDependencies": {
|
|
27
|
-
"@axionorbital/tamarind-darwin-arm64": "1.6.
|
|
28
|
-
"@axionorbital/tamarind-
|
|
29
|
-
"@axionorbital/tamarind-linux-
|
|
30
|
-
"@axionorbital/tamarind-
|
|
27
|
+
"@axionorbital/tamarind-darwin-arm64": "1.6.2",
|
|
28
|
+
"@axionorbital/tamarind-windows-x64": "1.6.2",
|
|
29
|
+
"@axionorbital/tamarind-linux-arm64": "1.6.2",
|
|
30
|
+
"@axionorbital/tamarind-linux-x64": "1.6.2",
|
|
31
|
+
"@axionorbital/tamarind-darwin-x64": "1.6.2"
|
|
31
32
|
},
|
|
32
33
|
"license": "UNLICENSED",
|
|
33
34
|
"private": false,
|
package/postinstall.mjs
CHANGED
|
@@ -93,65 +93,26 @@ function resolveBinary(name) {
|
|
|
93
93
|
return binaryPath
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
// Best-effort ONLY. The `tamarind` command is a Node launcher (bin/tamarind) that
|
|
97
|
+
// resolves + spawns the platform binary at RUNTIME (and self-heals a missing platform
|
|
98
|
+
// package on first run), so this postinstall no longer writes bin/tamarind and MUST NOT
|
|
99
|
+
// hard-fail the install — a non-zero exit here is exactly what made `npm i -g
|
|
100
|
+
// @axionorbital/tamarind` abort on Windows. We just confirm the matching platform
|
|
101
|
+
// package resolved (npm installs it via optionalDependencies); if not, we note it and
|
|
102
|
+
// let the launcher fetch it on first run.
|
|
103
|
+
function resolves(name) {
|
|
100
104
|
try {
|
|
101
|
-
|
|
102
|
-
} catch {
|
|
103
|
-
fs.copyFileSync(source, target)
|
|
104
|
-
}
|
|
105
|
-
fs.chmodSync(target, 0o755)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function installPackage(name) {
|
|
109
|
-
const version = packageJson.optionalDependencies?.[name]
|
|
110
|
-
if (!version) return
|
|
111
|
-
const temp = fs.mkdtempSync(path.join(os.tmpdir(), "tamarind-install-"))
|
|
112
|
-
try {
|
|
113
|
-
const result = childProcess.spawnSync(
|
|
114
|
-
"npm",
|
|
115
|
-
["install", "--ignore-scripts", "--no-save", "--loglevel=error", "--prefix", temp, `${name}@${version}`],
|
|
116
|
-
{ stdio: "inherit", windowsHide: true },
|
|
117
|
-
)
|
|
118
|
-
if (result.status !== 0) return
|
|
119
|
-
const packageDir = path.join(temp, "node_modules", name)
|
|
120
|
-
copyBinary(path.join(packageDir, "bin", sourceBinary), targetBinary)
|
|
105
|
+
resolveBinary(name)
|
|
121
106
|
return true
|
|
122
|
-
}
|
|
123
|
-
|
|
107
|
+
} catch {
|
|
108
|
+
return false
|
|
124
109
|
}
|
|
125
110
|
}
|
|
126
111
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
windowsHide: true,
|
|
132
|
-
})
|
|
133
|
-
return result.status === 0
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function main() {
|
|
137
|
-
for (const name of packageNames()) {
|
|
138
|
-
try {
|
|
139
|
-
copyBinary(resolveBinary(name), targetBinary)
|
|
140
|
-
if (verifyBinary()) return
|
|
141
|
-
} catch {
|
|
142
|
-
if (installPackage(name) && verifyBinary()) return
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
throw new Error(
|
|
146
|
-
`Could not find a Tamarind binary for your platform. Try manually installing ${packageNames()
|
|
147
|
-
.map((name) => JSON.stringify(name))
|
|
148
|
-
.join(" or ")}.`,
|
|
112
|
+
const ok = packageNames().some(resolves)
|
|
113
|
+
if (!ok) {
|
|
114
|
+
console.warn(
|
|
115
|
+
"tamarind: platform binary not present yet — the `tamarind` command will fetch it on first run.",
|
|
149
116
|
)
|
|
150
117
|
}
|
|
151
|
-
|
|
152
|
-
try {
|
|
153
|
-
main()
|
|
154
|
-
} catch (error) {
|
|
155
|
-
console.error(error.message)
|
|
156
|
-
process.exit(1)
|
|
157
|
-
}
|
|
118
|
+
process.exit(0)
|