Skip to content

Commit 15aae89

Browse files
committed
feat: add relative option support for mkdir and mkdirSync
Add support for `relative` option for `fs.mkdir` and `fs.mkdirSync` functions by using `vol.mkdirp` implementation. Make `vol.mkdirp` and `vol.mkdirpSync` functions deprecated.
1 parent f879f9b commit 15aae89

File tree

4 files changed

+90
-70
lines changed

4 files changed

+90
-70
lines changed

docs/api-status.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ implemented (you have access to any file), basically `fs.access()` is a no-op.
5252
- [x] `linkSync(existingPath, newPath)`
5353
- [x] `lstat(path, callback)`
5454
- [x] `lstatSync(path)`
55-
- [x] `mkdir(path[, mode], callback)`
56-
- [x] `mkdirSync(path[, mode])`
55+
- [x] `mkdir(path[, options], callback)`
56+
- [x] `mkdirSync(path[, options])`
5757
- [x] `mkdtemp(prefix[, options], callback)`
5858
- [x] `mkdtempSync(prefix[, options])`
5959
- [x] `open(path, flags[, mode], callback)`

docs/reference.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,17 @@ vol.reset();
9898
vol.toJSON(); // {}
9999
```
100100

101-
#### `vol.mkdirp(path, callback)`
101+
#### `vol.mkdirp(path[, mode], callback)`
102+
103+
Legacy interface, which now uses the `recursive` option of `vol.mkdir`.
102104

103105
Creates a directory tree recursively. `path` specifies a directory to
104106
create and can be a string, `Buffer`, or an `URL` object. `callback` is
105107
called on completion and may receive only one argument - an `Error` object.
106108

107-
#### `vol.mkdirpSync(path)`
109+
#### `vol.mkdirpSync(path[, mode])`
110+
111+
Legacy interface, which now uses the `recursive` option of `vol.mkdirSync`.
108112

109113
A synchronous version of `vol.mkdirp()`. This method throws.
110114

src/__tests__/volume.test.ts

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -825,7 +825,7 @@ describe('volume', () => {
825825
describe('.utimes(path, atime, mtime, callback)', () => {
826826
xit('...', () => {});
827827
});
828-
describe('.mkdirSync(path[, mode])', () => {
828+
describe('.mkdirSync(path[, options])', () => {
829829
it('Create dir at root', () => {
830830
const vol = new Volume;
831831
vol.mkdirSync('/test');
@@ -845,9 +845,23 @@ describe('volume', () => {
845845
expect(dir2.getNode().isDirectory()).toBe(true);
846846
expect(dir2.getPath()).toBe('/dir1/dir2');
847847
});
848+
it('Create /dir1/dir2/dir3 recursively', () => {
849+
const vol = new Volume;
850+
vol.mkdirSync('/dir1/dir2/dir3', { recursive: true });
851+
const dir1 = vol.root.getChild('dir1');
852+
const dir2 = dir1.getChild('dir2');
853+
const dir3 = dir2.getChild('dir3');
854+
expect(dir1).toBeInstanceOf(Link);
855+
expect(dir2).toBeInstanceOf(Link);
856+
expect(dir3).toBeInstanceOf(Link);
857+
expect(dir1.getNode().isDirectory()).toBe(true);
858+
expect(dir2.getNode().isDirectory()).toBe(true);
859+
expect(dir3.getNode().isDirectory()).toBe(true);
860+
});
848861
});
849862
describe('.mkdir(path[, mode], callback)', () => {
850863
xit('...');
864+
xit('Create /dir1/dir2/dir3', () => { });
851865
});
852866
describe('.mkdtempSync(prefix[, options])', () => {
853867
it('Create temp dir at root', () => {
@@ -860,24 +874,6 @@ describe('volume', () => {
860874
describe('.mkdtemp(prefix[, options], callback)', () => {
861875
xit('Create temp dir at root', () => {});
862876
});
863-
describe('.mkdirpSync(path[, mode])', () => {
864-
it('Create /dir1/dir2/dir3', () => {
865-
const vol = new Volume;
866-
vol.mkdirpSync('/dir1/dir2/dir3');
867-
const dir1 = vol.root.getChild('dir1');
868-
const dir2 = dir1.getChild('dir2');
869-
const dir3 = dir2.getChild('dir3');
870-
expect(dir1).toBeInstanceOf(Link);
871-
expect(dir2).toBeInstanceOf(Link);
872-
expect(dir3).toBeInstanceOf(Link);
873-
expect(dir1.getNode().isDirectory()).toBe(true);
874-
expect(dir2.getNode().isDirectory()).toBe(true);
875-
expect(dir3.getNode().isDirectory()).toBe(true);
876-
});
877-
});
878-
describe('.mkdirp(path[, mode], callback)', () => {
879-
xit('Create /dir1/dir2/dir3', () => {});
880-
});
881877
describe('.rmdirSync(path)', () => {
882878
it('Remove single dir', () => {
883879
const vol = new Volume;

src/volume.ts

Lines changed: 67 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,21 @@ export interface IWatchOptions extends IOptions {
334334
recursive?: boolean,
335335
}
336336

337+
// Options for `fs.mkdir` and `fs.mkdirSync`
338+
export interface IMkdirOptions {
339+
mode?: TMode,
340+
recursive?: boolean,
341+
};
342+
const mkdirDefaults: IMkdirOptions = {
343+
mode: MODE.DIR,
344+
recursive: false,
345+
};
346+
const getMkdirOptions = options => {
347+
if (typeof options === 'number')
348+
return extend({}, mkdirDefaults, { mode: options });
349+
return extend({}, mkdirDefaults, options);
350+
}
351+
337352

338353

339354
// ---------------------------------------- Utility functions
@@ -1725,19 +1740,63 @@ export class Volume {
17251740
dir.createChild(name, this.createNode(true, modeNum));
17261741
}
17271742

1728-
mkdirSync(path: TFilePath, mode?: TMode) {
1729-
const modeNum = modeToNumber(mode, 0o777);
1743+
/**
1744+
* Creates directory tree recursively.
1745+
* @param filename
1746+
* @param modeNum
1747+
*/
1748+
private mkdirpBase(filename: string, modeNum: number) {
1749+
const steps = filenameToSteps(filename);
1750+
let link = this.root;
1751+
for (let i = 0; i < steps.length; i++) {
1752+
const step = steps[i];
1753+
1754+
if (!link.getNode().isDirectory())
1755+
throwError(ENOTDIR, 'mkdir', link.getPath());
1756+
1757+
const child = link.getChild(step);
1758+
if (child) {
1759+
if (child.getNode().isDirectory()) link = child;
1760+
else throwError(ENOTDIR, 'mkdir', child.getPath());
1761+
} else {
1762+
link = link.createChild(step, this.createNode(true, modeNum));
1763+
}
1764+
}
1765+
}
1766+
1767+
mkdirSync(path: TFilePath, options?: TMode | IMkdirOptions) {
1768+
const opts = getMkdirOptions(options);
1769+
const modeNum = modeToNumber(opts.mode, 0o777);
17301770
const filename = pathToFilename(path);
1731-
this.mkdirBase(filename, modeNum);
1771+
if (opts.recursive)
1772+
this.mkdirpBase(filename, modeNum);
1773+
else
1774+
this.mkdirBase(filename, modeNum);
17321775
}
17331776

17341777
mkdir(path: TFilePath, callback: TCallback<void>);
1735-
mkdir(path: TFilePath, mode: TMode, callback: TCallback<void>);
1736-
mkdir(path: TFilePath, a: TCallback<void> | TMode, b?: TCallback<void>) {
1737-
const [mode, callback] = getArgAndCb<TMode, void>(a, b);
1738-
const modeNum = modeToNumber(mode, 0o777);
1778+
mkdir(path: TFilePath, mode: TMode | IMkdirOptions, callback: TCallback<void>);
1779+
mkdir(path: TFilePath, a: TCallback<void> | TMode | IMkdirOptions, b?: TCallback<void>) {
1780+
const [options, callback] = getArgAndCb<TMode | IMkdirOptions, void>(a, b);
1781+
const opts = getMkdirOptions(options);
1782+
const modeNum = modeToNumber(opts.mode, 0o777);
17391783
const filename = pathToFilename(path);
1740-
this.wrapAsync(this.mkdirBase, [filename, modeNum], callback);
1784+
if (opts.recursive)
1785+
this.wrapAsync(this.mkdirpBase, [filename, modeNum], callback);
1786+
else
1787+
this.wrapAsync(this.mkdirBase, [filename, modeNum], callback);
1788+
}
1789+
1790+
// legacy interface
1791+
mkdirpSync(path: TFilePath, mode?: TMode) {
1792+
this.mkdirSync(path, { mode, recursive: true });
1793+
}
1794+
1795+
mkdirp(path: TFilePath, callback: TCallback<void>);
1796+
mkdirp(path: TFilePath, mode: TMode, callback: TCallback<void>);
1797+
mkdirp(path: TFilePath, a: TCallback<void> | TMode, b?: TCallback<void>) {
1798+
const [mode, callback] = getArgAndCb<TMode, void>(a, b);
1799+
this.mkdir(path, { mode, recursive: true }, callback);
17411800
}
17421801

17431802
private mkdtempBase(prefix: string, encoding: TEncodingExtended, retry: number = 5): TDataOut {
@@ -1777,45 +1836,6 @@ export class Volume {
17771836
this.wrapAsync(this.mkdtempBase, [prefix, encoding], callback);
17781837
}
17791838

1780-
/**
1781-
* Creates directory tree recursively.
1782-
* @param filename
1783-
* @param modeNum
1784-
*/
1785-
private mkdirpBase(filename: string, modeNum: number) {
1786-
const steps = filenameToSteps(filename);
1787-
let link = this.root;
1788-
for(let i = 0; i < steps.length; i++) {
1789-
const step = steps[i];
1790-
1791-
if(!link.getNode().isDirectory())
1792-
throwError(ENOTDIR, 'mkdirp', link.getPath());
1793-
1794-
const child = link.getChild(step);
1795-
if(child) {
1796-
if(child.getNode().isDirectory()) link = child;
1797-
else throwError(ENOTDIR, 'mkdirp', child.getPath());
1798-
} else {
1799-
link = link.createChild(step, this.createNode(true, modeNum));
1800-
}
1801-
}
1802-
}
1803-
1804-
mkdirpSync(path: TFilePath, mode?: TMode) {
1805-
const modeNum = modeToNumber(mode, 0o777);
1806-
const filename = pathToFilename(path);
1807-
this.mkdirpBase(filename, modeNum);
1808-
}
1809-
1810-
mkdirp(path: TFilePath, callback: TCallback<void>);
1811-
mkdirp(path: TFilePath, mode: TMode, callback: TCallback<void>);
1812-
mkdirp(path: TFilePath, a: TCallback<void> | TMode, b?: TCallback<void>) {
1813-
const [mode, callback] = getArgAndCb<TMode, void>(a, b);
1814-
const modeNum = modeToNumber(mode, 0o777);
1815-
const filename = pathToFilename(path);
1816-
this.wrapAsync(this.mkdirpBase, [filename, modeNum], callback);
1817-
}
1818-
18191839
private rmdirBase(filename: string) {
18201840
const link = this.getLinkAsDirOrThrow(filename, 'rmdir');
18211841

0 commit comments

Comments
 (0)