@wwkit/opm 1.0.8 → 1.0.9

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.
@@ -290,8 +290,31 @@ export class NpmManager extends PackageManager {
290
290
  return null
291
291
  }
292
292
 
293
+ /**
294
+ * 通过 PATH 查找 bin 路径(Linux: which / Windows: where)
295
+ * @param {string} name - bin 名
296
+ * @returns {string|null} bin 路径或 null
297
+ * @private
298
+ */
299
+ _whichBin(name) {
300
+ const cmd = process.platform === 'win32' ? `where ${name}` : `which ${name}`
301
+ try {
302
+ const out = execSync(cmd, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] })
303
+ const lines = out.trim().split(/\r?\n/).filter(Boolean)
304
+ return lines[0] || null
305
+ } catch {
306
+ return null
307
+ }
308
+ }
309
+
293
310
  /**
294
311
  * 查询全局安装的包
312
+ *
313
+ * 三段式检查:
314
+ * 1. npm list -g <name>(当前激活 node 全局)
315
+ * 2. npm root -g 目录直查(同上,兜底)
316
+ * 3. PATH 回退:which <name> → realpath → 推导全局 node_modules
317
+ * 覆盖通过其他 node 版本/路径安装的包(如非 nvm 管理的独立 node)
295
318
  * @param {string} name - 包名
296
319
  * @returns {Promise<InstalledResult>}
297
320
  * @private
@@ -316,6 +339,30 @@ export class NpmManager extends PackageManager {
316
339
  }
317
340
  } catch {}
318
341
 
342
+ // PATH 回退:which <name> → realpath → 推导全局 node_modules
343
+ try {
344
+ const binPath = this._whichBin(name)
345
+ if (binPath) {
346
+ const realBin = fs.realpathSync(binPath)
347
+ const binDir = path.dirname(realBin)
348
+ const candidates = [
349
+ path.join(binDir, '..', 'package.json'),
350
+ path.join(binDir, '..', 'lib', 'node_modules', name, 'package.json'),
351
+ path.join(binDir, '..', 'node_modules', name, 'package.json')
352
+ ]
353
+ for (const pkgPath of candidates) {
354
+ try {
355
+ if (fs.existsSync(pkgPath)) {
356
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
357
+ return { name, installed: true, version: pkg.version, path: path.dirname(pkgPath), global: true }
358
+ }
359
+ } catch {}
360
+ }
361
+ // 找到 bin 但推导不到 package.json(极端情况)
362
+ return { name, installed: true, global: true }
363
+ }
364
+ } catch {}
365
+
319
366
  return { name, installed: false, global: true }
320
367
  }
321
368
 
@@ -375,48 +422,6 @@ export class NpmManager extends PackageManager {
375
422
  return { name, versions: [], registry }
376
423
  }
377
424
 
378
- async listInstalled(opts = {}) {
379
- if (opts.global) {
380
- try {
381
- const data = this._execJson(['list', '-g', '--depth=0'])
382
- const deps = data?.dependencies || {}
383
- return Object.entries(deps)
384
- .filter(([, info]) => info && info.version)
385
- .map(([name, info]) => ({ name, version: info.version, global: true }))
386
- } catch (err) {
387
- throw new Error(`npm list -g failed: ${err.message}`)
388
- }
389
- }
390
-
391
- const declared = this._readDeclaredDeps()
392
- const workspaces = this._readWorkspacePackages()
393
- const items = []
394
- const seen = new Set()
395
-
396
- for (const { name, version, dir } of workspaces) {
397
- if (seen.has(name)) continue
398
- seen.add(name)
399
- const resolved = this._resolveInstalled(name)
400
- items.push({
401
- name,
402
- version: resolved ? resolved.version : version,
403
- path: resolved ? resolved.path : dir
404
- })
405
- }
406
-
407
- for (const [name, declaredVersion] of Object.entries(declared)) {
408
- if (seen.has(name)) continue
409
- seen.add(name)
410
- const resolved = this._resolveInstalled(name)
411
- items.push({
412
- name,
413
- version: resolved ? resolved.version : declaredVersion
414
- })
415
- }
416
-
417
- return items
418
- }
419
-
420
425
  async listOutdated(opts = {}) {
421
426
  try {
422
427
  const args = ['outdated']
@@ -455,7 +460,8 @@ export class NpmManager extends PackageManager {
455
460
 
456
461
  /**
457
462
  * 构造 --dangerously-allow-all-scripts 参数(仅 npm >= 11 支持)
458
- * npm < 11 默认放行所有脚本,无需此 flag;npm >= 11 默认阻止,需显式放行
463
+ * npm < 11 默认放行所有脚本,无需此 flag;npm >= 11 默认阻止,需显式放行。
464
+ * install/upgrade 始终追加,保持与 npm < 11 一致的脚本行为(postinstall 可执行)。
459
465
  * @param {string} name - 包名(保留签名兼容,未使用)
460
466
  * @returns {string[]}
461
467
  * @private
@@ -470,7 +476,7 @@ export class NpmManager extends PackageManager {
470
476
  if (opts.global) args.push('-g')
471
477
  args.push(...this._proxyArgs(opts.proxy))
472
478
  args.push(...this._registryArgs())
473
- if (opts.allowScripts) args.push(...this._allowScriptsArgs(name))
479
+ args.push(...this._allowScriptsArgs(name))
474
480
 
475
481
  this._execInherit(args)
476
482
  return { name, version: version || 'latest', action: 'installed' }
@@ -489,7 +495,7 @@ export class NpmManager extends PackageManager {
489
495
  if (opts.global) args.push('-g')
490
496
  args.push(...this._proxyArgs(opts.proxy))
491
497
  args.push(...this._registryArgs())
492
- if (opts.allowScripts) args.push(...this._allowScriptsArgs(name))
498
+ args.push(...this._allowScriptsArgs(name))
493
499
 
494
500
  this._execInherit(args)
495
501
  return { name, version: 'latest', action: 'upgraded' }
@@ -429,6 +429,23 @@ export class PipManager extends PackageManager {
429
429
  return null
430
430
  }
431
431
 
432
+ /**
433
+ * 通过 PATH 查找 bin 路径(Linux: which / Windows: where)
434
+ * @param {string} name - bin 名
435
+ * @returns {string|null} bin 路径或 null
436
+ * @private
437
+ */
438
+ _whichBin(name) {
439
+ const cmd = process.platform === 'win32' ? `where ${name}` : `which ${name}`
440
+ try {
441
+ const out = execSync(cmd, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] })
442
+ const lines = out.trim().split(/\r?\n/).filter(Boolean)
443
+ return lines[0] || null
444
+ } catch {
445
+ return null
446
+ }
447
+ }
448
+
432
449
  async isInstalled(name, opts = {}) {
433
450
  const info = this._parsePipShow(name, opts)
434
451
  if (info) {
@@ -441,6 +458,13 @@ export class PipManager extends PackageManager {
441
458
  }
442
459
  }
443
460
 
461
+ if (opts.global) {
462
+ const binPath = this._whichBin(name)
463
+ if (binPath) {
464
+ return { name, installed: true, global: true, path: binPath }
465
+ }
466
+ }
467
+
444
468
  return { name, installed: false, ...(opts.global ? { global: true } : {}) }
445
469
  }
446
470
 
@@ -487,24 +511,6 @@ export class PipManager extends PackageManager {
487
511
  return { name, versions: [], registry }
488
512
  }
489
513
 
490
- async listInstalled(opts = {}) {
491
- try {
492
- const out = this._execPip(['list', '--format=freeze'], opts)
493
- const items = []
494
- for (const line of out.split('\n')) {
495
- const trimmed = line.trim()
496
- if (!trimmed) continue
497
- const match = trimmed.match(/^([a-zA-Z0-9_-]+)==(.+)$/)
498
- if (match) {
499
- items.push({ name: match[1], version: match[2], ...(opts.global ? { global: true } : {}) })
500
- }
501
- }
502
- return items
503
- } catch (err) {
504
- throw new Error(`pip list failed: ${err.message}`)
505
- }
506
- }
507
-
508
514
  async listOutdated(opts = {}) {
509
515
  try {
510
516
  const args = ['list', '--outdated', '--format=json']
@@ -175,6 +175,9 @@ export class WingetManager extends PackageManager {
175
175
  }
176
176
 
177
177
  async isInstalled(name, opts = {}) {
178
+ if (opts.global === false) {
179
+ return { name, installed: false, hint: 'no project scope (system-level manager)' }
180
+ }
178
181
  const out = this._exec(
179
182
  ['list', name, '--accept-source-agreements', '--disable-interactivity'],
180
183
  { allowNonZero: true }
@@ -218,19 +221,6 @@ export class WingetManager extends PackageManager {
218
221
  }
219
222
  }
220
223
 
221
- async listInstalled(opts = {}) {
222
- const out = this._exec(
223
- ['list', '--accept-source-agreements', '--disable-interactivity'],
224
- { allowNonZero: true }
225
- )
226
- return this._parseTable(out).map((r) => ({
227
- name: r.Name || r.Id,
228
- id: r.Id,
229
- version: r.Version || 'unknown',
230
- source: r.Source || '',
231
- }))
232
- }
233
-
234
224
  async listOutdated(opts = {}) {
235
225
  const out = this._exec(
236
226
  ['list', '--upgrade-available', '--accept-source-agreements', '--disable-interactivity'],