* PTY-based npm execution for interactive commands (uses node-pty). * * - Web OTP - either open URL in browser if openUrls is true or passes the URL to frontend. If no auth happened within AUTH_URL_TIMEOUT_MS kills the process to unlock the connector. * * - CLI OTP - if we get a classic OTP prom
(
args: string[],
options: ExecNpmOptions = {},
)
| 178 | * - CLI OTP - if we get a classic OTP prompt will either return OTP request to the frontend or will pass sent OTP if its provided |
| 179 | */ |
| 180 | async function execNpmInteractive( |
| 181 | args: string[], |
| 182 | options: ExecNpmOptions = {}, |
| 183 | ): Promise<NpmExecResult> { |
| 184 | const openUrls = options.openUrls === true |
| 185 | const { promise, resolve } = Promise.withResolvers<NpmExecResult>() |
| 186 | |
| 187 | // Lazy-load node-pty so the native addon is only required when interactive mode is actually used. |
| 188 | const pty = await import('@lydell/node-pty') |
| 189 | |
| 190 | const npmArgs = options.otp ? [...args, '--otp', options.otp] : args |
| 191 | |
| 192 | if (!options.silent) { |
| 193 | const displayCmd = options.otp |
| 194 | ? ['npm', ...args, '--otp', '******'].join(' ') |
| 195 | : ['npm', ...args].join(' ') |
| 196 | logCommand(`${displayCmd} (interactive/pty)`) |
| 197 | } |
| 198 | |
| 199 | let output = '' |
| 200 | let resolved = false |
| 201 | let otpPromptSeen = false |
| 202 | let authUrlSeen = false |
| 203 | let enterSent = false |
| 204 | let authUrlTimeout: ReturnType<typeof setTimeout> | null = null |
| 205 | let authUrlTimedOut = false |
| 206 | |
| 207 | const env = createNpmEnv() |
| 208 | |
| 209 | // When openUrls is false, tell npm not to open the browser. |
| 210 | // npm still prints the auth URL and polls doneUrl |
| 211 | if (!openUrls) { |
| 212 | env.npm_config_browser = 'false' |
| 213 | } |
| 214 | |
| 215 | const child = pty.spawn('npm', npmArgs, { |
| 216 | name: 'xterm-256color', |
| 217 | cols: 120, |
| 218 | rows: 30, |
| 219 | cwd: options.cwd, |
| 220 | env, |
| 221 | }) |
| 222 | |
| 223 | // General timeout: 5 minutes (covers non-auth interactive commands) |
| 224 | const timeout = setTimeout(() => { |
| 225 | if (resolved) return |
| 226 | logDebug('Interactive command timed out', { output }) |
| 227 | child.kill() |
| 228 | }, 300000) |
| 229 | |
| 230 | child.onData((data: string) => { |
| 231 | output += data |
| 232 | const clean = stripAnsi(data) |
| 233 | logDebug('pty data:', { text: clean.trim() }) |
| 234 | |
| 235 | const cleanAll = stripAnsi(output) |
| 236 | |
| 237 | // Detect auth URL in output and notify the caller. |
no test coverage detected