Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"scripts": {
"build": "tsdown",
"dev": "NPMX_CLI_DEV=true node src/cli.ts",
"dev:debug": "DEBUG=npmx-connector NPMX_CLI_DEV=true node src/cli.ts",
"test:types": "tsc --noEmit"
},
"dependencies": {
Expand Down
19 changes: 16 additions & 3 deletions cli/src/npm-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { tmpdir } from 'node:os'
import { join } from 'node:path'
import * as v from 'valibot'
import { PackageNameSchema, UsernameSchema, OrgNameSchema, ScopeTeamSchema } from './schemas.ts'
import { logCommand, logSuccess, logError } from './logger.ts'
import { logCommand, logSuccess, logError, logDebug } from './logger.ts'

const execFileAsync = promisify(execFile)

Expand Down Expand Up @@ -75,10 +75,15 @@ function detectOtpRequired(stderr: string): boolean {
'EOTP',
'one-time password',
'This operation requires a one-time password',
'OTP required for authentication',
'--otp=<code>',
]
const lowerStderr = stderr.toLowerCase()
return otpPatterns.some(pattern => lowerStderr.includes(pattern.toLowerCase()))
logDebug('Checking for OTP requirement in stderr:', stderr)
logDebug('OTP patterns:', otpPatterns)
const result = otpPatterns.some(pattern => lowerStderr.includes(pattern.toLowerCase()))
logDebug('OTP required:', result)
return result
}

function detectAuthFailure(stderr: string): boolean {
Expand All @@ -96,7 +101,11 @@ function detectAuthFailure(stderr: string): boolean {
'npm adduser',
]
const lowerStderr = stderr.toLowerCase()
return authPatterns.some(pattern => lowerStderr.includes(pattern.toLowerCase()))
logDebug('Checking for auth failure in stderr:', stderr)
logDebug('Auth patterns:', authPatterns)
const result = authPatterns.some(pattern => lowerStderr.includes(pattern.toLowerCase()))
logDebug('Auth failure:', result)
return result
}

function filterNpmWarnings(stderr: string): string {
Expand All @@ -123,6 +132,7 @@ async function execNpm(
}

try {
logDebug('Executing npm command:', { command: 'npm', args: npmArgs })
// Use execFile instead of exec to avoid shell injection vulnerabilities
// On Windows, shell: true is required to execute .cmd files (like npm.cmd)
// On Unix, we keep it false for better security and performance
Expand All @@ -132,6 +142,8 @@ async function execNpm(
shell: process.platform === 'win32',
})

logDebug('Command succeeded:', { stdout, stderr })

if (!options.silent) {
logSuccess('Done')
}
Expand All @@ -144,6 +156,7 @@ async function execNpm(
} catch (error) {
const err = error as { stdout?: string; stderr?: string; code?: number }
const stderr = err.stderr?.trim() ?? String(error)
logDebug('Command failed:', { error, stdout: err.stdout, stderr: err.stderr, code: err.code })
const requiresOtp = detectOtpRequired(stderr)
const authFailure = detectAuthFailure(stderr)

Expand Down
Loading