synsci 1.1.123 → 1.1.124

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.
Files changed (2) hide show
  1. package/bin/synsci.mjs +44 -19
  2. package/package.json +1 -1
package/bin/synsci.mjs CHANGED
@@ -12,7 +12,7 @@ if (process.env.__SYNSCI_LAUNCHER_PID) {
12
12
  }
13
13
  process.env.__SYNSCI_LAUNCHER_PID = String(process.pid)
14
14
 
15
- import { execSync, spawn } from "node:child_process"
15
+ import { execFileSync, execSync, spawn } from "node:child_process"
16
16
  import { existsSync, readFileSync, realpathSync } from "node:fs"
17
17
  import { homedir } from "node:os"
18
18
  import { join } from "node:path"
@@ -66,21 +66,43 @@ function runQuiet(cmd) {
66
66
  catch { return null }
67
67
  }
68
68
 
69
- function isInstalled() {
70
- const found = runQuiet("which synsci")
71
- if (!found) return false
69
+ function runFileQuiet(file, args = []) {
70
+ try { return execFileSync(file, args, { encoding: "utf-8", stdio: "pipe" }).trim() }
71
+ catch { return null }
72
+ }
73
+
74
+ function isLauncherPath(p) {
72
75
  try {
73
- const real = realpathSync(found)
74
- // Skip if resolves to this launcher itself — prevents the npx-cache recursion
75
- // where `which synsci` finds the launcher's own bin and `synsci --version`
76
- // re-invokes main() forever.
77
- if (SELF_PATH && real === SELF_PATH) return false
78
- // Belt-and-suspenders: an npx shim also routes back to a launcher
79
- if (real.includes("/_npx/")) return false
80
- return true
76
+ const real = realpathSync(p)
77
+ if (SELF_PATH && real === SELF_PATH) return true
78
+ if (real.includes("/_npx/")) return true
79
+ return false
81
80
  } catch { return false }
82
81
  }
83
82
 
83
+ // Returns the absolute path to the real @synsci/cli binary, never the launcher
84
+ // itself. Walks past PATH entries pointing at this script or any npx shim.
85
+ function resolveCli() {
86
+ // 1. Global npm prefix (where `npm i -g @synsci/cli` puts it)
87
+ const prefix = runQuiet("npm prefix -g")
88
+ if (prefix) {
89
+ const cand = join(prefix, "bin", "synsci")
90
+ if (existsSync(cand) && !isLauncherPath(cand)) return cand
91
+ }
92
+ // 2. ~/.synsc/bin/synsci (used by @synsci/cli postinstall on some setups)
93
+ const synscBin = join(homedir(), ".synsc", "bin", "synsci")
94
+ if (existsSync(synscBin) && !isLauncherPath(synscBin)) return synscBin
95
+ // 3. Walk every PATH entry, return first that is NOT a launcher
96
+ for (const dir of (process.env.PATH || "").split(":")) {
97
+ if (!dir) continue
98
+ const cand = join(dir, "synsci")
99
+ if (existsSync(cand) && !isLauncherPath(cand)) return cand
100
+ }
101
+ return null
102
+ }
103
+
104
+ function isInstalled() { return resolveCli() !== null }
105
+
84
106
  function isConnected() {
85
107
  const xdgData = process.env.XDG_DATA_HOME || join(homedir(), ".local", "share")
86
108
  const sessionPath = join(xdgData, "synsc", "synsci-session.json")
@@ -111,8 +133,9 @@ async function main() {
111
133
  console.log()
112
134
 
113
135
  // --- Step 1: Install or upgrade ---
114
- if (isInstalled()) {
115
- const raw = runQuiet("synsci --version") || "unknown"
136
+ let cliPath = resolveCli()
137
+ if (cliPath) {
138
+ const raw = runFileQuiet(cliPath, ["--version"]) || "unknown"
116
139
  const isDev = raw === "local" || raw.includes("-")
117
140
  if (isDev) {
118
141
  ok(`synsci ${DIM}(dev build)${RESET}`)
@@ -125,7 +148,7 @@ async function main() {
125
148
  } else {
126
149
  s.update(`Upgrading ${current} \u2192 ${latest}...`)
127
150
  try {
128
- execSync("synsci upgrade", { stdio: "pipe" })
151
+ execFileSync(cliPath, ["upgrade"], { stdio: "pipe" })
129
152
  s.ok(`Upgraded to ${latest}`)
130
153
  } catch {
131
154
  s.warn(`Upgrade failed, continuing with ${current}`)
@@ -136,9 +159,11 @@ async function main() {
136
159
  const s = spinner("Installing synsci...")
137
160
  try {
138
161
  execSync("npm i -g @synsci/cli@latest", { stdio: "pipe" })
162
+ cliPath = resolveCli()
163
+ if (!cliPath) throw new Error("real CLI not on PATH after install")
139
164
  s.ok("Installed synsci")
140
- } catch {
141
- s.fail("Install failed")
165
+ } catch (e) {
166
+ s.fail(`Install failed${e && e.message ? ": " + e.message : ""}`)
142
167
  console.log(`\n Try manually: ${CYAN}npm i -g @synsci/cli${RESET}\n`)
143
168
  process.exit(1)
144
169
  }
@@ -150,7 +175,7 @@ async function main() {
150
175
  } else {
151
176
  console.log()
152
177
  try {
153
- execSync("synsci connect login", { stdio: "inherit" })
178
+ execFileSync(cliPath, ["connect", "login"], { stdio: "inherit" })
154
179
  } catch {}
155
180
  }
156
181
 
@@ -167,7 +192,7 @@ async function main() {
167
192
  console.log()
168
193
 
169
194
  const cmd = choice === "2" ? ["web"] : []
170
- const child = spawn("synsci", cmd, { stdio: "inherit" })
195
+ const child = spawn(cliPath, cmd, { stdio: "inherit" })
171
196
  child.on("close", (code) => process.exit(code ?? 0))
172
197
  }
173
198
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synsci",
3
- "version": "1.1.123",
3
+ "version": "1.1.124",
4
4
  "description": "One command to install, connect, and launch Synthetic Sciences CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",