mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-26 04:25:46 +02:00
Revert "Fix npm global install permission error by using user-level prefix"
This commit is contained in:
@@ -4,12 +4,10 @@
|
||||
*/
|
||||
import { spawn } from 'child_process'
|
||||
import { platform } from 'os'
|
||||
import * as fs from 'fs'
|
||||
import type { BrowserWindow } from 'electron'
|
||||
import { IPC_CHANNELS } from '../../shared/ipc-channels'
|
||||
import type { InstallStep, InstallResult } from '../../shared/electron-api'
|
||||
import { commandExists } from './agent-check'
|
||||
import { getEnhancedPath, getNpmUserPrefix } from './path'
|
||||
|
||||
interface InstallOptions {
|
||||
window: BrowserWindow
|
||||
@@ -33,8 +31,8 @@ function spawnWithProgress(
|
||||
useShell: boolean = false
|
||||
): Promise<{ success: boolean; error?: string }> {
|
||||
return new Promise((resolve) => {
|
||||
// Use enhanced PATH that includes user-level npm global directory
|
||||
const enhancedPath = getEnhancedPath()
|
||||
// Ensure common paths are in PATH for npm, node, etc.
|
||||
const enhancedPath = `/usr/local/bin:/opt/homebrew/bin:${process.env.PATH || ''}`
|
||||
|
||||
console.log(`[agent-install] Spawning: ${command} ${args.join(' ')} (shell: ${useShell})`)
|
||||
|
||||
@@ -111,19 +109,14 @@ async function installClaudeCLI(
|
||||
}
|
||||
|
||||
/**
|
||||
* Install claude-code-acp via npm to user-level directory
|
||||
* Install claude-code-acp via npm
|
||||
*/
|
||||
async function installClaudeCodeACP(
|
||||
onProgress: (message: string) => void
|
||||
): Promise<{ success: boolean; error?: string }> {
|
||||
const prefix = getNpmUserPrefix()
|
||||
|
||||
// Ensure the user-level npm global directory exists
|
||||
await fs.promises.mkdir(prefix, { recursive: true }).catch(() => {})
|
||||
|
||||
return spawnWithProgress(
|
||||
'npm',
|
||||
['install', '-g', '--prefix', prefix, '@zed-industries/claude-code-acp'],
|
||||
['install', '-g', '@zed-industries/claude-code-acp'],
|
||||
onProgress,
|
||||
true
|
||||
)
|
||||
@@ -224,41 +217,21 @@ async function installClaudeCode(options: InstallOptions): Promise<InstallResult
|
||||
}
|
||||
|
||||
/**
|
||||
* Install Codex CLI via npm to user-level directory
|
||||
* Install Codex CLI via npm
|
||||
*/
|
||||
async function installCodexCLI(
|
||||
onProgress: (message: string) => void
|
||||
): Promise<{ success: boolean; error?: string }> {
|
||||
const prefix = getNpmUserPrefix()
|
||||
|
||||
// Ensure the user-level npm global directory exists
|
||||
await fs.promises.mkdir(prefix, { recursive: true }).catch(() => {})
|
||||
|
||||
return spawnWithProgress(
|
||||
'npm',
|
||||
['install', '-g', '--prefix', prefix, '@openai/codex'],
|
||||
onProgress,
|
||||
true
|
||||
)
|
||||
return spawnWithProgress('npm', ['install', '-g', '@openai/codex'], onProgress, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Install codex-acp via npm to user-level directory
|
||||
* Install codex-acp via npm
|
||||
*/
|
||||
async function installCodexACP(
|
||||
onProgress: (message: string) => void
|
||||
): Promise<{ success: boolean; error?: string }> {
|
||||
const prefix = getNpmUserPrefix()
|
||||
|
||||
// Ensure the user-level npm global directory exists
|
||||
await fs.promises.mkdir(prefix, { recursive: true }).catch(() => {})
|
||||
|
||||
return spawnWithProgress(
|
||||
'npm',
|
||||
['install', '-g', '--prefix', prefix, '@zed-industries/codex-acp'],
|
||||
onProgress,
|
||||
true
|
||||
)
|
||||
return spawnWithProgress('npm', ['install', '-g', '@zed-industries/codex-acp'], onProgress, true)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -387,9 +360,9 @@ export async function installAgent(options: InstallOptions): Promise<InstallResu
|
||||
/**
|
||||
* Format installation error messages for user display
|
||||
*/
|
||||
export function formatInstallError(error: string): string {
|
||||
function formatInstallError(error: string): string {
|
||||
if (error.includes('EACCES') || error.includes('permission denied')) {
|
||||
return 'Permission denied. Please run: chmod -R u+w ~/.npm-global'
|
||||
return 'Permission denied. Please check your permissions and try again.'
|
||||
}
|
||||
if (error.includes('Could not resolve host') || error.includes('ENOTFOUND')) {
|
||||
return 'Network error. Please check your internet connection and try again.'
|
||||
|
||||
@@ -10,23 +10,11 @@ import { homedir } from 'node:os'
|
||||
export function getEnhancedPath(): string {
|
||||
const home = homedir()
|
||||
const customPaths = [
|
||||
`${home}/.npm-global/bin`, // User-level npm global directory
|
||||
`${home}/.opencode/bin`,
|
||||
`${home}/.claude/local/bin`,
|
||||
`${home}/.claude/local`,
|
||||
`${home}/.bun/bin`,
|
||||
`${home}/.local/bin`,
|
||||
'/opt/homebrew/bin',
|
||||
'/usr/local/bin'
|
||||
]
|
||||
return `${customPaths.join(':')}:${process.env.PATH || ''}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user-level npm global prefix directory
|
||||
* Used for installing npm packages without requiring sudo
|
||||
*/
|
||||
export function getNpmUserPrefix(): string {
|
||||
const home = homedir()
|
||||
return `${home}/.npm-global`
|
||||
}
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { homedir } from 'node:os'
|
||||
|
||||
// Mock child_process
|
||||
vi.mock('child_process', () => ({
|
||||
spawn: vi.fn()
|
||||
}))
|
||||
|
||||
// Mock fs
|
||||
vi.mock('fs', () => ({
|
||||
promises: {
|
||||
mkdir: vi.fn().mockResolvedValue(undefined)
|
||||
}
|
||||
}))
|
||||
|
||||
// Mock os module
|
||||
vi.mock('os', () => ({
|
||||
platform: vi.fn().mockReturnValue('darwin')
|
||||
}))
|
||||
|
||||
// Mock path utilities
|
||||
vi.mock('../../../../src/main/utils/path', () => ({
|
||||
getEnhancedPath: vi
|
||||
.fn()
|
||||
.mockReturnValue(
|
||||
'/Users/test/.npm-global/bin:/Users/test/.opencode/bin:/usr/local/bin:/usr/bin:/bin'
|
||||
),
|
||||
getNpmUserPrefix: vi.fn().mockReturnValue('/Users/test/.npm-global')
|
||||
}))
|
||||
|
||||
// Mock agent-check
|
||||
vi.mock('../../../../src/main/utils/agent-check', () => ({
|
||||
commandExists: vi.fn().mockResolvedValue({ exists: false })
|
||||
}))
|
||||
|
||||
import { formatInstallError } from '../../../../src/main/utils/agent-install'
|
||||
import { getNpmUserPrefix, getEnhancedPath } from '../../../../src/main/utils/path'
|
||||
|
||||
describe('agent-install', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('getNpmUserPrefix', () => {
|
||||
it('should return user-level npm global directory path', () => {
|
||||
const prefix = getNpmUserPrefix()
|
||||
expect(prefix).toBe('/Users/test/.npm-global')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getEnhancedPath', () => {
|
||||
it('should include npm-global/bin in PATH', () => {
|
||||
const path = getEnhancedPath()
|
||||
expect(path).toContain('.npm-global/bin')
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatInstallError', () => {
|
||||
it('should format EACCES permission errors', () => {
|
||||
const error = 'npm ERR! code EACCES\nnpm ERR! syscall mkdir'
|
||||
const result = formatInstallError(error)
|
||||
expect(result).toBe('Permission denied. Please run: chmod -R u+w ~/.npm-global')
|
||||
})
|
||||
|
||||
it('should format permission denied errors', () => {
|
||||
const error = 'Error: EACCES: permission denied, mkdir /some/path'
|
||||
const result = formatInstallError(error)
|
||||
expect(result).toBe('Permission denied. Please run: chmod -R u+w ~/.npm-global')
|
||||
})
|
||||
|
||||
it('should format network errors with ENOTFOUND', () => {
|
||||
const error = 'npm ERR! code ENOTFOUND\nnpm ERR! network'
|
||||
const result = formatInstallError(error)
|
||||
expect(result).toBe('Network error. Please check your internet connection and try again.')
|
||||
})
|
||||
|
||||
it('should format network errors with host resolution', () => {
|
||||
const error = 'Could not resolve host: registry.npmjs.org'
|
||||
const result = formatInstallError(error)
|
||||
expect(result).toBe('Network error. Please check your internet connection and try again.')
|
||||
})
|
||||
|
||||
it('should format command not found errors', () => {
|
||||
const error = 'spawn npm ENOENT'
|
||||
const result = formatInstallError(error)
|
||||
expect(result).toBe('Command not found. Please ensure required tools are installed.')
|
||||
})
|
||||
|
||||
it('should pass through unknown errors unchanged', () => {
|
||||
const error = 'Some unknown error message'
|
||||
const result = formatInstallError(error)
|
||||
expect(result).toBe('Some unknown error message')
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user