free-coding-models 0.5.17 → 0.5.19
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/changelog/v0.5.18.md +12 -0
- package/changelog/v0.5.19.md +25 -0
- package/package.json +1 -1
- package/sources.js +12 -4
- package/src/core/router-daemon.js +4 -0
- package/src/core/updater.js +112 -21
- package/web/dist/assets/{index-B4ioiEvP.js → index-CMdaiTwG.js} +2 -2
- package/web/dist/index.html +1 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Changelog v0.5.18 - 2026-06-09
|
|
2
|
+
|
|
3
|
+
### Fixed
|
|
4
|
+
- Fixed the mandatory startup updater loop when the active `free-coding-models` binary is installed under one npm prefix but another Node/npm manager shadows `npm` in `PATH`. The updater now detects the npm prefix that owns the currently running package, installs into that exact prefix, and verifies that the active package version changed before relaunching.
|
|
5
|
+
- Improved updater fallback instructions so manual recovery commands include the owning npm binary and `--prefix` target when needed, instead of pointing users at a different global install.
|
|
6
|
+
- Stabilized the router dashboard SSE endpoints so `/api/events` and `/stream/events` flush an initial frame immediately and tests/clients do not hang while waiting for the first broadcast.
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
- Restored the normal startup update path and live OpenRouter/release-date startup behavior after the temporary local debug bypass used to diagnose the update loop.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- Added regression coverage for npm-prefix-aware update command generation so future updater changes do not reintroduce the same cross-prefix loop.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Changelog v0.5.19 - 2026-06-10
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
- **nvidia**: Added `nvidia/nemotron-3-ultra-550b-a55b` (S+, 1M context)
|
|
5
|
+
- **groq**: Added 7 new specialized models:
|
|
6
|
+
- `whisper-large-v3`, `whisper-large-v3-turbo` (audio transcription)
|
|
7
|
+
- `canopylabs/orpheus-arabic-saudi`, `canopylabs/orpheus-v1-english` (Arabic/English speech)
|
|
8
|
+
- `meta-llama/llama-prompt-guard-2-22m`, `meta-llama/llama-prompt-guard-2-86m` (security guardrails)
|
|
9
|
+
- `openai/gpt-oss-safeguard-20b` (safety)
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- **nvidia**: Corrected context windows for three models:
|
|
13
|
+
- `deepseek-ai/deepseek-v4-pro`: 128k → 1M
|
|
14
|
+
- `deepseek-ai/deepseek-v4-flash`: 128k → 1M
|
|
15
|
+
- `mistralai/mistral-small-4-119b-2603`: 128k → 256k
|
|
16
|
+
- **nvidia**: Removed deprecated model `z-ai/glm5` (replaced by `z-ai/glm-5.1`)
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- Model counts: nvidia stable at 27, groq increased from 8 to 15.
|
|
20
|
+
- Total catalog size: 161 models across 16 providers.
|
|
21
|
+
|
|
22
|
+
### Notes
|
|
23
|
+
- This update reflects the latest provider catalogs as of June 10, 2026.
|
|
24
|
+
- All existing models were verified against official docs; only minor configuration drift was found.
|
|
25
|
+
- The `audit_state.json` was updated with fresh fingerprints.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-coding-models",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.19",
|
|
4
4
|
"description": "Find the fastest coding LLM models in seconds — ping free models from multiple providers, pick the best one for OpenCode, Cursor, or any AI coding assistant.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nvidia",
|
package/sources.js
CHANGED
|
@@ -44,19 +44,19 @@ export const nvidiaNim = [
|
|
|
44
44
|
['minimaxai/minimax-m2.7', 'MiniMax M2.7', 'S+', '80.2%', '200k'],
|
|
45
45
|
['z-ai/glm-5.1', 'GLM 5.1', 'S+', '77.8%', '128k'],
|
|
46
46
|
['moonshotai/kimi-k2.6', 'Kimi K2.6', 'S+', '76.8%', '256k'],
|
|
47
|
-
['deepseek-ai/deepseek-v4-pro', 'DeepSeek V4 Pro', 'S+', '73.1%', '
|
|
48
|
-
['deepseek-ai/deepseek-v4-flash', 'DeepSeek V4 Flash', 'S+', '72.0%', '
|
|
49
|
-
['z-ai/glm5', 'GLM 5', 'S+', '73.8%', '200k'],
|
|
47
|
+
['deepseek-ai/deepseek-v4-pro', 'DeepSeek V4 Pro', 'S+', '73.1%', '1M'],
|
|
48
|
+
['deepseek-ai/deepseek-v4-flash', 'DeepSeek V4 Flash', 'S+', '72.0%', '1M'],
|
|
50
49
|
['stepfun-ai/step-3.5-flash', 'Step 3.5 Flash', 'S+', '74.4%', '256k'],
|
|
51
50
|
['stepfun-ai/step-3.7-flash', 'Step 3.7 Flash', 'S+', '74.4%', '256k'],
|
|
52
51
|
['qwen/qwen3-coder-480b-a35b-instruct', 'Qwen3 Coder 480B', 'S+', '70.6%', '256k'],
|
|
52
|
+
['nvidia/nemotron-3-ultra-550b-a55b', 'Nemotron 3 Ultra', 'S+', '-', '1M'],
|
|
53
53
|
// ── S tier — SWE-bench Verified 60–70% ──
|
|
54
54
|
['qwen/qwen3-next-80b-a3b-instruct', 'Qwen3 80B Instruct', 'S', '65.0%', '128k'],
|
|
55
55
|
['qwen/qwen3.5-397b-a17b', 'Qwen3.5 400B VLM', 'S', '68.0%', '128k'],
|
|
56
56
|
['openai/gpt-oss-120b', 'GPT OSS 120B', 'S', '60.0%', '128k'],
|
|
57
57
|
['meta/llama-4-maverick-17b-128e-instruct', 'Llama 4 Maverick', 'S', '62.0%', '1M'],
|
|
58
58
|
['mistralai/mistral-medium-3.5-128b', 'Mistral Medium 3.5', 'S', '66.0%', '128k'],
|
|
59
|
-
['mistralai/mistral-small-4-119b-2603', 'Mistral Small 4', 'S', '60.0%', '
|
|
59
|
+
['mistralai/mistral-small-4-119b-2603', 'Mistral Small 4', 'S', '60.0%', '256k'],
|
|
60
60
|
['qwen/qwen3.5-122b-a10b', 'Qwen3.5 122B', 'S', '64.0%', '128k'],
|
|
61
61
|
// ── A+ tier — SWE-bench Verified 50–60% ──
|
|
62
62
|
['mistralai/mistral-large-3-675b-instruct-2512', 'Mistral Large 675B', 'A+', '58.0%', '256k'],
|
|
@@ -80,6 +80,7 @@ export const nvidiaNim = [
|
|
|
80
80
|
// llama-3.3-70b-instruct (404), mixtral-8x22b-instruct-v0.1 (404),
|
|
81
81
|
// granite-34b-code-instruct (deprecated), llama-4-scout-17b-16e-instruct (deprecated, not in nvidiaNim),
|
|
82
82
|
// llama-3.1-8b-instruct (404) → replaced by llama-3.2-11b-vision-instruct
|
|
83
|
+
// Removed (2026-06-10): z-ai/glm5 (replaced by z-ai/glm-5.1)
|
|
83
84
|
]
|
|
84
85
|
|
|
85
86
|
// 📖 Groq source - https://console.groq.com
|
|
@@ -93,6 +94,13 @@ export const groq = [
|
|
|
93
94
|
['qwen/qwen3-32b', 'Qwen3 32B', 'A+', '50.0%', '131k'],
|
|
94
95
|
['groq/compound', 'Groq Compound', 'A', '45.0%', '131k'],
|
|
95
96
|
['groq/compound-mini', 'Groq Compound Mini', 'B+', '32.0%', '131k'],
|
|
97
|
+
['whisper-large-v3', 'Whisper', 'C', '-', '-'],
|
|
98
|
+
['whisper-large-v3-turbo', 'Whisper Large V3 Turbo', 'C', '-', '-'],
|
|
99
|
+
['canopylabs/orpheus-arabic-saudi', 'Canopy Labs Orpheus Arabic Saudi', 'C', '-', '4k'],
|
|
100
|
+
['canopylabs/orpheus-v1-english', 'Canopy Labs Orpheus V1 English', 'C', '-', '4k'],
|
|
101
|
+
['meta-llama/llama-prompt-guard-2-22m', 'Llama Prompt Guard 2 22M', 'C', '-', '512'],
|
|
102
|
+
['meta-llama/llama-prompt-guard-2-86m', 'Prompt Guard 2 86M', 'C', '-', '512'],
|
|
103
|
+
['openai/gpt-oss-safeguard-20b', 'Safety GPT OSS 20B', 'C', '-', '131k'],
|
|
96
104
|
]
|
|
97
105
|
|
|
98
106
|
// 📖 Cerebras source - https://cloud.cerebras.ai
|
|
@@ -2508,6 +2508,8 @@ class RouterRuntime {
|
|
|
2508
2508
|
Connection: 'keep-alive',
|
|
2509
2509
|
'x-request-id': requestId,
|
|
2510
2510
|
})
|
|
2511
|
+
res.flushHeaders?.()
|
|
2512
|
+
res.write(': connected\n\n')
|
|
2511
2513
|
res.write(`event: hello\ndata: ${JSON.stringify(this.statusPayload())}\n\n`)
|
|
2512
2514
|
this.sseClients.add(res)
|
|
2513
2515
|
req.on('close', () => this.sseClients.delete(res))
|
|
@@ -2643,6 +2645,8 @@ class RouterRuntime {
|
|
|
2643
2645
|
'Connection': 'keep-alive',
|
|
2644
2646
|
'x-request-id': requestId,
|
|
2645
2647
|
})
|
|
2648
|
+
res.flushHeaders?.()
|
|
2649
|
+
res.write(': connected\n\n')
|
|
2646
2650
|
res.write(`data: ${JSON.stringify(getWebStatePayload(this))}\n\n`)
|
|
2647
2651
|
this.sseClients.add(res)
|
|
2648
2652
|
req.on('close', () => this.sseClients.delete(res))
|
package/src/core/updater.js
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
*
|
|
29
29
|
* @functions
|
|
30
30
|
* → detectPackageManager() — Detect which PM owns the current installation
|
|
31
|
+
* → resolveCurrentNpmInstallTarget() — Detect the npm prefix that owns the active package
|
|
31
32
|
* → getInstallArgs(pm, version) — Build correct { bin, args } per package manager
|
|
32
33
|
* → getManualInstallCmd(pm, version) — Human-readable install command string for error messages
|
|
33
34
|
* → checkForUpdateDetailed() — Fetch npm latest with explicit error info
|
|
@@ -36,7 +37,7 @@
|
|
|
36
37
|
* → enforceMandatoryStartupUpdate() — Mandatory startup self-update with two-failure fallback
|
|
37
38
|
* → runUpdate(latestVersion) — Install new version via detected PM + relaunch
|
|
38
39
|
* @exports
|
|
39
|
-
* detectPackageManager, getInstallArgs, getManualInstallCmd,
|
|
40
|
+
* detectPackageManager, resolveCurrentNpmInstallTarget, getInstallArgs, getManualInstallCmd,
|
|
40
41
|
* checkForUpdateDetailed, checkForUpdate, isPackageDevMode,
|
|
41
42
|
* enforceMandatoryStartupUpdate, runUpdate, fetchLastReleaseDate
|
|
42
43
|
*
|
|
@@ -47,15 +48,42 @@ import chalk from 'chalk'
|
|
|
47
48
|
import { createRequire } from 'module'
|
|
48
49
|
import { fileURLToPath } from 'url'
|
|
49
50
|
import { dirname, join } from 'path'
|
|
50
|
-
import { accessSync, constants, existsSync } from 'fs'
|
|
51
|
+
import { accessSync, constants, existsSync, readFileSync } from 'fs'
|
|
51
52
|
|
|
52
53
|
const require = createRequire(import.meta.url)
|
|
53
54
|
const readline = require('readline')
|
|
54
55
|
const pkg = require('../../package.json')
|
|
55
56
|
const LOCAL_VERSION = pkg.version
|
|
56
57
|
const PACKAGE_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..')
|
|
58
|
+
const PACKAGE_NAME = 'free-coding-models'
|
|
57
59
|
export const UPDATE_FAILURE_THRESHOLD = 2
|
|
58
60
|
|
|
61
|
+
/**
|
|
62
|
+
* 📖 resolveCurrentNpmInstallTarget: detect the npm prefix that owns this exact
|
|
63
|
+
* 📖 running package, not just the first `npm` found in PATH. Users can run FCM
|
|
64
|
+
* 📖 from one global install while another Node manager shadows `npm`; updating
|
|
65
|
+
* 📖 the shadow prefix leaves the active binary stale and creates an update loop.
|
|
66
|
+
* @param {string} [packageRoot]
|
|
67
|
+
* @returns {{ packageRoot: string, prefix: string, bin: string } | null}
|
|
68
|
+
*/
|
|
69
|
+
export function resolveCurrentNpmInstallTarget(packageRoot = PACKAGE_ROOT) {
|
|
70
|
+
const normalizedRoot = String(packageRoot || '').replace(/\\/g, '/')
|
|
71
|
+
const suffix = `/lib/node_modules/${PACKAGE_NAME}`
|
|
72
|
+
if (!normalizedRoot.endsWith(suffix)) return null
|
|
73
|
+
|
|
74
|
+
const prefix = packageRoot.slice(0, packageRoot.length - suffix.length)
|
|
75
|
+
if (!prefix) return null
|
|
76
|
+
|
|
77
|
+
const npmBinName = process.platform === 'win32' ? 'npm.cmd' : 'npm'
|
|
78
|
+
const npmBin = join(prefix, 'bin', npmBinName)
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
packageRoot,
|
|
82
|
+
prefix,
|
|
83
|
+
bin: existsSync(npmBin) ? npmBin : 'npm',
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
59
87
|
/**
|
|
60
88
|
* 📖 detectPackageManager: figure out which package manager owns the current installation.
|
|
61
89
|
* 📖 Checks import.meta.url (package install path), process.argv[1] (script entry),
|
|
@@ -141,27 +169,48 @@ export function buildOutdatedWarningMessage(latestVersion, failures = UPDATE_FAI
|
|
|
141
169
|
* 📖 Each PM has different syntax for global install — this normalises them.
|
|
142
170
|
* @param {'npm' | 'bun' | 'pnpm' | 'yarn'} pm
|
|
143
171
|
* @param {string} version
|
|
172
|
+
* @param {{ prefix?: string, bin?: string }} [options]
|
|
144
173
|
* @returns {{ bin: string, args: string[] }}
|
|
145
174
|
*/
|
|
146
|
-
export function getInstallArgs(pm, version) {
|
|
147
|
-
const pkg =
|
|
175
|
+
export function getInstallArgs(pm, version, options = {}) {
|
|
176
|
+
const pkg = `${PACKAGE_NAME}@${version}`
|
|
148
177
|
switch (pm) {
|
|
149
178
|
case 'bun': return { bin: 'bun', args: ['add', '-g', pkg] }
|
|
150
179
|
case 'pnpm': return { bin: 'pnpm', args: ['add', '-g', pkg] }
|
|
151
180
|
case 'yarn': return { bin: 'yarn', args: ['global', 'add', pkg] }
|
|
152
|
-
default:
|
|
181
|
+
default: {
|
|
182
|
+
const args = ['i', '-g']
|
|
183
|
+
if (options.prefix) args.push('--prefix', options.prefix)
|
|
184
|
+
args.push(pkg, '--prefer-online')
|
|
185
|
+
return { bin: options.bin || 'npm', args }
|
|
186
|
+
}
|
|
153
187
|
}
|
|
154
188
|
}
|
|
155
189
|
|
|
190
|
+
function shellQuoteArg(arg) {
|
|
191
|
+
const value = String(arg)
|
|
192
|
+
if (/^[A-Za-z0-9_./:@+=,-]+$/.test(value)) return value
|
|
193
|
+
return `'${value.replace(/'/g, "'\\''")}'`
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function getCurrentInstallOptions(pm = detectPackageManager()) {
|
|
197
|
+
const installTarget = pm === 'npm' ? resolveCurrentNpmInstallTarget() : null
|
|
198
|
+
return pm === 'npm' && installTarget ? {
|
|
199
|
+
prefix: installTarget.prefix,
|
|
200
|
+
bin: installTarget.bin,
|
|
201
|
+
} : {}
|
|
202
|
+
}
|
|
203
|
+
|
|
156
204
|
/**
|
|
157
205
|
* 📖 getManualInstallCmd: human-readable command string for error / fallback messages.
|
|
158
206
|
* @param {'npm' | 'bun' | 'pnpm' | 'yarn'} pm
|
|
159
207
|
* @param {string} version
|
|
208
|
+
* @param {{ prefix?: string, bin?: string }} [options]
|
|
160
209
|
* @returns {string}
|
|
161
210
|
*/
|
|
162
|
-
export function getManualInstallCmd(pm, version) {
|
|
163
|
-
const { bin, args } = getInstallArgs(pm, version)
|
|
164
|
-
return
|
|
211
|
+
export function getManualInstallCmd(pm, version, options = {}) {
|
|
212
|
+
const { bin, args } = getInstallArgs(pm, version, options)
|
|
213
|
+
return [bin, ...args].map(shellQuoteArg).join(' ')
|
|
165
214
|
}
|
|
166
215
|
|
|
167
216
|
/**
|
|
@@ -262,14 +311,16 @@ export async function enforceMandatoryStartupUpdate(config, options = {}) {
|
|
|
262
311
|
base.allowedOutdated = true
|
|
263
312
|
base.warningMessage = buildOutdatedWarningMessage(latestVersion, failures)
|
|
264
313
|
console.log(chalk.red(` ${base.warningMessage}`))
|
|
265
|
-
|
|
314
|
+
const pm = detectPackageManager()
|
|
315
|
+
console.log(chalk.dim(` Manual update: ${getManualInstallCmd(pm, latestVersion, getCurrentInstallOptions(pm))}`))
|
|
266
316
|
console.log()
|
|
267
317
|
return base
|
|
268
318
|
}
|
|
269
319
|
|
|
270
320
|
base.blocked = true
|
|
271
321
|
console.log(chalk.red(' ✖ Mandatory update failed. FCM will retry on the next launch.'))
|
|
272
|
-
|
|
322
|
+
const pm = detectPackageManager()
|
|
323
|
+
console.log(chalk.dim(` Attempt ${failures}/${UPDATE_FAILURE_THRESHOLD}. Manual update: ${getManualInstallCmd(pm, latestVersion, getCurrentInstallOptions(pm))}`))
|
|
273
324
|
console.log()
|
|
274
325
|
return base
|
|
275
326
|
}
|
|
@@ -305,9 +356,10 @@ export async function fetchLastReleaseDate() {
|
|
|
305
356
|
* 📖 Bun installs to ~/.bun/install/global/ (always user-writable) so sudo is never needed.
|
|
306
357
|
* 📖 For npm/pnpm/yarn we probe their global root/prefix paths and check writability.
|
|
307
358
|
* @param {'npm' | 'bun' | 'pnpm' | 'yarn'} pm
|
|
359
|
+
* @param {{ packageRoot: string, prefix: string, bin: string } | null} [installTarget]
|
|
308
360
|
* @returns {{ needsSudo: boolean, checkedPath: string|null }}
|
|
309
361
|
*/
|
|
310
|
-
function detectGlobalInstallPermission(pm) {
|
|
362
|
+
function detectGlobalInstallPermission(pm, installTarget = null) {
|
|
311
363
|
if (pm === 'bun') {
|
|
312
364
|
return { needsSudo: false, checkedPath: null }
|
|
313
365
|
}
|
|
@@ -326,13 +378,20 @@ function detectGlobalInstallPermission(pm) {
|
|
|
326
378
|
if (dir) candidates.push(dir)
|
|
327
379
|
} catch {}
|
|
328
380
|
} else {
|
|
381
|
+
if (installTarget?.prefix) {
|
|
382
|
+
candidates.push(join(installTarget.prefix, 'lib', 'node_modules'))
|
|
383
|
+
candidates.push(installTarget.prefix)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const npmBin = installTarget?.bin || 'npm'
|
|
387
|
+
|
|
329
388
|
try {
|
|
330
|
-
const npmRoot = execFileSync(
|
|
389
|
+
const npmRoot = execFileSync(npmBin, ['root', '-g'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim()
|
|
331
390
|
if (npmRoot) candidates.push(npmRoot)
|
|
332
391
|
} catch {}
|
|
333
392
|
|
|
334
393
|
try {
|
|
335
|
-
const npmPrefix = execFileSync(
|
|
394
|
+
const npmPrefix = execFileSync(npmBin, ['prefix', '-g'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim()
|
|
336
395
|
if (npmPrefix) candidates.push(npmPrefix)
|
|
337
396
|
} catch {}
|
|
338
397
|
}
|
|
@@ -348,6 +407,28 @@ function detectGlobalInstallPermission(pm) {
|
|
|
348
407
|
return { needsSudo: false, checkedPath: candidates[0] || null }
|
|
349
408
|
}
|
|
350
409
|
|
|
410
|
+
function readCurrentPackageVersion() {
|
|
411
|
+
try {
|
|
412
|
+
const data = JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8'))
|
|
413
|
+
return typeof data.version === 'string' ? data.version : null
|
|
414
|
+
} catch {
|
|
415
|
+
return null
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function verifyCurrentInstallUpdated(latestVersion, installTarget) {
|
|
420
|
+
if (!installTarget) return
|
|
421
|
+
|
|
422
|
+
const installedVersion = readCurrentPackageVersion()
|
|
423
|
+
if (installedVersion === latestVersion) return
|
|
424
|
+
|
|
425
|
+
const actual = installedVersion ? `v${installedVersion}` : 'an unknown version'
|
|
426
|
+
throw new Error(
|
|
427
|
+
`Update command completed, but the active install at ${installTarget.packageRoot} still reports ${actual}. ` +
|
|
428
|
+
'The package manager likely installed into another global prefix.'
|
|
429
|
+
)
|
|
430
|
+
}
|
|
431
|
+
|
|
351
432
|
/**
|
|
352
433
|
* 📖 hasSudoCommand: lightweight guard so we don't suggest sudo on systems where it does not exist.
|
|
353
434
|
* @returns {boolean}
|
|
@@ -402,11 +483,16 @@ function relaunchCurrentProcess() {
|
|
|
402
483
|
* 📖 installUpdateCommand: run global install using the detected package manager, optionally prefixed with sudo.
|
|
403
484
|
* @param {string} latestVersion
|
|
404
485
|
* @param {boolean} useSudo
|
|
486
|
+
* @param {{ packageRoot: string, prefix: string, bin: string } | null} [installTarget]
|
|
405
487
|
*/
|
|
406
|
-
function installUpdateCommand(latestVersion, useSudo) {
|
|
488
|
+
function installUpdateCommand(latestVersion, useSudo, installTarget = null) {
|
|
407
489
|
const { execFileSync } = require('child_process')
|
|
408
490
|
const pm = detectPackageManager()
|
|
409
|
-
const
|
|
491
|
+
const installOptions = pm === 'npm' && installTarget ? {
|
|
492
|
+
prefix: installTarget.prefix,
|
|
493
|
+
bin: installTarget.bin,
|
|
494
|
+
} : {}
|
|
495
|
+
const { bin, args } = getInstallArgs(pm, latestVersion, installOptions)
|
|
410
496
|
|
|
411
497
|
if (useSudo) {
|
|
412
498
|
execFileSync('sudo', [bin, ...args], { stdio: 'inherit', shell: false })
|
|
@@ -433,7 +519,12 @@ export function runUpdate(latestVersion, options = {}) {
|
|
|
433
519
|
console.log()
|
|
434
520
|
|
|
435
521
|
const pm = detectPackageManager()
|
|
436
|
-
const
|
|
522
|
+
const installTarget = pm === 'npm' ? resolveCurrentNpmInstallTarget() : null
|
|
523
|
+
const installOptions = pm === 'npm' && installTarget ? {
|
|
524
|
+
prefix: installTarget.prefix,
|
|
525
|
+
bin: installTarget.bin,
|
|
526
|
+
} : {}
|
|
527
|
+
const { needsSudo, checkedPath } = detectGlobalInstallPermission(pm, installTarget)
|
|
437
528
|
const sudoAvailable = process.platform !== 'win32' && hasSudoCommand()
|
|
438
529
|
let lastError = null
|
|
439
530
|
|
|
@@ -444,7 +535,8 @@ export function runUpdate(latestVersion, options = {}) {
|
|
|
444
535
|
}
|
|
445
536
|
|
|
446
537
|
try {
|
|
447
|
-
installUpdateCommand(latestVersion, needsSudo && sudoAvailable)
|
|
538
|
+
installUpdateCommand(latestVersion, needsSudo && sudoAvailable, installTarget)
|
|
539
|
+
verifyCurrentInstallUpdated(latestVersion, installTarget)
|
|
448
540
|
console.log()
|
|
449
541
|
console.log(chalk.green(` ✅ Update complete! Version ${latestVersion} installed.`))
|
|
450
542
|
console.log()
|
|
@@ -452,13 +544,14 @@ export function runUpdate(latestVersion, options = {}) {
|
|
|
452
544
|
return { ok: true }
|
|
453
545
|
} catch (err) {
|
|
454
546
|
lastError = err
|
|
455
|
-
const manualCmd = getManualInstallCmd(pm, latestVersion)
|
|
547
|
+
const manualCmd = getManualInstallCmd(pm, latestVersion, installOptions)
|
|
456
548
|
console.log()
|
|
457
549
|
if (isPermissionError(err) && !needsSudo && sudoAvailable) {
|
|
458
550
|
console.log(chalk.yellow(` ⚠ Permission denied during ${pm} global install. Retrying with sudo...`))
|
|
459
551
|
console.log()
|
|
460
552
|
try {
|
|
461
|
-
installUpdateCommand(latestVersion, true)
|
|
553
|
+
installUpdateCommand(latestVersion, true, installTarget)
|
|
554
|
+
verifyCurrentInstallUpdated(latestVersion, installTarget)
|
|
462
555
|
console.log()
|
|
463
556
|
console.log(chalk.green(` ✅ Update complete with sudo! Version ${latestVersion} installed.`))
|
|
464
557
|
console.log()
|
|
@@ -484,5 +577,3 @@ export function runUpdate(latestVersion, options = {}) {
|
|
|
484
577
|
if (exitOnFailure) process.exit(1)
|
|
485
578
|
return { ok: false, error: lastError }
|
|
486
579
|
}
|
|
487
|
-
|
|
488
|
-
|