Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 24 additions & 10 deletions backend/cli/bin/openscience
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,24 @@ const arch = archMap[os.arch()] || os.arch()
const base = "openscience-" + platform + "-" + arch
const scopedBase = "openscience-" + platform + "-" + arch

// Rank matching platform packages instead of trusting readdir order (which
// is filesystem-dependent): right libc first, exact name before suffixed
// variants like -baseline, wrong libc only as a last resort.
const musl = (() => {
if (platform !== "linux") return false
try { return fs.readdirSync("/lib").some((f) => f.startsWith("ld-musl-")) } catch { return false }
})()
function variantRank(prefix, entry) {
const entryMusl = entry.includes("-musl")
if (entryMusl !== musl) return 0
return entry === prefix || entry === prefix + "-musl" ? 2 : 1
}
function matchingVariants(prefix, entries) {
return entries
.filter((e) => e.startsWith(prefix))
.sort((a, b) => variantRank(prefix, b) - variantRank(prefix, a))
}

// 2. Search this install's node_modules for the matching platform package.
// This must come before ~/.openscience or Homebrew fallbacks; otherwise an npm
// install can accidentally launch an older globally-installed binary.
Expand All @@ -70,21 +88,17 @@ while (true) {
if (fs.existsSync(modules)) {
try {
const entries = fs.readdirSync(modules)
for (const entry of entries) {
if (entry.startsWith(base)) {
const candidate = path.join(modules, entry, "bin", binary)
if (isBinary(candidate)) run(candidate)
}
for (const entry of matchingVariants(base, entries)) {
const candidate = path.join(modules, entry, "bin", binary)
if (isBinary(candidate)) run(candidate)
}
// Check scoped packages (@synsci/openscience-darwin-arm64)
const scoped = path.join(modules, "@synsci")
if (fs.existsSync(scoped)) {
const scopedEntries = fs.readdirSync(scoped)
for (const entry of scopedEntries) {
if (entry.startsWith(scopedBase)) {
const candidate = path.join(scoped, entry, "bin", binary)
if (isBinary(candidate)) run(candidate)
}
for (const entry of matchingVariants(scopedBase, scopedEntries)) {
const candidate = path.join(scoped, entry, "bin", binary)
if (isBinary(candidate)) run(candidate)
}
}
} catch {}
Expand Down
3 changes: 3 additions & 0 deletions backend/cli/script/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,9 @@ for (const item of targets) {
version: Script.version,
os: [item.os],
cpu: [item.arch],
// Without libc, linux-x64 hosts install all four x64 variants
// (~750 MB) and glibc/musl selection falls to the bin wrapper.
...(item.os === "linux" ? { libc: [item.abi === "musl" ? "musl" : "glibc"] } : {}),
// npm provenance refuses packages whose repository.url doesn't match
// the repo the workflow ran from (case-sensitive)
repository: {
Expand Down
33 changes: 25 additions & 8 deletions frontend/landing/public/install
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,11 @@ else

if [ -z "$requested_version" ]; then
url="https://github.com/synthetic-sciences/OpenScience/releases/latest/download/$filename"
specific_version=$(curl -s https://api.github.com/repos/synthetic-sciences/OpenScience/releases/latest | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p')
# The || fallback keeps set -e from killing the script inside the
# assignment when curl fails, so the message below can print.
specific_version=$(curl -fsS https://api.github.com/repos/synthetic-sciences/OpenScience/releases/latest | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p') || specific_version=""

if [[ $? -ne 0 || -z "$specific_version" ]]; then
if [[ -z "$specific_version" ]]; then
echo -e "${RED}Failed to fetch version information${NC}"
exit 1
fi
Expand Down Expand Up @@ -273,7 +275,7 @@ download_with_progress() {
trap "trap - RETURN; rm -f \"$tracefile\"; printf '\033[?25h' >&4; exec 4>&-" RETURN

(
curl --trace-ascii "$tracefile" -s -L -o "$output" "$url"
curl --trace-ascii "$tracefile" -f -s -L -o "$output" "$url"
) &
local curl_pid=$!

Expand Down Expand Up @@ -317,7 +319,16 @@ download_and_install() {

if [[ "$os" == "windows" ]] || ! [ -t 2 ] || ! download_with_progress "$url" "$tmp_dir/$filename"; then
# Fallback to standard curl on Windows, non-TTY environments, or if custom progress fails
curl -# -L -o "$tmp_dir/$filename" "$url"
curl -f -# -L -o "$tmp_dir/$filename" "$url"
fi

# Without -f curl saves the error body ("Not Found") and exits 0, and the
# user gets a cryptic unzip/tar failure. Catch a missing/empty download
# here with a real message instead.
if [ ! -s "$tmp_dir/$filename" ]; then
echo -e "${RED}Download failed: $url${NC}"
rm -rf "$tmp_dir"
exit 1
fi

if [ "$os" = "linux" ]; then
Expand All @@ -326,8 +337,14 @@ download_and_install() {
unzip -q "$tmp_dir/$filename" -d "$tmp_dir"
fi

mv "$tmp_dir/openscience" "$INSTALL_DIR"
chmod 755 "${INSTALL_DIR}/openscience"
# Bun --compile appends .exe for win32 targets, so the archive entry is
# openscience.exe there. Git Bash resolves `openscience` to the .exe.
local bin_name="openscience"
if [ "$os" = "windows" ]; then
bin_name="openscience.exe"
fi
mv "$tmp_dir/$bin_name" "$INSTALL_DIR"
chmod 755 "${INSTALL_DIR}/${bin_name}"
rm -rf "$tmp_dir"
}

Expand Down Expand Up @@ -424,8 +441,8 @@ if [[ "$no_modify_path" != "true" ]]; then
fi
fi

if [ -n "${GITHUB_ACTIONS-}" ] && [ "${GITHUB_ACTIONS}" == "true" ]; then
echo "$INSTALL_DIR" >> $GITHUB_PATH
if [ "${GITHUB_ACTIONS-}" == "true" ] && [ -n "${GITHUB_PATH-}" ]; then
echo "$INSTALL_DIR" >> "$GITHUB_PATH"
print_message info "Added $INSTALL_DIR to \$GITHUB_PATH"
fi

Expand Down
33 changes: 25 additions & 8 deletions install
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,11 @@ else

if [ -z "$requested_version" ]; then
url="https://github.com/synthetic-sciences/OpenScience/releases/latest/download/$filename"
specific_version=$(curl -s https://api.github.com/repos/synthetic-sciences/OpenScience/releases/latest | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p')
# The || fallback keeps set -e from killing the script inside the
# assignment when curl fails, so the message below can print.
specific_version=$(curl -fsS https://api.github.com/repos/synthetic-sciences/OpenScience/releases/latest | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p') || specific_version=""

if [[ $? -ne 0 || -z "$specific_version" ]]; then
if [[ -z "$specific_version" ]]; then
echo -e "${RED}Failed to fetch version information${NC}"
exit 1
fi
Expand Down Expand Up @@ -273,7 +275,7 @@ download_with_progress() {
trap "trap - RETURN; rm -f \"$tracefile\"; printf '\033[?25h' >&4; exec 4>&-" RETURN

(
curl --trace-ascii "$tracefile" -s -L -o "$output" "$url"
curl --trace-ascii "$tracefile" -f -s -L -o "$output" "$url"
) &
local curl_pid=$!

Expand Down Expand Up @@ -317,7 +319,16 @@ download_and_install() {

if [[ "$os" == "windows" ]] || ! [ -t 2 ] || ! download_with_progress "$url" "$tmp_dir/$filename"; then
# Fallback to standard curl on Windows, non-TTY environments, or if custom progress fails
curl -# -L -o "$tmp_dir/$filename" "$url"
curl -f -# -L -o "$tmp_dir/$filename" "$url"
fi

# Without -f curl saves the error body ("Not Found") and exits 0, and the
# user gets a cryptic unzip/tar failure. Catch a missing/empty download
# here with a real message instead.
if [ ! -s "$tmp_dir/$filename" ]; then
echo -e "${RED}Download failed: $url${NC}"
rm -rf "$tmp_dir"
exit 1
fi

if [ "$os" = "linux" ]; then
Expand All @@ -326,8 +337,14 @@ download_and_install() {
unzip -q "$tmp_dir/$filename" -d "$tmp_dir"
fi

mv "$tmp_dir/openscience" "$INSTALL_DIR"
chmod 755 "${INSTALL_DIR}/openscience"
# Bun --compile appends .exe for win32 targets, so the archive entry is
# openscience.exe there. Git Bash resolves `openscience` to the .exe.
local bin_name="openscience"
if [ "$os" = "windows" ]; then
bin_name="openscience.exe"
fi
mv "$tmp_dir/$bin_name" "$INSTALL_DIR"
chmod 755 "${INSTALL_DIR}/${bin_name}"
rm -rf "$tmp_dir"
}

Expand Down Expand Up @@ -424,8 +441,8 @@ if [[ "$no_modify_path" != "true" ]]; then
fi
fi

if [ -n "${GITHUB_ACTIONS-}" ] && [ "${GITHUB_ACTIONS}" == "true" ]; then
echo "$INSTALL_DIR" >> $GITHUB_PATH
if [ "${GITHUB_ACTIONS-}" == "true" ] && [ -n "${GITHUB_PATH-}" ]; then
echo "$INSTALL_DIR" >> "$GITHUB_PATH"
print_message info "Added $INSTALL_DIR to \$GITHUB_PATH"
fi

Expand Down
47 changes: 38 additions & 9 deletions tooling/launcher/bin/synsci.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,17 @@ function runQuiet(cmd) {
catch { return null }
}

// Windows global installs expose a .cmd shim, which can't be exec'd
// directly — it needs a shell (and the path quoted for it).
const isCmdShim = (p) => process.platform === "win32" && p.toLowerCase().endsWith(".cmd")

function execCli(file, args = [], opts = {}) {
if (isCmdShim(file)) return execSync(['"' + file + '"', ...args].join(" "), opts)
return execFileSync(file, args, opts)
}

function runFileQuiet(file, args = []) {
try { return execFileSync(file, args, { encoding: "utf-8", stdio: "pipe" }).trim() }
try { return execCli(file, args, { encoding: "utf-8", stdio: "pipe" }).trim() }
catch { return null }
}

Expand All @@ -97,16 +106,21 @@ function isLauncherPath(p) {
// `--version` so half-broken installs are skipped instead of accepted.
function resolveCli() {
const candidates = []
// 1. Global npm prefix (where `npm i -g @synsci/openscience` puts it)
// 1. Global npm prefix (where `npm i -g @synsci/openscience` puts it).
// On Windows the global bin dir is the prefix itself and the entry is an
// openscience.cmd shim; on POSIX it's <prefix>/bin/openscience.
const prefix = runQuiet("npm prefix -g")
if (prefix) candidates.push(join(prefix, "bin", "openscience"))
// 2. ~/.openscience/bin/openscience (curl-installer location)
candidates.push(join(homedir(), ".openscience", "bin", "openscience"))
if (prefix) {
if (process.platform === "win32") candidates.push(join(prefix, "openscience.cmd"))
else candidates.push(join(prefix, "bin", "openscience"))
}
// 2. ~/.openscience/bin/openscience (curl-installer location, POSIX only)
if (process.platform !== "win32") candidates.push(join(homedir(), ".openscience", "bin", "openscience"))

for (const cand of candidates) {
if (!existsSync(cand) || isLauncherPath(cand)) continue
try {
const ver = execFileSync(cand, ["--version"], {
const ver = execCli(cand, ["--version"], {
encoding: "utf-8", stdio: "pipe", timeout: 5000,
}).trim()
if (/^\d/.test(ver)) return cand
Expand Down Expand Up @@ -140,6 +154,11 @@ function isConnected() {
}

function atlasVersion() {
// `atlas` on PATH may be Ariga Atlas or the MongoDB Atlas CLI, whose
// --version output also survives the digit filter. Only trust the command
// when the global @synsci/atlas package is present to own it.
const owned = runQuiet("npm ls -g @synsci/atlas --depth=0")
if (!owned || !owned.includes("@synsci/atlas")) return null
const raw = runQuiet("atlas --version")
return raw ? raw.replace(/[^0-9.]/g, "") : null
}
Expand Down Expand Up @@ -215,7 +234,7 @@ async function main() {
} else {
s.update(`Upgrading ${current} → ${latest}...`)
try {
execFileSync(cliPath, ["upgrade"], { stdio: "pipe" })
execCli(cliPath, ["upgrade"], { stdio: "pipe" })
s.ok(`Upgraded to ${latest}`)
} catch {
s.warn(`Upgrade failed, continuing with ${current}`)
Expand Down Expand Up @@ -243,6 +262,13 @@ async function main() {
if (!cliPath) throw new Error("openscience not on PATH after install")
s.ok("Installed OpenScience")
} catch {
// The standalone installer is a bash script; on native Windows there's
// no bash to pipe it into, so don't suggest a fallback that can't run.
if (process.platform === "win32") {
s.fail("Install failed")
console.log(`\n Try manually: ${CYAN}npm i -g @synsci/openscience${RESET}\n`)
process.exit(1)
}
// Global npm installs commonly fail on permissions. Fall back to the
// standalone installer, which lands in ~/.openscience/bin without sudo
// (resolveCli already checks that location).
Expand Down Expand Up @@ -306,7 +332,7 @@ async function main() {
} else {
console.log()
try {
execFileSync(cliPath, ["connect", "login"], { stdio: "inherit" })
execCli(cliPath, ["connect", "login"], { stdio: "inherit" })
} catch {}
}
} else {
Expand All @@ -319,7 +345,10 @@ async function main() {
console.log(` ${DIM}Opening the workspace in your browser…${RESET}`)
console.log()

const child = spawn(cliPath, ["web", ...process.argv.slice(2)], { stdio: "inherit" })
const webArgs = ["web", ...process.argv.slice(2)]
const child = isCmdShim(cliPath)
? spawn(['"' + cliPath + '"', ...webArgs].join(" "), { stdio: "inherit", shell: true })
: spawn(cliPath, webArgs, { stdio: "inherit" })
child.on("close", (code) => process.exit(code ?? 0))
}

Expand Down
Loading