thinkpool-pair 0.7.193 → 0.7.194
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/package.json +1 -1
- package/service.mjs +45 -9
package/package.json
CHANGED
package/service.mjs
CHANGED
|
@@ -66,9 +66,30 @@ function npxPath(platform) {
|
|
|
66
66
|
try { fs.accessSync(cand); return cand } catch { return platform === 'win32' ? 'npx.cmd' : 'npx' }
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
function npmPath(platform = process.platform) {
|
|
70
|
+
const bin = path.dirname(process.execPath)
|
|
71
|
+
const cand = path.join(bin, platform === 'win32' ? 'npm.cmd' : 'npm')
|
|
72
|
+
try { fs.accessSync(cand); return cand } catch { return platform === 'win32' ? 'npm.cmd' : 'npm' }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Persistent services never execute through npx. Provision an immutable, versioned
|
|
76
|
+
// runtime from the tarball URL (which bypasses stale npm packument metadata), then point
|
|
77
|
+
// launchd/systemd directly at its bridge.mjs with the current Node binary.
|
|
78
|
+
export function provisionRuntime(version, { exec = execSync } = {}) {
|
|
79
|
+
if (!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(String(version))) throw new Error(`invalid runtime version: ${version}`)
|
|
80
|
+
const dir = path.join(os.homedir(), '.thinkpool-pair', 'runtimes', String(version))
|
|
81
|
+
const entry = path.join(dir, 'node_modules', 'thinkpool-pair', 'bridge.mjs')
|
|
82
|
+
if (fs.existsSync(entry)) return entry
|
|
83
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
84
|
+
const tarball = `https://registry.npmjs.org/thinkpool-pair/-/thinkpool-pair-${version}.tgz`
|
|
85
|
+
exec(`${shq(npmPath())} install --prefix ${shq(dir)} ${shq(tarball)} --prefer-online --no-audit --no-fund`, { stdio: 'inherit', shell: '/bin/bash' })
|
|
86
|
+
if (!fs.existsSync(entry)) throw new Error(`runtime install completed without ${entry}`)
|
|
87
|
+
return entry
|
|
88
|
+
}
|
|
89
|
+
|
|
69
90
|
// Pure artifact builder — returned shape is testable without side effects.
|
|
70
91
|
// room falsy → ACCOUNT service (bare `thinkpool-pair`, auto-serves all sessions).
|
|
71
|
-
export function buildArtifact(platform, { room = null, cmdArgs = [], autoUpdate = false, version = VERSION, staleProof = false } = {}) {
|
|
92
|
+
export function buildArtifact(platform, { room = null, cmdArgs = [], autoUpdate = false, version = VERSION, staleProof = false, runtimeEntry = null } = {}) {
|
|
72
93
|
const npx = npxPath(platform)
|
|
73
94
|
const cwd = process.cwd()
|
|
74
95
|
const id = slug(room)
|
|
@@ -97,7 +118,9 @@ export function buildArtifact(platform, { room = null, cmdArgs = [], autoUpdate
|
|
|
97
118
|
if (platform === 'darwin') {
|
|
98
119
|
// launchd KeepAlive supervises. staleProof → wrap in a cache-wiping /bin/bash launch so
|
|
99
120
|
// every (re)start re-fetches the pinned version fresh (deliberate-update path).
|
|
100
|
-
const args =
|
|
121
|
+
const args = runtimeEntry
|
|
122
|
+
? [process.execPath, runtimeEntry, ...(room ? [room] : []), ...tail]
|
|
123
|
+
: staleProof
|
|
101
124
|
? ['/bin/bash', '-lc', `${cacheWipe('"$HOME"')}; exec ${[npx, ...pkgArgs, ...tail].map(shq).join(' ')}`]
|
|
102
125
|
: [npx, ...pkgArgs, ...tail]
|
|
103
126
|
const file = path.join(os.homedir(), 'Library', 'LaunchAgents', `${label(room)}.plist`)
|
|
@@ -153,11 +176,15 @@ export function buildArtifact(platform, { room = null, cmdArgs = [], autoUpdate
|
|
|
153
176
|
}
|
|
154
177
|
|
|
155
178
|
if (platform === 'linux') {
|
|
156
|
-
const args =
|
|
179
|
+
const args = runtimeEntry
|
|
180
|
+
? [process.execPath, runtimeEntry, ...(room ? [room] : []), ...tail]
|
|
181
|
+
: [npx, ...pkgArgs, ...tail] // systemd Restart=always supervises
|
|
157
182
|
const file = path.join(os.homedir(), '.config', 'systemd', 'user', `${label(room)}.service`)
|
|
158
183
|
// staleProof → cache-wiping /bin/bash launch (same intent as darwin). `$$HOME` so systemd
|
|
159
184
|
// emits a literal $HOME to bash rather than expanding it itself (empty + warning).
|
|
160
|
-
const execStart =
|
|
185
|
+
const execStart = runtimeEntry
|
|
186
|
+
? args.map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(' ')
|
|
187
|
+
: staleProof
|
|
161
188
|
? `/bin/bash -lc "${cacheWipe('$$HOME')}; exec ${[npx, ...pkgArgs, ...tail].join(' ')}"`
|
|
162
189
|
: args.map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(' ')
|
|
163
190
|
const content = `[Unit]
|
|
@@ -192,7 +219,9 @@ WantedBy=default.target
|
|
|
192
219
|
// so it gets login-persistence only — no in-process crash-restart on Windows.
|
|
193
220
|
const startup = path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup')
|
|
194
221
|
const file = path.join(startup, `thinkpool-pair-${id}.cmd`)
|
|
195
|
-
const inner =
|
|
222
|
+
const inner = runtimeEntry
|
|
223
|
+
? [process.execPath, runtimeEntry, ...(room ? [room, '--supervise'] : []), ...tail].map((s) => `"${s}"`).join(' ')
|
|
224
|
+
: room
|
|
196
225
|
? ['npx', '-y', ...onlineFlag, verSpec, room, '--supervise', ...tail].join(' ')
|
|
197
226
|
: ['npx', '-y', ...onlineFlag, verSpec].join(' ')
|
|
198
227
|
const content = `@echo off\r\ntitle thinkpool-pair ${id}\r\n${inner}\r\n`
|
|
@@ -299,7 +328,14 @@ export function sweepExistingServices(keepLabel, platform = process.platform) {
|
|
|
299
328
|
// pinned version is fetched fresh on every (re)start. buildArtifact keeps staleProof=false
|
|
300
329
|
// so its unit tests assert the pure pinned shape.
|
|
301
330
|
export function installService(room, cmdArgs = [], { autoUpdate = false, version = VERSION, staleProof = true } = {}) {
|
|
302
|
-
|
|
331
|
+
// Pinned services get a stable local runtime. The legacy @latest opt-in keeps its
|
|
332
|
+
// existing npx behavior until it resolves a concrete version through updateService.
|
|
333
|
+
let runtimeEntry = null
|
|
334
|
+
if (!autoUpdate) {
|
|
335
|
+
try { runtimeEntry = provisionRuntime(version) }
|
|
336
|
+
catch (e) { process.stderr.write(` ⚠ runtime install failed; service left unchanged: ${e.message}\n`); return false }
|
|
337
|
+
}
|
|
338
|
+
const a = buildArtifact(process.platform, { room, cmdArgs, autoUpdate, version, staleProof, runtimeEntry })
|
|
303
339
|
// F4: clear every OTHER pre-existing pair service first, so only the one we install remains
|
|
304
340
|
// (the "two account services live" storm trigger). Our own label is bootout+bootstrapped
|
|
305
341
|
// by a.post below, so keep it.
|
|
@@ -323,10 +359,11 @@ export function installService(room, cmdArgs = [], { autoUpdate = false, version
|
|
|
323
359
|
? (process.platform === 'win32'
|
|
324
360
|
? 'auto-update ON: a new thinkpool-pair@latest applies on the next login/restart (Windows has no in-process self-update).'
|
|
325
361
|
: `auto-update ON: checks npm ~every 30 min and restarts to apply a new @latest once sessions are idle (≥${idleSecs}s quiet; they resume in place).`)
|
|
326
|
-
: `PINNED to ${version}
|
|
362
|
+
: `PINNED to ${version} (stable local runtime; restarts do not depend on npx or the network). To update: use "Restart & update the bridge" (or re-run install-service from a newer thinkpool-pair). Pass --auto-update to track @latest instead.`
|
|
327
363
|
const removeArg = room ? ` ${room}` : ''
|
|
328
364
|
const what = room ? `room ${room}` : 'your account (auto-serves every session)'
|
|
329
365
|
process.stderr.write(` ◆ ${what}\n ◆ ${a.note}\n ◆ ${updateNote}\n ◆ logs: ${path.join(a.logDir, `${slug(room)}.log`)}\n ◆ remove with: thinkpool-pair uninstall-service${removeArg}\n\n`)
|
|
366
|
+
return true
|
|
330
367
|
}
|
|
331
368
|
|
|
332
369
|
// Restart an already-installed boot-persistent service. launchd/systemd relaunch
|
|
@@ -376,8 +413,7 @@ export function updateService(room, { exec = execSync, install = installService,
|
|
|
376
413
|
return false
|
|
377
414
|
}
|
|
378
415
|
process.stderr.write(`\n ◆ updating the background service to v${target} and restarting…\n`)
|
|
379
|
-
install(room, [], { version: target, staleProof: true })
|
|
380
|
-
return true
|
|
416
|
+
return install(room, [], { version: target, staleProof: true }) !== false
|
|
381
417
|
}
|
|
382
418
|
|
|
383
419
|
// Ground truth: is the OS service manager ACTUALLY running our service right now?
|