Skip to content

Commit ed0ae94

Browse files
committedJan 24, 2020
new implementation for v1.0
1 parent ae00d44 commit ed0ae94

9 files changed

+282
-132
lines changed
 

‎bin/cmd.js

+64-29
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,68 @@
11
#!/usr/bin/env node
22

3-
var mkdirp = require('../');
4-
var minimist = require('minimist');
5-
var fs = require('fs');
6-
7-
var argv = minimist(process.argv.slice(2), {
8-
alias: { m: 'mode', h: 'help' },
9-
string: [ 'mode' ]
10-
});
11-
if (argv.help) {
12-
fs.createReadStream(__dirname + '/usage.txt').pipe(process.stdout);
13-
return;
14-
}
3+
const usage = () => `
4+
usage: mkdirp [DIR1,DIR2..] {OPTIONS}
5+
6+
Create each supplied directory including any necessary parent directories
7+
that don't yet exist.
8+
9+
If the directory already exists, do nothing.
10+
11+
OPTIONS are:
12+
13+
-m<mode> If a directory needs to be created, set the mode as an octal
14+
--mode=<mode> permission string.
15+
16+
-v --version Print the mkdirp version number
1517
16-
var paths = argv._.slice();
17-
var mode = argv.mode ? parseInt(argv.mode, 8) : undefined;
18-
19-
(function next () {
20-
if (paths.length === 0) return;
21-
var p = paths.shift();
22-
23-
if (mode === undefined) mkdirp(p, cb)
24-
else mkdirp(p, mode, cb)
25-
26-
function cb (err) {
27-
if (err) {
28-
console.error(err.message);
29-
process.exit(1);
30-
}
31-
else next();
18+
-h --help Print this helpful banner
19+
20+
-p --print Print the first directories created for each path provided
21+
22+
--manual Use manual implementation, even if native is available
23+
`
24+
25+
const dirs = []
26+
const opts = {}
27+
let print = false
28+
let dashdash = false
29+
let manual = false
30+
for (const arg of process.argv.slice(2)) {
31+
if (dashdash)
32+
dirs.push(arg)
33+
else if (arg === '--')
34+
dashdash = true
35+
else if (arg === '--manual')
36+
manual = true
37+
else if (/^-h/.test(arg) || /^--help/.test(arg)) {
38+
console.log(usage())
39+
process.exit(0)
40+
} else if (arg === '-v' || arg === '--version') {
41+
console.log(require('../package.json').version)
42+
process.exit(0)
43+
} else if (arg === '-p' || arg === '--print') {
44+
print = true
45+
} else if (/^-m/.test(arg) || /^--mode=/.test(arg)) {
46+
const mode = parseInt(arg.replace(/^(-m|--mode=)/, ''), 8)
47+
if (isNaN(mode)) {
48+
console.error(`invalid mode argument: ${arg}\nMust be an octal number.`)
49+
process.exit(1)
3250
}
33-
})();
51+
opts.mode = mode
52+
} else
53+
dirs.push(arg)
54+
}
55+
56+
const mkdirp = require('../')
57+
const impl = manual ? mkdirp.manual : mkdirp
58+
if (dirs.length === 0)
59+
console.error(usage())
60+
61+
Promise.all(dirs.map(dir => impl(dir, opts)))
62+
.then(made => print ? made.forEach(m => m && console.log(m)) : null)
63+
.catch(er => {
64+
console.error(er.message)
65+
if (er.code)
66+
console.error(' code: ' + er.code)
67+
process.exit(1)
68+
})

‎bin/usage.txt

-12
This file was deleted.

‎index.js

+24-91
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,31 @@
1-
var path = require('path');
2-
var fs = require('fs');
3-
var _0777 = parseInt('0777', 8);
1+
const optsArg = require('./lib/opts-arg.js')
2+
const pathArg = require('./lib/path-arg.js')
43

5-
module.exports = mkdirP.mkdirp = mkdirP.mkdirP = mkdirP;
4+
const {mkdirpNative, mkdirpNativeSync} = require('./lib/mkdirp-native.js')
5+
const {mkdirpManual, mkdirpManualSync} = require('./lib/mkdirp-manual.js')
6+
const {useNative, useNativeSync} = require('./lib/use-native.js')
67

7-
function mkdirP (p, opts, f, made) {
8-
if (typeof opts === 'function') {
9-
f = opts;
10-
opts = {};
11-
}
12-
else if (!opts || typeof opts !== 'object') {
13-
opts = { mode: opts };
14-
}
15-
16-
var mode = opts.mode;
17-
var xfs = opts.fs || fs;
18-
19-
if (mode === undefined) {
20-
mode = _0777 & (~process.umask());
21-
}
22-
if (!made) made = null;
23-
24-
var cb = f || function () {};
25-
p = path.resolve(p);
26-
27-
xfs.mkdir(p, mode, function (er) {
28-
if (!er) {
29-
made = made || p;
30-
return cb(null, made);
31-
}
32-
switch (er.code) {
33-
case 'ENOENT':
34-
mkdirP(path.dirname(p), opts, function (er, made) {
35-
if (er) cb(er, made);
36-
else mkdirP(p, opts, cb, made);
37-
});
38-
break;
398

40-
// In the case of any other error, just see if there's a dir
41-
// there already. If so, then hooray! If not, then something
42-
// is borked.
43-
default:
44-
xfs.stat(p, function (er2, stat) {
45-
// if the stat fails, then that's super weird.
46-
// let the original error be the failure reason.
47-
if (er2 || !stat.isDirectory()) cb(er, made)
48-
else cb(null, made);
49-
});
50-
break;
51-
}
52-
});
9+
const mkdirp = (path, opts) => {
10+
path = pathArg(path)
11+
opts = optsArg(opts)
12+
return useNative(opts)
13+
? mkdirpNative(path, opts)
14+
: mkdirpManual(path, opts)
5315
}
5416

55-
mkdirP.sync = function sync (p, opts, made) {
56-
if (!opts || typeof opts !== 'object') {
57-
opts = { mode: opts };
58-
}
59-
60-
var mode = opts.mode;
61-
var xfs = opts.fs || fs;
62-
63-
if (mode === undefined) {
64-
mode = _0777 & (~process.umask());
65-
}
66-
if (!made) made = null;
67-
68-
p = path.resolve(p);
69-
70-
try {
71-
xfs.mkdirSync(p, mode);
72-
made = made || p;
73-
}
74-
catch (err0) {
75-
switch (err0.code) {
76-
case 'ENOENT' :
77-
made = sync(path.dirname(p), opts, made);
78-
sync(p, opts, made);
79-
break;
17+
const mkdirpSync = (path, opts) => {
18+
path = pathArg(path)
19+
opts = optsArg(opts)
20+
return useNativeSync(opts)
21+
? mkdirpNativeSync(path, opts)
22+
: mkdirpManualSync(path, opts)
23+
}
8024

81-
// In the case of any other error, just see if there's a dir
82-
// there already. If so, then hooray! If not, then something
83-
// is borked.
84-
default:
85-
var stat;
86-
try {
87-
stat = xfs.statSync(p);
88-
}
89-
catch (err1) {
90-
throw err0;
91-
}
92-
if (!stat.isDirectory()) throw err0;
93-
break;
94-
}
95-
}
25+
mkdirp.sync = mkdirpSync
26+
mkdirp.native = (path, opts) => mkdirpNative(pathArg(path), optsArg(opts))
27+
mkdirp.manual = (path, opts) => mkdirpManual(pathArg(path), optsArg(opts))
28+
mkdirp.nativeSync = (path, opts) => mkdirpNativeSync(pathArg(path), optsArg(opts))
29+
mkdirp.manualSync = (path, opts) => mkdirpManualSync(pathArg(path), optsArg(opts))
9630

97-
return made;
98-
};
31+
module.exports = mkdirp

‎lib/find-made.js

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
const {dirname} = require('path')
2+
3+
const findMade = (opts, parent, path = undefined) => {
4+
// we never want the 'made' return value to be a root directory
5+
if (path === parent)
6+
return Promise.resolve()
7+
8+
return opts.statAsync(parent).then(
9+
st => st.isDirectory() ? path : undefined, // will fail later
10+
er => er.code === 'ENOENT'
11+
? findMade(opts, dirname(parent), parent)
12+
: undefined
13+
)
14+
}
15+
16+
const findMadeSync = (opts, parent, path = undefined) => {
17+
if (path === parent)
18+
return undefined
19+
20+
try {
21+
return opts.statSync(parent).isDirectory() ? path : undefined
22+
} catch (er) {
23+
return er.code === 'ENOENT'
24+
? findMadeSync(opts, dirname(parent), parent)
25+
: undefined
26+
}
27+
}
28+
29+
module.exports = {findMade, findMadeSync}

‎lib/mkdirp-manual.js

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
const {dirname} = require('path')
2+
3+
const mkdirpManual = (path, opts, made) => {
4+
opts.recursive = false
5+
const parent = dirname(path)
6+
if (parent === path) {
7+
return opts.mkdirAsync(path, opts).catch(er => {
8+
// swallowed by recursive implementation on posix systems
9+
// any other error is a failure
10+
if (er.code !== 'EISDIR')
11+
throw er
12+
})
13+
}
14+
15+
return opts.mkdirAsync(path, opts).then(() => made || path, er => {
16+
if (er.code === 'ENOENT')
17+
return mkdirpManual(parent, opts)
18+
.then(made => mkdirpManual(path, opts, made))
19+
if (er.code !== 'EEXIST')
20+
throw er
21+
return opts.statAsync(path).then(st => {
22+
if (st.isDirectory())
23+
return made
24+
else
25+
throw er
26+
}, () => { throw er })
27+
})
28+
}
29+
30+
const mkdirpManualSync = (path, opts, made) => {
31+
const parent = dirname(path)
32+
opts.recursive = false
33+
34+
if (parent === path) {
35+
try {
36+
return opts.mkdirSync(path, opts)
37+
} catch (er) {
38+
// swallowed by recursive implementation on posix systems
39+
// any other error is a failure
40+
if (er.code !== 'EISDIR')
41+
throw er
42+
else
43+
return
44+
}
45+
}
46+
47+
try {
48+
opts.mkdirSync(path, opts)
49+
return made || path
50+
} catch (er) {
51+
if (er.code === 'ENOENT')
52+
return mkdirpManualSync(path, opts, mkdirpManualSync(parent, opts, made))
53+
if (er.code !== 'EEXIST')
54+
throw er
55+
try {
56+
if (!opts.statSync(path).isDirectory())
57+
throw er
58+
} catch (_) {
59+
throw er
60+
}
61+
}
62+
}
63+
64+
module.exports = {mkdirpManual, mkdirpManualSync}

‎lib/mkdirp-native.js

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
const {dirname} = require('path')
2+
const {findMade, findMadeSync} = require('./find-made.js')
3+
const {mkdirpManual, mkdirpManualSync} = require('./mkdirp-manual.js')
4+
5+
const mkdirpNative = (path, opts) => {
6+
opts.recursive = true
7+
const parent = dirname(path)
8+
if (parent === path)
9+
return opts.mkdirAsync(path, opts)
10+
11+
return findMade(opts, path).then(made =>
12+
opts.mkdirAsync(path, opts).then(() => made)
13+
.catch(er => {
14+
if (er.code === 'ENOENT')
15+
return mkdirpManual(path, opts)
16+
else
17+
throw er
18+
}))
19+
}
20+
21+
const mkdirpNativeSync = (path, opts) => {
22+
opts.recursive = true
23+
const parent = dirname(path)
24+
if (parent === path)
25+
return opts.mkdirSync(path, opts)
26+
27+
const made = findMadeSync(opts, path)
28+
try {
29+
opts.mkdirSync(path, opts)
30+
return made
31+
} catch (er) {
32+
if (er.code === 'ENOENT')
33+
return mkdirpManualSync(path, opts)
34+
else
35+
throw er
36+
}
37+
}
38+
39+
module.exports = {mkdirpNative, mkdirpNativeSync}

‎lib/opts-arg.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
const { promisify } = require('util')
2+
const fs = require('fs')
3+
const optsArg = opts => {
4+
if (!opts)
5+
opts = { mode: 0o777 & (~process.umask()), fs }
6+
else if (typeof opts === 'object')
7+
opts = { mode: 0o777 & (~process.umask()), fs, ...opts }
8+
else if (typeof opts === 'number')
9+
opts = { mode: opts, fs }
10+
else if (typeof opts === 'string')
11+
opts = { mode: parseInt(opts, 8), fs }
12+
else
13+
throw new TypeError('invalid options argument')
14+
15+
opts.mkdir = opts.mkdir || opts.fs.mkdir || fs.mkdir
16+
opts.mkdirAsync = promisify(opts.mkdir)
17+
opts.stat = opts.stat || opts.fs.stat || fs.stat
18+
opts.statAsync = promisify(opts.stat)
19+
opts.statSync = opts.statSync || opts.fs.statSync || fs.statSync
20+
opts.mkdirSync = opts.mkdirSync || opts.fs.mkdirSync || fs.mkdirSync
21+
return opts
22+
}
23+
module.exports = optsArg

‎lib/path-arg.js

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
const platform = process.env.__TESTING_MKDIRP_PLATFORM__ || process.platform
2+
const { resolve, parse } = require('path')
3+
const pathArg = path => {
4+
if (/\0/.test(path)) {
5+
// simulate same failure that node raises
6+
throw Object.assign(
7+
new TypeError('path must be a string without null bytes'),
8+
{
9+
path,
10+
code: 'ERR_INVALID_ARG_VALUE',
11+
}
12+
)
13+
}
14+
15+
path = resolve(path)
16+
if (platform === 'win32') {
17+
const badWinChars = /[*|"<>?:]/
18+
const {root} = parse(path)
19+
if (badWinChars.test(path.substr(root.length))) {
20+
throw Object.assign(new Error('Illegal characters in path.'), {
21+
path,
22+
code: 'EINVAL',
23+
})
24+
}
25+
}
26+
27+
return path
28+
}
29+
module.exports = pathArg

‎lib/use-native.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const fs = require('fs')
2+
3+
const version = process.env.__TESTING_MKDIRP_NODE_VERSION__ || process.version
4+
const versArr = version.replace(/^v/, '').split('.')
5+
const hasNative = +versArr[0] > 10 || +versArr[0] === 10 && +versArr[1] >= 12
6+
7+
const useNative = !hasNative ? () => false : opts => opts.mkdir === fs.mkdir
8+
const useNativeSync = !hasNative ? () => false : opts => opts.mkdirSync === fs.mkdirSync
9+
10+
module.exports = {useNative, useNativeSync}

0 commit comments

Comments
 (0)
Please sign in to comment.