synsci 1.1.122 → 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 +65 -10
  2. package/package.json +2 -2
package/bin/synsci.mjs CHANGED
@@ -1,10 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { execSync, spawn } from "node:child_process"
4
- import { existsSync, readFileSync } from "node:fs"
3
+ // Hard guard against recursive invocation. The launcher binary and the real
4
+ // @synsci/cli binary share the name `synsci`; if a check ever resolves to
5
+ // the launcher itself, this prevents an infinite spawn chain that exhausts
6
+ // memory.
7
+ if (process.env.__SYNSCI_LAUNCHER_PID) {
8
+ process.stderr.write(
9
+ `synsci: launcher invoked recursively (parent pid ${process.env.__SYNSCI_LAUNCHER_PID}). Exiting.\n`,
10
+ )
11
+ process.exit(2)
12
+ }
13
+ process.env.__SYNSCI_LAUNCHER_PID = String(process.pid)
14
+
15
+ import { execFileSync, execSync, spawn } from "node:child_process"
16
+ import { existsSync, readFileSync, realpathSync } from "node:fs"
5
17
  import { homedir } from "node:os"
6
18
  import { join } from "node:path"
7
19
  import { createInterface } from "node:readline"
20
+ import { fileURLToPath } from "node:url"
21
+
22
+ const SELF_PATH = (() => {
23
+ try { return realpathSync(fileURLToPath(import.meta.url)) } catch { return "" }
24
+ })()
8
25
 
9
26
  const BOLD = "\x1b[1m"
10
27
  const DIM = "\x1b[2m"
@@ -49,7 +66,42 @@ function runQuiet(cmd) {
49
66
  catch { return null }
50
67
  }
51
68
 
52
- function isInstalled() { return runQuiet("which synsci") !== null }
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) {
75
+ try {
76
+ const real = realpathSync(p)
77
+ if (SELF_PATH && real === SELF_PATH) return true
78
+ if (real.includes("/_npx/")) return true
79
+ return false
80
+ } catch { return false }
81
+ }
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 }
53
105
 
54
106
  function isConnected() {
55
107
  const xdgData = process.env.XDG_DATA_HOME || join(homedir(), ".local", "share")
@@ -81,8 +133,9 @@ async function main() {
81
133
  console.log()
82
134
 
83
135
  // --- Step 1: Install or upgrade ---
84
- if (isInstalled()) {
85
- const raw = runQuiet("synsci --version") || "unknown"
136
+ let cliPath = resolveCli()
137
+ if (cliPath) {
138
+ const raw = runFileQuiet(cliPath, ["--version"]) || "unknown"
86
139
  const isDev = raw === "local" || raw.includes("-")
87
140
  if (isDev) {
88
141
  ok(`synsci ${DIM}(dev build)${RESET}`)
@@ -95,7 +148,7 @@ async function main() {
95
148
  } else {
96
149
  s.update(`Upgrading ${current} \u2192 ${latest}...`)
97
150
  try {
98
- execSync("synsci upgrade", { stdio: "pipe" })
151
+ execFileSync(cliPath, ["upgrade"], { stdio: "pipe" })
99
152
  s.ok(`Upgraded to ${latest}`)
100
153
  } catch {
101
154
  s.warn(`Upgrade failed, continuing with ${current}`)
@@ -106,9 +159,11 @@ async function main() {
106
159
  const s = spinner("Installing synsci...")
107
160
  try {
108
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")
109
164
  s.ok("Installed synsci")
110
- } catch {
111
- s.fail("Install failed")
165
+ } catch (e) {
166
+ s.fail(`Install failed${e && e.message ? ": " + e.message : ""}`)
112
167
  console.log(`\n Try manually: ${CYAN}npm i -g @synsci/cli${RESET}\n`)
113
168
  process.exit(1)
114
169
  }
@@ -120,7 +175,7 @@ async function main() {
120
175
  } else {
121
176
  console.log()
122
177
  try {
123
- execSync("synsci connect login", { stdio: "inherit" })
178
+ execFileSync(cliPath, ["connect", "login"], { stdio: "inherit" })
124
179
  } catch {}
125
180
  }
126
181
 
@@ -137,7 +192,7 @@ async function main() {
137
192
  console.log()
138
193
 
139
194
  const cmd = choice === "2" ? ["web"] : []
140
- const child = spawn("synsci", cmd, { stdio: "inherit" })
195
+ const child = spawn(cliPath, cmd, { stdio: "inherit" })
141
196
  child.on("close", (code) => process.exit(code ?? 0))
142
197
  }
143
198
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synsci",
3
- "version": "1.1.122",
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",
@@ -20,7 +20,7 @@
20
20
  ],
21
21
  "repository": {
22
22
  "type": "git",
23
- "url": "git+https://github.com/ishaan1124/synthetisciences-cli.git"
23
+ "url": "git+https://github.com/synthetic-sciences/synsci-cli.git"
24
24
  },
25
25
  "homepage": "https://syntheticsciences.ai"
26
26
  }