1- import { readFileSync , existsSync , PathLike } from 'fs' ; // eslint-disable-line no-restricted-imports
1+ import { readFileSync , PathLike } from 'fs' ; // eslint-disable-line no-restricted-imports
22import { readFile , rename , unlink , open } from 'fs/promises' ; // eslint-disable-line no-restricted-imports
33import { LoggerFactory } from '../telemetry/LoggerFactory' ;
44import { isWindows } from './Environment' ;
55import { DoesNotExist } from './Errors' ;
6- import { sleep } from './Retry' ;
6+ import { calculateDelay , sleep } from './Retry' ;
77import { toString } from './String' ;
88
99const log = LoggerFactory . getLogger ( 'File' ) ;
@@ -15,29 +15,31 @@ type Options =
1515 flag ?: string | undefined ;
1616 } ;
1717
18+ const ENOENT = 'ENOENT' ; // No such file or directory
19+ const RETRIABLE_RENAME_CODES = new Set ( [ 'EPERM' , 'EACCES' , 'EBUSY' ] ) ;
20+
21+ function wrapReadEnoentError ( path : PathLike , err : unknown ) : never {
22+ log . error ( err ) ;
23+ if ( isFileNotFoundError ( err ) ) {
24+ throw new DoesNotExist ( toString ( path ) ) ;
25+ }
26+
27+ throw err ;
28+ }
29+
1830export function readFileIfExists ( path : PathLike , options : Options = 'utf8' ) : string {
1931 try {
20- if ( existsSync ( path ) ) {
21- return readFileSync ( path , options ) ;
22- } else {
23- throw new DoesNotExist ( toString ( path ) ) ;
24- }
32+ return readFileSync ( path , options ) ;
2533 } catch ( err ) {
26- log . error ( err ) ;
27- throw err ;
34+ wrapReadEnoentError ( path , err ) ;
2835 }
2936}
3037
3138export async function readFileIfExistsAsync ( path : PathLike , options : Options = 'utf8' ) : Promise < string > {
3239 try {
33- if ( existsSync ( path ) ) {
34- return await readFile ( path , options ) ;
35- } else {
36- throw new DoesNotExist ( toString ( path ) ) ;
37- }
40+ return await readFile ( path , options ) ;
3841 } catch ( err ) {
39- log . error ( err ) ;
40- throw err ;
42+ wrapReadEnoentError ( path , err ) ;
4143 }
4244}
4345
@@ -49,43 +51,31 @@ export function readBufferIfExists(
4951 } | null ,
5052) : Buffer {
5153 try {
52- if ( existsSync ( path ) ) {
53- return readFileSync ( path , options ) ;
54- } else {
55- throw new DoesNotExist ( toString ( path ) ) ;
56- }
54+ return readFileSync ( path , options ) ;
5755 } catch ( err ) {
58- log . error ( err ) ;
59- throw err ;
56+ wrapReadEnoentError ( path , err ) ;
6057 }
6158}
6259
63- export function readBufferIfExistsAsync (
60+ export async function readBufferIfExistsAsync (
6461 path : PathLike ,
6562 options ?: {
6663 encoding ?: null | undefined ;
6764 flag ?: string | undefined ;
6865 } | null ,
6966) : Promise < Buffer > {
7067 try {
71- if ( existsSync ( path ) ) {
72- return readFile ( path , options ) ;
73- } else {
74- throw new DoesNotExist ( toString ( path ) ) ;
75- }
68+ return await readFile ( path , options ) ;
7669 } catch ( err ) {
77- log . error ( err ) ;
78- throw err ;
70+ wrapReadEnoentError ( path , err ) ;
7971 }
8072}
8173
82- const RETRIABLE_RENAME_CODES = new Set ( [ 'EPERM' , 'EACCES' , 'EBUSY' , 'ENOENT' ] ) ;
83-
8474export async function asyncRenameWithRetry (
8575 sourcePath : string ,
8676 destinationPath : string ,
8777 maxRetries = 10 ,
88- retryDelayMs = 50 ,
78+ initialDelayMs = 50 ,
8979) : Promise < void > {
9080 for ( let attempt = 0 ; attempt < maxRetries ; attempt ++ ) {
9181 try {
@@ -95,14 +85,15 @@ export async function asyncRenameWithRetry(
9585 const code = ( error as NodeJS . ErrnoException ) . code ;
9686 if ( ! code || ! RETRIABLE_RENAME_CODES . has ( code ) || attempt === maxRetries - 1 ) {
9787 try {
98- await unlink ( sourcePath ) ;
88+ if ( ! isFileNotFoundError ( error ) ) {
89+ await unlink ( sourcePath ) ;
90+ }
9991 } catch ( err ) {
100- // best-effort tmp cleanup
101- log . error ( err ) ;
92+ log . error ( err , `Best effort tmp cleanup failed for ${ sourcePath } ` ) ;
10293 }
10394 throw error ;
10495 }
105- await sleep ( retryDelayMs ) ;
96+ await sleep ( calculateDelay ( attempt , initialDelayMs ) ) ;
10697 }
10798 }
10899}
@@ -133,7 +124,5 @@ export async function asyncDirSync(dirPath: string): Promise<void> {
133124}
134125
135126export function isFileNotFoundError ( error : unknown ) : boolean {
136- // File was deleted by another process (e.g. a concurrent IDE session sharing the same storage directory)
137- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-explicit-any
138- return ( error as any ) . code === 'ENOENT' ;
127+ return error !== null && typeof error === 'object' && ( error as NodeJS . ErrnoException ) . code === ENOENT ;
139128}
0 commit comments