@synsci/openscience 1.3.4 → 1.3.5-test.3.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/openscience CHANGED
@@ -5,7 +5,93 @@ const fs = require("fs")
5
5
  const path = require("path")
6
6
  const os = require("os")
7
7
 
8
+ const MIN_LINUX_KERNEL = { major: 5, minor: 1 }
9
+
10
+ function parseKernelVersion(release) {
11
+ const match = /^(\d+)\.(\d+)/.exec(release)
12
+ if (!match) return undefined
13
+ return { major: Number(match[1]), minor: Number(match[2]) }
14
+ }
15
+
16
+ function linuxKernelProblem(platform = os.platform(), release = os.release()) {
17
+ if (platform !== "linux") return undefined
18
+ const version = parseKernelVersion(release)
19
+ if (!version) return undefined
20
+ if (
21
+ version.major > MIN_LINUX_KERNEL.major ||
22
+ (version.major === MIN_LINUX_KERNEL.major && version.minor >= MIN_LINUX_KERNEL.minor)
23
+ ) {
24
+ return undefined
25
+ }
26
+ return `Linux kernel ${release} is unsupported. OpenScience's bundled runtime requires kernel 5.1 or newer.`
27
+ }
28
+
29
+ function detectMusl(platform = os.platform()) {
30
+ if (platform !== "linux") return false
31
+ try {
32
+ return fs.readdirSync("/lib").some((entry) => entry.startsWith("ld-musl-"))
33
+ } catch {
34
+ return false
35
+ }
36
+ }
37
+
38
+ function expectedPlatformPackages(platform, arch, musl) {
39
+ const suffix = musl ? "-musl" : ""
40
+ const name = `openscience-${platform}-${arch}${suffix}`
41
+ return [`@synsci/${name}`, name]
42
+ }
43
+
44
+ function pageSize() {
45
+ if (os.platform() !== "linux") return undefined
46
+ const result = childProcess.spawnSync("getconf", ["PAGESIZE"], { encoding: "utf8" })
47
+ return result.status === 0 ? result.stdout.trim() || undefined : undefined
48
+ }
49
+
50
+ function linuxArm64PageSizeProblem(platform, arch, detectedPageSize) {
51
+ if (platform !== "linux" || arch !== "arm64" || detectedPageSize === undefined) return undefined
52
+ const bytes = Number.parseInt(String(detectedPageSize), 10)
53
+ if (!Number.isFinite(bytes) || bytes === 4096) return undefined
54
+ return [
55
+ `Linux ARM64 page size ${bytes} is unsupported by OpenScience's Bun-compiled executable.`,
56
+ "Use a kernel or VM configured with 4 KB pages.",
57
+ "Upstream status: https://github.com/oven-sh/bun/issues/17627",
58
+ ].join(" ")
59
+ }
60
+
61
+ function runtimeDescription(platform, arch, musl) {
62
+ const details = [`${platform}-${arch}`]
63
+ if (platform === "linux") {
64
+ details.push(musl ? "musl" : "glibc", `kernel ${os.release()}`)
65
+ const detectedPageSize = pageSize()
66
+ if (detectedPageSize) details.push(`page size ${detectedPageSize}`)
67
+ }
68
+ details.push(`node ${process.version}`)
69
+ return details.join(", ")
70
+ }
71
+
72
+ function exitCodeForResult(result) {
73
+ if (typeof result.status === "number") return result.status
74
+ if (result.signal) return 128 + (os.constants.signals[result.signal] || 0)
75
+ return 1
76
+ }
77
+
8
78
  function run(target) {
79
+ const kernelProblem = linuxKernelProblem()
80
+ if (kernelProblem) {
81
+ console.error(kernelProblem)
82
+ console.error(
83
+ "Upgrade the host kernel or run OpenScience on a newer VM. CentOS 7's stock 3.10 kernel is not supported.",
84
+ )
85
+ process.exit(1)
86
+ }
87
+
88
+ const pageSizeProblem = linuxArm64PageSizeProblem(os.platform(), os.arch(), pageSize())
89
+ if (pageSizeProblem) {
90
+ console.error(pageSizeProblem)
91
+ console.error(`Detected runtime: ${runtimeDescription(os.platform(), os.arch(), detectMusl())}`)
92
+ process.exit(1)
93
+ }
94
+
9
95
  // Clean up files that confuse Bun compiled binaries.
10
96
  // Install dir stays as ~/.openscience/ until the path-migration follow-up PR.
11
97
  const openscienceDir = path.join(os.homedir(), ".openscience")
@@ -28,11 +114,20 @@ function run(target) {
28
114
  stdio: "inherit",
29
115
  })
30
116
  if (result.error) {
31
- console.error(result.error.message)
117
+ console.error(`Failed to launch OpenScience binary at ${target}: ${result.error.message}`)
118
+ console.error(`Detected runtime: ${runtimeDescription(os.platform(), os.arch(), detectMusl())}`)
32
119
  process.exit(1)
33
120
  }
34
- const code = typeof result.status === "number" ? result.status : 0
35
- process.exit(code)
121
+ if (result.signal) {
122
+ console.error(
123
+ `openscience was terminated by ${result.signal}. The prebuilt native binary may be ` +
124
+ `incompatible with this host (platform ${os.platform()}, arch ${os.arch()}). ` +
125
+ `Some ARM64 hosts (e.g. 64KB page-size kernels) cannot run the prebuilt binary. ` +
126
+ `Try reinstalling, set OPENSCIENCE_BIN_PATH to a compatible binary, or report at ` +
127
+ `https://github.com/synthetic-sciences/openscience/issues`,
128
+ )
129
+ }
130
+ process.exit(exitCodeForResult(result))
36
131
  }
37
132
 
38
133
  // Helper: check if a path points to a real binary (not this wrapper script)
@@ -50,89 +145,104 @@ function isBinary(p) {
50
145
  }
51
146
  }
52
147
 
53
- // 1. Check OPENSCIENCE_BIN_PATH env var
54
- const envPath = process.env.OPENSCIENCE_BIN_PATH
55
- if (envPath && isBinary(envPath)) {
56
- run(envPath)
57
- }
58
-
59
- const binary = os.platform() === "win32" ? "openscience.exe" : "openscience"
60
- const scriptPath = fs.realpathSync(__filename)
61
- const scriptDir = path.dirname(scriptPath)
62
-
63
- const platformMap = { darwin: "darwin", linux: "linux", win32: "windows" }
64
- const archMap = { x64: "x64", arm64: "arm64", arm: "arm" }
65
- const platform = platformMap[os.platform()] || os.platform()
66
- const arch = archMap[os.arch()] || os.arch()
67
- const base = "openscience-" + platform + "-" + arch
68
- const scopedBase = "openscience-" + platform + "-" + arch
69
-
70
148
  // Rank matching platform packages instead of trusting readdir order (which
71
149
  // is filesystem-dependent): right libc first, exact name before suffixed
72
150
  // variants like -baseline, wrong libc only as a last resort.
73
- const musl = (() => {
74
- if (platform !== "linux") return false
75
- try {
76
- return fs.readdirSync("/lib").some((f) => f.startsWith("ld-musl-"))
77
- } catch {
78
- return false
79
- }
80
- })()
81
- function variantRank(prefix, entry) {
151
+ function variantRank(prefix, entry, preferMusl) {
82
152
  const entryMusl = entry.includes("-musl")
83
- if (entryMusl !== musl) return 0
153
+ if (entryMusl !== preferMusl) return 0
84
154
  return entry === prefix || entry === prefix + "-musl" ? 2 : 1
85
155
  }
86
- function matchingVariants(prefix, entries) {
87
- return entries.filter((e) => e.startsWith(prefix)).sort((a, b) => variantRank(prefix, b) - variantRank(prefix, a))
156
+ function matchingVariants(prefix, entries, preferMusl) {
157
+ return entries
158
+ .filter((entry) => entry.startsWith(prefix))
159
+ .sort((a, b) => variantRank(prefix, b, preferMusl) - variantRank(prefix, a, preferMusl))
88
160
  }
89
161
 
90
- // 2. Search this install's node_modules for the matching platform package.
91
- // This must come before ~/.openscience or Homebrew fallbacks; otherwise an npm
92
- // install can accidentally launch an older globally-installed binary.
93
- let current = scriptDir
94
- while (true) {
95
- const modules = path.join(current, "node_modules")
96
- if (fs.existsSync(modules)) {
97
- try {
98
- const entries = fs.readdirSync(modules)
99
- for (const entry of matchingVariants(base, entries)) {
100
- const candidate = path.join(modules, entry, "bin", binary)
101
- if (isBinary(candidate)) run(candidate)
102
- }
103
- // Check scoped packages (@synsci/openscience-darwin-arm64)
104
- const scoped = path.join(modules, "@synsci")
105
- if (fs.existsSync(scoped)) {
106
- const scopedEntries = fs.readdirSync(scoped)
107
- for (const entry of matchingVariants(scopedBase, scopedEntries)) {
108
- const candidate = path.join(scoped, entry, "bin", binary)
162
+ function main() {
163
+ const envPath = process.env.OPENSCIENCE_BIN_PATH
164
+ if (envPath && isBinary(envPath)) {
165
+ run(envPath)
166
+ }
167
+
168
+ const binary = os.platform() === "win32" ? "openscience.exe" : "openscience"
169
+ const scriptPath = fs.realpathSync(__filename)
170
+ const scriptDir = path.dirname(scriptPath)
171
+
172
+ const platformMap = { darwin: "darwin", linux: "linux", win32: "windows" }
173
+ const archMap = { x64: "x64", arm64: "arm64", arm: "arm" }
174
+ const platform = platformMap[os.platform()] || os.platform()
175
+ const arch = archMap[os.arch()] || os.arch()
176
+ const base = "openscience-" + platform + "-" + arch
177
+ const scopedBase = "openscience-" + platform + "-" + arch
178
+ const musl = detectMusl()
179
+
180
+ // Search this install's node_modules for the matching platform package.
181
+ // This must come before ~/.openscience or Homebrew fallbacks; otherwise an npm
182
+ // install can accidentally launch an older globally-installed binary.
183
+ let current = scriptDir
184
+ while (true) {
185
+ const modules = path.join(current, "node_modules")
186
+ if (fs.existsSync(modules)) {
187
+ try {
188
+ const entries = fs.readdirSync(modules)
189
+ for (const entry of matchingVariants(base, entries, musl)) {
190
+ const candidate = path.join(modules, entry, "bin", binary)
109
191
  if (isBinary(candidate)) run(candidate)
110
192
  }
111
- }
112
- } catch {}
193
+ // Check scoped packages (@synsci/openscience-darwin-arm64)
194
+ const scoped = path.join(modules, "@synsci")
195
+ if (fs.existsSync(scoped)) {
196
+ const scopedEntries = fs.readdirSync(scoped)
197
+ for (const entry of matchingVariants(scopedBase, scopedEntries, musl)) {
198
+ const candidate = path.join(scoped, entry, "bin", binary)
199
+ if (isBinary(candidate)) run(candidate)
200
+ }
201
+ }
202
+ } catch {}
203
+ }
204
+ const parent = path.dirname(current)
205
+ if (parent === current) break
206
+ current = parent
113
207
  }
114
- const parent = path.dirname(current)
115
- if (parent === current) break
116
- current = parent
117
- }
118
208
 
119
- // 3. Check ~/.openscience/bin (curl installer fallback; dir renamed later)
120
- const homeBin = path.join(os.homedir(), ".openscience", "bin", binary)
121
- if (isBinary(homeBin)) {
122
- run(homeBin)
123
- }
209
+ // Check ~/.openscience/bin (curl installer fallback; dir renamed later)
210
+ const homeBin = path.join(os.homedir(), ".openscience", "bin", binary)
211
+ if (isBinary(homeBin)) {
212
+ run(homeBin)
213
+ }
214
+
215
+ // Check /opt/homebrew/bin (macOS)
216
+ if (os.platform() === "darwin" && isBinary("/opt/homebrew/bin/openscience")) {
217
+ run("/opt/homebrew/bin/openscience")
218
+ }
219
+
220
+ // Check /usr/local/bin
221
+ if (isBinary("/usr/local/bin/openscience")) {
222
+ run("/usr/local/bin/openscience")
223
+ }
124
224
 
125
- // 4. Check /opt/homebrew/bin (macOS)
126
- if (os.platform() === "darwin" && isBinary("/opt/homebrew/bin/openscience")) {
127
- run("/opt/homebrew/bin/openscience")
225
+ const expectedPackages = expectedPlatformPackages(platform, arch, musl)
226
+ console.error(`OpenScience binary not found for ${platform}-${arch}.`)
227
+ console.error(`Detected runtime: ${runtimeDescription(platform, arch, musl)}`)
228
+ console.error(`Expected npm package: ${expectedPackages[0]}`)
229
+ console.error("Check npm's platform selection with:")
230
+ console.error(" npm config get cpu")
231
+ console.error(` npm ls -g @synsci/openscience ${expectedPackages[0]} --depth=0`)
232
+ console.error("Reinstall with: npm install -g @synsci/openscience")
233
+ console.error("Or set OPENSCIENCE_BIN_PATH to a native OpenScience binary.")
234
+ process.exit(1)
128
235
  }
129
236
 
130
- // 5. Check /usr/local/bin
131
- if (isBinary("/usr/local/bin/openscience")) {
132
- run("/usr/local/bin/openscience")
237
+ module.exports = {
238
+ detectMusl,
239
+ exitCodeForResult,
240
+ expectedPlatformPackages,
241
+ linuxArm64PageSizeProblem,
242
+ linuxKernelProblem,
243
+ matchingVariants,
244
+ parseKernelVersion,
245
+ variantRank,
133
246
  }
134
247
 
135
- console.error("openscience binary not found.")
136
- console.error("Install with: curl -fsSL https://openscience.sh/install | bash")
137
- console.error("Or set OPENSCIENCE_BIN_PATH environment variable.")
138
- process.exit(1)
248
+ if (require.main === module) main()
package/package.json CHANGED
@@ -7,22 +7,23 @@
7
7
  "preinstall": "node ./preinstall.mjs || exit 0",
8
8
  "postinstall": "bun ./postinstall.mjs || node ./postinstall.mjs"
9
9
  },
10
- "version": "1.3.4",
10
+ "version": "1.3.5-test.3.1",
11
11
  "repository": {
12
12
  "type": "git",
13
13
  "url": "git+https://github.com/synthetic-sciences/openscience.git"
14
14
  },
15
15
  "optionalDependencies": {
16
- "@synsci/openscience-linux-x64-baseline": "1.3.4",
17
- "@synsci/openscience-linux-arm64": "1.3.4",
18
- "@synsci/openscience-linux-x64": "1.3.4",
19
- "@synsci/openscience-windows-x64": "1.3.4",
20
- "@synsci/openscience-darwin-arm64": "1.3.4",
21
- "@synsci/openscience-darwin-x64": "1.3.4",
22
- "@synsci/openscience-linux-x64-baseline-musl": "1.3.4",
23
- "@synsci/openscience-linux-x64-musl": "1.3.4",
24
- "@synsci/openscience-linux-arm64-musl": "1.3.4",
25
- "@synsci/openscience-windows-x64-baseline": "1.3.4",
26
- "@synsci/openscience-darwin-x64-baseline": "1.3.4"
16
+ "@synsci/atlas": "^0.13.2",
17
+ "@synsci/openscience-linux-x64-baseline-musl": "1.3.5-test.3.1",
18
+ "@synsci/openscience-windows-x64": "1.3.5-test.3.1",
19
+ "@synsci/openscience-linux-x64": "1.3.5-test.3.1",
20
+ "@synsci/openscience-darwin-arm64": "1.3.5-test.3.1",
21
+ "@synsci/openscience-windows-x64-baseline": "1.3.5-test.3.1",
22
+ "@synsci/openscience-darwin-x64-baseline": "1.3.5-test.3.1",
23
+ "@synsci/openscience-linux-x64-musl": "1.3.5-test.3.1",
24
+ "@synsci/openscience-linux-x64-baseline": "1.3.5-test.3.1",
25
+ "@synsci/openscience-darwin-x64": "1.3.5-test.3.1",
26
+ "@synsci/openscience-linux-arm64-musl": "1.3.5-test.3.1",
27
+ "@synsci/openscience-linux-arm64": "1.3.5-test.3.1"
27
28
  }
28
29
  }
package/postinstall.mjs CHANGED
@@ -3,6 +3,7 @@
3
3
  import fs from "fs"
4
4
  import path from "path"
5
5
  import os from "os"
6
+ import childProcess from "child_process"
6
7
  import { fileURLToPath } from "url"
7
8
  import { createRequire } from "module"
8
9
 
@@ -47,12 +48,56 @@ function detectPlatformAndArch() {
47
48
  return { platform, arch }
48
49
  }
49
50
 
51
+ function detectMusl(platform) {
52
+ if (platform !== "linux") return false
53
+ try {
54
+ return fs.readdirSync("/lib").some((entry) => entry.startsWith("ld-musl-"))
55
+ } catch {
56
+ return false
57
+ }
58
+ }
59
+
60
+ function linuxKernelProblem(platform, release = os.release()) {
61
+ if (platform !== "linux") return undefined
62
+ const match = /^(\d+)\.(\d+)/.exec(release)
63
+ if (!match) return undefined
64
+ const major = Number(match[1])
65
+ const minor = Number(match[2])
66
+ if (major > 5 || (major === 5 && minor >= 1)) return undefined
67
+ return `Linux kernel ${release} is unsupported; OpenScience's bundled runtime requires kernel 5.1 or newer`
68
+ }
69
+
70
+ function pageSize(platform) {
71
+ if (platform !== "linux") return undefined
72
+ const result = childProcess.spawnSync("getconf", ["PAGESIZE"], { encoding: "utf8" })
73
+ return result.status === 0 ? result.stdout.trim() || undefined : undefined
74
+ }
75
+
76
+ function linuxArm64PageSizeProblem(platform, arch, detectedPageSize) {
77
+ if (platform !== "linux" || arch !== "arm64" || detectedPageSize === undefined) return undefined
78
+ const bytes = Number.parseInt(String(detectedPageSize), 10)
79
+ if (!Number.isFinite(bytes) || bytes === 4096) return undefined
80
+ return [
81
+ `Linux ARM64 page size ${bytes} is unsupported by OpenScience's Bun-compiled executable`,
82
+ "use a kernel or VM configured with 4 KB pages",
83
+ "upstream status: https://github.com/oven-sh/bun/issues/17627",
84
+ ].join("; ")
85
+ }
86
+
87
+ function platformPackageNames(platform, arch, musl = detectMusl(platform)) {
88
+ const base = `openscience-${platform}-${arch}`
89
+ const variants = musl ? [`${base}-musl`] : [base]
90
+ if (arch === "x64") variants.push(musl ? `${base}-baseline-musl` : `${base}-baseline`)
91
+ return variants.flatMap((name) => [`@synsci/${name}`, name])
92
+ }
93
+
50
94
  function findBinary() {
51
95
  const { platform, arch } = detectPlatformAndArch()
52
96
  const binaryName = platform === "windows" ? "openscience.exe" : "openscience"
53
97
 
54
- // Try scoped package first (@synsci/openscience-darwin-arm64), then unscoped (openscience-darwin-arm64)
55
- const packageNames = [`@synsci/openscience-${platform}-${arch}`, `openscience-${platform}-${arch}`]
98
+ // Try the libc-compatible native package first, followed by an x64 baseline
99
+ // build for older CPUs. Never select a package for the wrong architecture.
100
+ const packageNames = platformPackageNames(platform, arch)
56
101
 
57
102
  for (const packageName of packageNames) {
58
103
  try {
@@ -70,7 +115,15 @@ function findBinary() {
70
115
  }
71
116
  }
72
117
 
73
- throw new Error(`Could not find platform binary package for ${platform}-${arch}`)
118
+ throw new Error(
119
+ [
120
+ `Could not find a native platform binary for ${platform}-${arch}.`,
121
+ `Expected one of: ${packageNames.join(", ")}.`,
122
+ `Detected node ${process.version} on kernel ${os.release()}.`,
123
+ `Check npm selection with: npm config get cpu`,
124
+ `Then inspect the install with: npm ls -g @synsci/openscience ${packageNames[0]} --depth=0`,
125
+ ].join("\n"),
126
+ )
74
127
  }
75
128
 
76
129
  function prepareBinDirectory(binaryName) {
@@ -102,7 +155,7 @@ function symlinkBinary(sourcePath, binaryName) {
102
155
  }
103
156
  }
104
157
 
105
- async function main() {
158
+ function main() {
106
159
  try {
107
160
  if (os.platform() === "win32") {
108
161
  // On Windows, the .exe is already included in the package and bin field points to it
@@ -111,6 +164,16 @@ async function main() {
111
164
  return
112
165
  }
113
166
 
167
+ const { platform, arch } = detectPlatformAndArch()
168
+ const kernelProblem = linuxKernelProblem(platform)
169
+ if (kernelProblem) {
170
+ throw new Error(`${kernelProblem}. CentOS 7's stock 3.10 kernel is not supported.`)
171
+ }
172
+ const pageSizeProblem = linuxArm64PageSizeProblem(platform, arch, pageSize(platform))
173
+ if (pageSizeProblem) {
174
+ throw new Error(pageSizeProblem)
175
+ }
176
+
114
177
  // On non-Windows platforms, just verify the binary package exists
115
178
  // Don't replace the wrapper script - it handles binary execution
116
179
  const { binaryPath } = findBinary()
@@ -122,9 +185,13 @@ async function main() {
122
185
  }
123
186
  }
124
187
 
125
- try {
126
- main()
127
- } catch (error) {
128
- console.error("Postinstall script error:", error.message)
129
- process.exit(0)
188
+ export {
189
+ detectMusl,
190
+ detectPlatformAndArch,
191
+ findBinary,
192
+ linuxArm64PageSizeProblem,
193
+ linuxKernelProblem,
194
+ platformPackageNames,
130
195
  }
196
+
197
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) main()