Skip to content

Commit

Permalink
Recognize recursive option in mkdir and mkdirSync
Browse files Browse the repository at this point in the history
resolves #75
  • Loading branch information
yoursunny committed Jun 9, 2024
1 parent 772e054 commit e06c562
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 13 deletions.
31 changes: 26 additions & 5 deletions src/emulation/promises.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type { ReadableStreamController } from 'stream/web';
import { Errno, ErrnoError } from '../error.js';
import type { File } from '../file.js';
import { isAppendable, isExclusive, isReadable, isTruncating, isWriteable, parseFlag } from '../file.js';
import type { FileContents } from '../filesystem.js';
import type { FileContents, FileSystem } from '../filesystem.js';

Check warning on line 12 in src/emulation/promises.ts

View workflow job for this annotation

GitHub Actions / ubuntu-latest

'FileSystem' is defined but never used

Check warning on line 12 in src/emulation/promises.ts

View workflow job for this annotation

GitHub Actions / macos-latest

'FileSystem' is defined but never used

Check warning on line 12 in src/emulation/promises.ts

View workflow job for this annotation

GitHub Actions / windows-latest

'FileSystem' is defined but never used
import { BigIntStats, FileType, type Stats } from '../stats.js';
import { normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
import * as constants from './constants.js';
Expand Down Expand Up @@ -656,13 +656,34 @@ export async function mkdir(path: fs.PathLike, options: fs.MakeDirectoryOptions
export async function mkdir(path: fs.PathLike, options?: fs.Mode | (fs.MakeDirectoryOptions & { recursive?: false | undefined }) | null): Promise<void>;
export async function mkdir(path: fs.PathLike, options?: fs.Mode | fs.MakeDirectoryOptions | null): Promise<string | undefined>;
export async function mkdir(path: fs.PathLike, options?: fs.Mode | fs.MakeDirectoryOptions | null): Promise<string | undefined | void> {
options = typeof options === 'object' ? options : { mode: options };
const mode = normalizeMode(options?.mode, 0o777);

path = normalizePath(path);
path = (await exists(path)) ? await realpath(path) : path;
const { fs, path: resolved } = resolveMount(path);
try {
await fs.mkdir(resolved, normalizeMode(typeof options == 'object' ? options?.mode : options, 0o777), cred);
} catch (e) {
throw fixError(e as Error, { [resolved]: path });
const errorPaths: Record<string, string> = { [resolved]: path };

const mkdirSingle = async (dir: string) => {
try {
await fs.mkdir(dir, mode, cred);
} catch (e) {
throw fixError(e as Error, errorPaths);
}
};

if (options?.recursive) {
const dirs: string[] = [];
for (let dir = resolved, origDir = path; !(await fs.exists(dir, cred)); dir = dirname(dir), origDir = dirname(origDir)) {
dirs.unshift(dir);
errorPaths[dir] = origDir;
}
for (const dir of dirs) {
await mkdirSingle(dir);
}
return dirs[0];
} else {
return mkdirSingle(resolved);
}
}
mkdir satisfies typeof promises.mkdir;
Expand Down
34 changes: 27 additions & 7 deletions src/emulation/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -481,14 +481,34 @@ export function mkdirSync(path: fs.PathLike, options: fs.MakeDirectoryOptions &
export function mkdirSync(path: fs.PathLike, options?: fs.Mode | (fs.MakeDirectoryOptions & { recursive?: false }) | null): void;
export function mkdirSync(path: fs.PathLike, options?: fs.Mode | fs.MakeDirectoryOptions | null): string | undefined;
export function mkdirSync(path: fs.PathLike, options?: fs.Mode | fs.MakeDirectoryOptions | null): string | undefined | void {
const mode: fs.Mode = normalizeMode(typeof options == 'number' || typeof options == 'string' ? options : options?.mode, 0o777);
const recursive = typeof options == 'object' && options?.recursive;
options = typeof options === 'object' ? options : { mode: options };
const mode = normalizeMode(options?.mode, 0o777);

path = normalizePath(path);
const { fs, path: resolved } = resolveMount(existsSync(path) ? realpathSync(path) : path);
try {
return fs.mkdirSync(resolved, mode, cred);
} catch (e) {
throw fixError(e as Error, { [resolved]: path });
path = existsSync(path) ? realpathSync(path) : path;
const { fs, path: resolved } = resolveMount(path);
const errorPaths: Record<string, string> = { [resolved]: path };

const mkdirSingle = (dir: string) => {
try {
fs.mkdirSync(dir, mode, cred);
} catch (e) {
throw fixError(e as Error, errorPaths);
}
};

if (options?.recursive) {
const dirs: string[] = [];
for (let dir = resolved, origDir = path; !fs.existsSync(dir, cred); dir = dirname(dir), origDir = dirname(origDir)) {
dirs.unshift(dir);
errorPaths[dir] = origDir;
}
for (const dir of dirs) {
mkdirSingle(dir);
}
return dirs[0];
} else {
return mkdirSingle(resolved);
}
}
mkdirSync satisfies typeof fs.mkdirSync;
Expand Down
27 changes: 26 additions & 1 deletion tests/fs/directory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { fs } from '../common.js';
describe('Directory', () => {
test('mkdir', async () => {
await fs.promises.mkdir('/one', 0o755);
expect(await fs.promises.exists('/one')).toBe(true);
await expect(fs.promises.exists('/one')).resolves.toBe(true);
await expect(fs.promises.mkdir('/one', 0o755)).rejects.toThrow(/EEXIST/);
});

test('mkdirSync', () => fs.mkdirSync('/two', 0o000));
Expand All @@ -17,6 +18,30 @@ describe('Directory', () => {
expect(await fs.promises.exists('/nested/dir')).toBe(false);
});

test('mkdir, recursive', async () => {
await expect(fs.promises.mkdir('/recursiveP/A/B', { recursive: true, mode: 0o755 })).resolves.toBe('/recursiveP');
await expect(fs.promises.mkdir('/recursiveP/A/B/C/D', { recursive: true, mode: 0o777 })).resolves.toBe('/recursiveP/A/B/C');
await expect(fs.promises.mkdir('/recursiveP/A/B/C/D', { recursive: true, mode: 0o700 })).resolves.toBeUndefined();

await expect(fs.promises.stat('/recursiveP')).resolves.toMatchObject({ mode: fs.constants.S_IFDIR | 0o755 });
await expect(fs.promises.stat('/recursiveP/A')).resolves.toMatchObject({ mode: fs.constants.S_IFDIR | 0o755 });
await expect(fs.promises.stat('/recursiveP/A/B')).resolves.toMatchObject({ mode: fs.constants.S_IFDIR | 0o755 });
await expect(fs.promises.stat('/recursiveP/A/B/C')).resolves.toMatchObject({ mode: fs.constants.S_IFDIR | 0o777 });
await expect(fs.promises.stat('/recursiveP/A/B/C/D')).resolves.toMatchObject({ mode: fs.constants.S_IFDIR | 0o777 });
});

test('mkdirSync, recursive', () => {
expect(fs.mkdirSync('/recursiveS/A/B', { recursive: true, mode: 0o755 })).toBe('/recursiveS');
expect(fs.mkdirSync('/recursiveS/A/B/C/D', { recursive: true, mode: 0o777 })).toBe('/recursiveS/A/B/C');
expect(fs.mkdirSync('/recursiveS/A/B/C/D', { recursive: true, mode: 0o700 })).toBeUndefined();

expect(fs.statSync('/recursiveS')).toMatchObject({ mode: fs.constants.S_IFDIR | 0o755 });
expect(fs.statSync('/recursiveS/A')).toMatchObject({ mode: fs.constants.S_IFDIR | 0o755 });
expect(fs.statSync('/recursiveS/A/B')).toMatchObject({ mode: fs.constants.S_IFDIR | 0o755 });
expect(fs.statSync('/recursiveS/A/B/C')).toMatchObject({ mode: fs.constants.S_IFDIR | 0o777 });
expect(fs.statSync('/recursiveS/A/B/C/D')).toMatchObject({ mode: fs.constants.S_IFDIR | 0o777 });
});

test('readdirSync without permission', () => {
try {
fs.readdirSync('/two');
Expand Down

0 comments on commit e06c562

Please sign in to comment.