@axionorbital/tamarind 1.6.0 → 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 CHANGED
@@ -1,5 +1,117 @@
1
- #!/usr/bin/env sh
2
- echo "Error: Tamarind's postinstall did not run, so the platform binary is missing." >&2
3
- echo "This happens with --ignore-scripts or package managers that skip postinstall." >&2
4
- echo "Fix: cd \"\$(npm root -g)/@axionorbital/tamarind\" && node postinstall.mjs" >&2
5
- exit 1
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.0",
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.0",
28
- "@axionorbital/tamarind-linux-arm64": "1.6.0",
29
- "@axionorbital/tamarind-linux-x64": "1.6.0",
30
- "@axionorbital/tamarind-darwin-x64": "1.6.0"
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
- function copyBinary(source, target) {
97
- if (!fs.existsSync(source)) throw new Error(`Binary not found at ${source}`)
98
- fs.mkdirSync(path.dirname(target), { recursive: true })
99
- if (fs.existsSync(target)) fs.unlinkSync(target)
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
- fs.linkSync(source, target)
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
- } finally {
123
- fs.rmSync(temp, { recursive: true, force: true })
107
+ } catch {
108
+ return false
124
109
  }
125
110
  }
126
111
 
127
- function verifyBinary() {
128
- const result = childProcess.spawnSync(targetBinary, ["--version"], {
129
- encoding: "utf8",
130
- stdio: "ignore",
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)