@@ -1144,7 +1144,50 @@ async function promptManualCallback(
11441144 console . log ( "" ) ;
11451145 console . log ( stylePromptText ( UI_COPY . oauth . pastePrompt , "accent" ) ) ;
11461146 }
1147- const answer = await rl . question ( useInteractivePrompt ? "◆ " : "" ) ;
1147+ const answer = useInteractivePrompt
1148+ ? await rl . question ( "◆ " )
1149+ : await new Promise < string | null > ( ( resolve , reject ) => {
1150+ if ( input . readableEnded || input . destroyed ) {
1151+ resolve ( null ) ;
1152+ return ;
1153+ }
1154+ let settled = false ;
1155+ const handleInputClosed = ( ) => {
1156+ if ( settled ) return ;
1157+ settled = true ;
1158+ input . off ( "end" , handleInputClosed ) ;
1159+ input . off ( "close" , handleInputClosed ) ;
1160+ resolve ( null ) ;
1161+ } ;
1162+ const finish = ( value : string ) => {
1163+ if ( settled ) return ;
1164+ settled = true ;
1165+ input . off ( "end" , handleInputClosed ) ;
1166+ input . off ( "close" , handleInputClosed ) ;
1167+ resolve ( value ) ;
1168+ } ;
1169+ const fail = ( error : unknown ) => {
1170+ if ( settled ) return ;
1171+ settled = true ;
1172+ input . off ( "end" , handleInputClosed ) ;
1173+ input . off ( "close" , handleInputClosed ) ;
1174+ reject ( error ) ;
1175+ } ;
1176+ rl . question ( "" )
1177+ . then ( ( value ) => finish ( value ) )
1178+ . catch ( ( error ) => {
1179+ if ( isAbortError ( error ) || isReadlineClosedError ( error ) ) {
1180+ handleInputClosed ( ) ;
1181+ return ;
1182+ }
1183+ fail ( error ) ;
1184+ } ) ;
1185+ input . once ( "end" , handleInputClosed ) ;
1186+ input . once ( "close" , handleInputClosed ) ;
1187+ } ) ;
1188+ if ( answer === null ) {
1189+ return null ;
1190+ }
11481191 if ( answer . includes ( "\u001b" ) ) {
11491192 return null ;
11501193 }
@@ -1163,7 +1206,7 @@ async function promptManualCallback(
11631206 if ( parsed . state && parsed . state !== state ) return null ;
11641207 return parsed . code ;
11651208 } catch ( error ) {
1166- if ( isAbortError ( error ) ) {
1209+ if ( isAbortError ( error ) || isReadlineClosedError ( error ) ) {
11671210 return null ;
11681211 }
11691212 throw error ;
@@ -1172,6 +1215,17 @@ async function promptManualCallback(
11721215 }
11731216}
11741217
1218+ function isReadlineClosedError ( error : unknown ) : boolean {
1219+ if ( ! ( error instanceof Error ) ) {
1220+ return false ;
1221+ }
1222+ const errorCode =
1223+ typeof error === "object" && error !== null && "code" in error
1224+ ? String ( ( error as { code ?: unknown } ) . code )
1225+ : "" ;
1226+ return errorCode === "ERR_USE_AFTER_CLOSE" || / r e a d l i n e w a s c l o s e d / i. test ( error . message ) ;
1227+ }
1228+
11751229type OAuthSignInMode = "browser" | "manual" | "cancel" ;
11761230
11771231async function promptOAuthSignInMode ( ) : Promise < OAuthSignInMode > {
@@ -1471,28 +1525,8 @@ async function runOAuthFlow(
14711525) : Promise < TokenResult > {
14721526 const { pkce, state, url } = await createAuthorizationFlow ( { forceNewLogin } ) ;
14731527 const preferManualMode = options . manual || isBrowserLaunchSuppressed ( ) ;
1474- let oauthServer : Awaited < ReturnType < typeof startLocalOAuthServer > > | null = null ;
1475- try {
1476- oauthServer = await startLocalOAuthServer ( { state } ) ;
1477- } catch ( serverError ) {
1478- log . warn (
1479- "Local OAuth callback server unavailable; falling back to manual callback entry." ,
1480- serverError instanceof Error
1481- ? {
1482- message : serverError . message ,
1483- stack : serverError . stack ,
1484- code :
1485- typeof serverError === "object" &&
1486- serverError !== null &&
1487- "code" in serverError
1488- ? String ( serverError . code )
1489- : undefined ,
1490- }
1491- : { error : String ( serverError ) } ,
1492- ) ;
1493- oauthServer = null ;
1494- }
14951528 let code : string | null = null ;
1529+ let oauthServer : Awaited < ReturnType < typeof startLocalOAuthServer > > | null = null ;
14961530 try {
14971531 const signInMode = preferManualMode ? "manual" : await promptOAuthSignInMode ( ) ;
14981532 if ( signInMode === "cancel" ) {
@@ -1503,6 +1537,29 @@ async function runOAuthFlow(
15031537 } ;
15041538 }
15051539
1540+ if ( signInMode === "browser" ) {
1541+ try {
1542+ oauthServer = await startLocalOAuthServer ( { state } ) ;
1543+ } catch ( serverError ) {
1544+ log . warn (
1545+ "Local OAuth callback server unavailable; falling back to manual callback entry." ,
1546+ serverError instanceof Error
1547+ ? {
1548+ message : serverError . message ,
1549+ stack : serverError . stack ,
1550+ code :
1551+ typeof serverError === "object" &&
1552+ serverError !== null &&
1553+ "code" in serverError
1554+ ? String ( serverError . code )
1555+ : undefined ,
1556+ }
1557+ : { error : String ( serverError ) } ,
1558+ ) ;
1559+ oauthServer = null ;
1560+ }
1561+ }
1562+
15061563 if ( signInMode === "browser" ) {
15071564 const opened = openBrowserUrl ( url ) ;
15081565 if ( opened ) {
@@ -1529,7 +1586,7 @@ async function runOAuthFlow(
15291586 ) ;
15301587 }
15311588
1532- const waitingForCallback = signInMode === "browser" && oauthServer ?. ready === true ;
1589+ const waitingForCallback = oauthServer ?. ready === true ;
15331590 if ( waitingForCallback && oauthServer ) {
15341591 console . log ( stylePromptText ( UI_COPY . oauth . waitingCallback , "muted" ) ) ;
15351592 const callbackResult = await oauthServer . waitForCode ( state ) ;
@@ -1541,7 +1598,9 @@ async function runOAuthFlow(
15411598 stylePromptText (
15421599 waitingForCallback
15431600 ? UI_COPY . oauth . callbackMissed
1544- : UI_COPY . oauth . callbackUnavailable ,
1601+ : signInMode === "manual"
1602+ ? UI_COPY . oauth . callbackBypassed
1603+ : UI_COPY . oauth . callbackUnavailable ,
15451604 "warning" ,
15461605 ) ,
15471606 ) ;
0 commit comments