diff --git a/src/__tests__/volume.test.ts b/src/__tests__/volume.test.ts index 317dfda0e..298993b85 100644 --- a/src/__tests__/volume.test.ts +++ b/src/__tests__/volume.test.ts @@ -1008,7 +1008,7 @@ describe('volume', () => { }); it('Create /dir1/dir2/dir3 recursively', () => { const vol = new Volume(); - vol.mkdirSync('/dir1/dir2/dir3', { recursive: true }); + const fullPath = vol.mkdirSync('/dir1/dir2/dir3', { recursive: true }); const dir1 = tryGetChild(vol.root, 'dir1'); const dir2 = tryGetChild(dir1, 'dir2'); const dir3 = tryGetChild(dir2, 'dir3'); @@ -1018,6 +1018,9 @@ describe('volume', () => { expect(dir1.getNode().isDirectory()).toBe(true); expect(dir2.getNode().isDirectory()).toBe(true); expect(dir3.getNode().isDirectory()).toBe(true); + expect(fullPath).toBe('/dir1/dir2/dir3'); + const dirAlreadyExists = vol.mkdirSync('/dir1/dir2/dir3', { recursive: true }); + expect(dirAlreadyExists).toBe(undefined); }); }); describe('.mkdir(path[, mode], callback)', () => { diff --git a/src/volume.ts b/src/volume.ts index cc098c67a..eea9c5f4b 100644 --- a/src/volume.ts +++ b/src/volume.ts @@ -225,9 +225,11 @@ function optsGenerator(defaults: TOpts): (opts) => TOpts { return options => getOptions(defaults, options); } -function validateCallback(callback) { +type AssertCallback = T extends Function ? T : never; + +function validateCallback(callback: T): AssertCallback { if (typeof callback !== 'function') throw TypeError(ERRSTR.CB); - return callback; + return callback as AssertCallback; } function optsAndCbGenerator(getOpts): (options, callback?) => [TOpts, TCallback] { @@ -431,7 +433,7 @@ if (isWin) { export function filenameToSteps(filename: string, base?: string): string[] { const fullPath = resolve(filename, base); - const fullPathSansSlash = fullPath.substr(1); + const fullPathSansSlash = fullPath.substring(1); if (!fullPathSansSlash) return []; return fullPathSansSlash.split(sep); } @@ -726,7 +728,7 @@ export class Volume { // Generates 6 character long random string, used by `mkdtemp`. genRndStr() { - const str = (Math.random() + 1).toString(36).substr(2, 6); + const str = (Math.random() + 1).toString(36).substring(2, 8); if (str.length === 6) return str; else return this.genRndStr(); } @@ -1920,8 +1922,11 @@ export class Volume { * @param modeNum */ private mkdirpBase(filename: string, modeNum: number) { - const steps = filenameToSteps(filename); + const fullPath = resolve(filename); + const fullPathSansSlash = fullPath.substring(1); + const steps = !fullPathSansSlash ? [] : fullPathSansSlash.split(sep); let link = this.root; + let created = false; for (let i = 0; i < steps.length; i++) { const step = steps[i]; @@ -1933,23 +1938,30 @@ export class Volume { else throw createError(ENOTDIR, 'mkdir', child.getPath()); } else { link = link.createChild(step, this.createNode(true, modeNum)); + created = true; } } + return created ? fullPath : undefined; } + mkdirSync(path: PathLike, options: IMkdirOptions & { recursive: true }): string | undefined; + mkdirSync(path: PathLike, options?: TMode | (IMkdirOptions & { recursive?: false })): void; + mkdirSync(path: PathLike, options?: TMode | IMkdirOptions): string | undefined; mkdirSync(path: PathLike, options?: TMode | IMkdirOptions) { const opts = getMkdirOptions(options); const modeNum = modeToNumber(opts.mode, 0o777); const filename = pathToFilename(path); - if (opts.recursive) this.mkdirpBase(filename, modeNum); - else this.mkdirBase(filename, modeNum); + if (opts.recursive) return this.mkdirpBase(filename, modeNum); + this.mkdirBase(filename, modeNum); } mkdir(path: PathLike, callback: TCallback); - mkdir(path: PathLike, mode: TMode | IMkdirOptions, callback: TCallback); - mkdir(path: PathLike, a: TCallback | TMode | IMkdirOptions, b?: TCallback) { + mkdir(path: PathLike, mode: TMode | (IMkdirOptions & { recursive?: false }), callback: TCallback); + mkdir(path: PathLike, mode: IMkdirOptions & { recursive: true }, callback: TCallback); + mkdir(path: PathLike, mode: TMode | IMkdirOptions, callback: TCallback); + mkdir(path: PathLike, a: TCallback | TMode | IMkdirOptions, b?: TCallback | TCallback) { const opts: TMode | IMkdirOptions = getMkdirOptions(a); - const callback: TCallback = validateCallback(typeof a === 'function' ? a : b); + const callback = validateCallback(typeof a === 'function' ? a : b!); const modeNum = modeToNumber(opts.mode, 0o777); const filename = pathToFilename(path); if (opts.recursive) this.wrapAsync(this.mkdirpBase, [filename, modeNum], callback); @@ -1958,14 +1970,14 @@ export class Volume { // legacy interface mkdirpSync(path: PathLike, mode?: TMode) { - this.mkdirSync(path, { mode, recursive: true }); + return this.mkdirSync(path, { mode, recursive: true }); } - mkdirp(path: PathLike, callback: TCallback); - mkdirp(path: PathLike, mode: TMode, callback: TCallback); - mkdirp(path: PathLike, a: TCallback | TMode, b?: TCallback) { + mkdirp(path: PathLike, callback: TCallback); + mkdirp(path: PathLike, mode: TMode, callback: TCallback); + mkdirp(path: PathLike, a: TCallback | TMode, b?: TCallback) { const mode: TMode | undefined = typeof a === 'function' ? undefined : a; - const callback: TCallback = validateCallback(typeof a === 'function' ? a : b); + const callback = validateCallback(typeof a === 'function' ? a : b); this.mkdir(path, { mode, recursive: true }, callback); }