Skip to content

Commit 5eda2d9

Browse files
committed
feat(fs): add safeMkdir and safeMkdirSync functions
Add safe directory creation functions that ignore EEXIST errors: - safeMkdir: Async version - safeMkdirSync: Sync version Both functions: - Wrap fs.mkdir/mkdirSync with EEXIST error handling - Silently ignore directory-already-exists errors - Re-throw all other errors (permissions, invalid paths, etc.) - Work reliably in concurrent/multi-process scenarios This prevents race conditions when creating directories that might already exist.
1 parent ae85140 commit 5eda2d9

1 file changed

Lines changed: 94 additions & 0 deletions

File tree

src/fs.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import type { Abortable } from 'node:events'
77
import type {
88
Dirent,
9+
MakeDirectoryOptions,
910
ObjectEncodingOptions,
1011
OpenMode,
1112
PathLike,
@@ -1275,6 +1276,99 @@ export function safeDeleteSync(
12751276
})
12761277
}
12771278

1279+
/**
1280+
* Safely create a directory asynchronously, ignoring EEXIST errors.
1281+
* This function wraps fs.promises.mkdir and handles the race condition where
1282+
* the directory might already exist, which is common in concurrent code.
1283+
*
1284+
* Unlike fs.promises.mkdir with recursive:true, this function:
1285+
* - Silently ignores EEXIST errors (directory already exists)
1286+
* - Re-throws all other errors (permissions, invalid path, etc.)
1287+
* - Works reliably in multi-process/concurrent scenarios
1288+
*
1289+
* @param path - Directory path to create
1290+
* @param options - Options including recursive and mode settings
1291+
* @returns Promise that resolves when directory is created or already exists
1292+
*
1293+
* @example
1294+
* ```ts
1295+
* // Create a directory, no error if it exists
1296+
* await safeMkdir('./config')
1297+
*
1298+
* // Create nested directories
1299+
* await safeMkdir('./data/cache/temp', { recursive: true })
1300+
*
1301+
* // Create with specific permissions
1302+
* await safeMkdir('./secure', { mode: 0o700 })
1303+
* ```
1304+
*/
1305+
/*@__NO_SIDE_EFFECTS__*/
1306+
export async function safeMkdir(
1307+
path: PathLike,
1308+
options?: MakeDirectoryOptions | undefined,
1309+
): Promise<void> {
1310+
const fs = getFs()
1311+
try {
1312+
await fs.promises.mkdir(path, options)
1313+
} catch (e: unknown) {
1314+
// Ignore EEXIST error - directory already exists.
1315+
if (
1316+
typeof e === 'object' &&
1317+
e !== null &&
1318+
'code' in e &&
1319+
e.code !== 'EEXIST'
1320+
) {
1321+
throw e
1322+
}
1323+
}
1324+
}
1325+
1326+
/**
1327+
* Safely create a directory synchronously, ignoring EEXIST errors.
1328+
* This function wraps fs.mkdirSync and handles the race condition where
1329+
* the directory might already exist, which is common in concurrent code.
1330+
*
1331+
* Unlike fs.mkdirSync with recursive:true, this function:
1332+
* - Silently ignores EEXIST errors (directory already exists)
1333+
* - Re-throws all other errors (permissions, invalid path, etc.)
1334+
* - Works reliably in multi-process/concurrent scenarios
1335+
*
1336+
* @param path - Directory path to create
1337+
* @param options - Options including recursive and mode settings
1338+
*
1339+
* @example
1340+
* ```ts
1341+
* // Create a directory, no error if it exists
1342+
* safeMkdirSync('./config')
1343+
*
1344+
* // Create nested directories
1345+
* safeMkdirSync('./data/cache/temp', { recursive: true })
1346+
*
1347+
* // Create with specific permissions
1348+
* safeMkdirSync('./secure', { mode: 0o700 })
1349+
* ```
1350+
*/
1351+
/*@__NO_SIDE_EFFECTS__*/
1352+
export function safeMkdirSync(
1353+
path: PathLike,
1354+
options?: MakeDirectoryOptions | undefined,
1355+
): void {
1356+
const fs = getFs()
1357+
try {
1358+
fs.mkdirSync(path, options)
1359+
} catch (e: unknown) {
1360+
// Ignore EEXIST error - directory already exists.
1361+
if (
1362+
typeof e === 'object' &&
1363+
e !== null &&
1364+
'code' in e &&
1365+
e.code !== 'EEXIST'
1366+
) {
1367+
throw e
1368+
}
1369+
}
1370+
}
1371+
12781372
/**
12791373
* Safely read a file asynchronously, returning undefined on error.
12801374
* Useful when you want to attempt reading a file without handling errors explicitly.

0 commit comments

Comments
 (0)