Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: isaacs/node-mkdirp
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: d4eff0f06093aed4f387e88e9fc301cb76beedc7
Choose a base ref
...
head repository: isaacs/node-mkdirp
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: b694079b54a0a59ef150c54b29c7c24e90d642f5
Choose a head ref

Commits on Feb 6, 2016

  1. tools: update tap + mock-fs. Fix broken test

    The test suite is currently failing on anything higher than v4
    
    Updating mock-fs fixes this. As tap was quite out of date I have
    updated that as well.
    
    After updating the dependencies a test began failing due to
    `test.end()` being called more than once. This is fixed
    Myles Borins committed Feb 6, 2016
    Copy the full SHA
    b8629ff View commit details
  2. test: add v4 and v5 to travis

    Myles Borins committed Feb 6, 2016
    Copy the full SHA
    f2003bb View commit details

Commits on Jan 24, 2020

  1. docs for new version

    isaacs committed Jan 24, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    3e68692 View commit details
  2. tests for new version

    isaacs committed Jan 24, 2020
    Copy the full SHA
    c360cf5 View commit details
  3. Copy the full SHA
    2ed0350 View commit details
  4. add a changelog

    isaacs committed Jan 24, 2020
    Copy the full SHA
    19511d6 View commit details
  5. Copy the full SHA
    b6ea965 View commit details
  6. package updates

    isaacs committed Jan 24, 2020
    8
    Copy the full SHA
    2ecfd49 View commit details
  7. ignore stuff

    isaacs committed Jan 24, 2020
    Copy the full SHA
    824f3f8 View commit details
  8. modernize travis

    isaacs committed Jan 24, 2020
    Copy the full SHA
    ae00d44 View commit details
  9. new implementation for v1.0

    isaacs committed Jan 24, 2020
    Copy the full SHA
    ed0ae94 View commit details
  10. 1.0.0

    isaacs committed Jan 24, 2020
    Copy the full SHA
    1b64c7b View commit details
  11. add files list to package.json

    isaacs committed Jan 24, 2020
    Copy the full SHA
    73f8f38 View commit details
  12. 1.0.1

    isaacs committed Jan 24, 2020
    Copy the full SHA
    ab8b754 View commit details
  13. readme typo

    isaacs committed Jan 24, 2020
    Copy the full SHA
    11d2b2a View commit details
  14. 1.0.2

    isaacs committed Jan 24, 2020
    Copy the full SHA
    6a061db View commit details
  15. add sponsorship button

    isaacs committed Jan 24, 2020
    Copy the full SHA
    6b2632d View commit details
  16. Handle EROFS errors

    A EROFS will be raised when trying to write a dir onto a read-only
    filesystem.  However, if the dir already is there, then it should be
    treated like an EEXIST (since EROFS can be raised by trying to create a
    dir over a mount point, where the mount point is not read-only, but you
    still can't just clobber over it).
    
    If the dir doesn't already exist, then the EROFS will be accurately
    reported as the reason why it could not be created.
    
    (Copied from 17ee84f)
    isaacs committed Jan 24, 2020
    Copy the full SHA
    e84d327 View commit details
  17. 1.0.3

    isaacs committed Jan 24, 2020
    Copy the full SHA
    9f29fc8 View commit details
  18. correct gitignore

    I'd copied it from the wrong project.  Now it makes sense why git was
    still showing me so many unknown files :)
    isaacs committed Jan 24, 2020
    Copy the full SHA
    7cd4e9e View commit details
  19. remove outdated example

    isaacs committed Jan 24, 2020
    Copy the full SHA
    117328b View commit details

Commits on Mar 31, 2020

  1. update tap

    isaacs committed Mar 31, 2020
    Copy the full SHA
    4d59b81 View commit details

Commits on Apr 3, 2020

  1. Copy the full SHA
    2d42170 View commit details
  2. 1.0.4

    isaacs committed Apr 3, 2020
    Copy the full SHA
    b694079 View commit details
Showing with 4,542 additions and 651 deletions.
  1. +1 −0 .github/FUNDING.yml
  2. +22 −0 .gitignore
  3. +4 −6 .travis.yml
  4. +15 −0 CHANGELOG.md
  5. +2 −2 LICENSE
  6. +64 −29 bin/cmd.js
  7. +0 −12 bin/usage.txt
  8. +0 −6 examples/pow.js
  9. +24 −91 index.js
  10. +29 −0 lib/find-made.js
  11. +64 −0 lib/mkdirp-manual.js
  12. +39 −0 lib/mkdirp-native.js
  13. +23 −0 lib/opts-arg.js
  14. +29 −0 lib/path-arg.js
  15. +10 −0 lib/use-native.js
  16. +6 −0 map.js
  17. +3,361 −0 package-lock.json
  18. +27 −10 package.json
  19. +201 −35 readme.markdown
  20. +104 −0 tap-snapshots/test-cmd.js-TAP.test.js
  21. +0 −41 test/chmod.js
  22. +0 −38 test/clobber.js
  23. +69 −0 test/cmd.js
  24. +53 −0 test/find-made.js
  25. +61 −0 test/index.js
  26. +154 −0 test/mkdirp-manual.js
  27. +73 −0 test/mkdirp-native.js
  28. +0 −28 test/mkdirp.js
  29. +37 −0 test/opts-arg.js
  30. +0 −29 test/opts_fs.js
  31. +0 −27 test/opts_fs_sync.js
  32. +40 −0 test/path-arg.js
  33. +0 −32 test/perm.js
  34. +0 −36 test/perm_sync.js
  35. +0 −37 test/race.js
  36. +0 −32 test/rel.js
  37. +0 −25 test/return.js
  38. +0 −24 test/return_sync.js
  39. +0 −19 test/root.js
  40. +0 −32 test/sync.js
  41. +0 −28 test/umask.js
  42. +0 −32 test/umask_sync.js
  43. +30 −0 test/use-native.js
1 change: 1 addition & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
github: [isaacs, substack]
22 changes: 22 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# ignore most things, include some others
/*
/.*

!bin/
!lib/
!docs/
!package.json
!package-lock.json
!README.md
!CONTRIBUTING.md
!LICENSE
!CHANGELOG.md
!example/
!scripts/
!tap-snapshots/
!test/
!.travis.yml
!.gitignore
!.gitattributes
!map.js
!index.js
10 changes: 4 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
language: node_js

node_js:
- "0.8"
- "0.10"
- "0.12"
- "iojs"
before_install:
- npm install -g npm@~1.4.6
- node
- 12
- 10
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Changers Lorgs!

## 1.0

Full rewrite. Essentially a brand new module.

- Return a promise instead of taking a callback.
- Use native `fs.mkdir(path, { recursive: true })` when available.
- Drop support for outdated Node.js versions. (Technically still works on
Node.js v8, but only 10 and above are officially supported.)

## 0.x

Original and most widely used recursive directory creation implementation
in JavaScript, dating back to 2010.
4 changes: 2 additions & 2 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Copyright 2010 James Halliday (mail@substack.net)
Copyright James Halliday (mail@substack.net) and Isaac Z. Schlueter (i@izs.me)

This project is free software released under the MIT/X11 license:
This project is free software released under the MIT license:

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
93 changes: 64 additions & 29 deletions bin/cmd.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,68 @@
#!/usr/bin/env node

var mkdirp = require('../');
var minimist = require('minimist');
var fs = require('fs');

var argv = minimist(process.argv.slice(2), {
alias: { m: 'mode', h: 'help' },
string: [ 'mode' ]
});
if (argv.help) {
fs.createReadStream(__dirname + '/usage.txt').pipe(process.stdout);
return;
}
const usage = () => `
usage: mkdirp [DIR1,DIR2..] {OPTIONS}
Create each supplied directory including any necessary parent directories
that don't yet exist.
If the directory already exists, do nothing.
OPTIONS are:
-m<mode> If a directory needs to be created, set the mode as an octal
--mode=<mode> permission string.
-v --version Print the mkdirp version number
var paths = argv._.slice();
var mode = argv.mode ? parseInt(argv.mode, 8) : undefined;

(function next () {
if (paths.length === 0) return;
var p = paths.shift();

if (mode === undefined) mkdirp(p, cb)
else mkdirp(p, mode, cb)

function cb (err) {
if (err) {
console.error(err.message);
process.exit(1);
}
else next();
-h --help Print this helpful banner
-p --print Print the first directories created for each path provided
--manual Use manual implementation, even if native is available
`

const dirs = []
const opts = {}
let print = false
let dashdash = false
let manual = false
for (const arg of process.argv.slice(2)) {
if (dashdash)
dirs.push(arg)
else if (arg === '--')
dashdash = true
else if (arg === '--manual')
manual = true
else if (/^-h/.test(arg) || /^--help/.test(arg)) {
console.log(usage())
process.exit(0)
} else if (arg === '-v' || arg === '--version') {
console.log(require('../package.json').version)
process.exit(0)
} else if (arg === '-p' || arg === '--print') {
print = true
} else if (/^-m/.test(arg) || /^--mode=/.test(arg)) {
const mode = parseInt(arg.replace(/^(-m|--mode=)/, ''), 8)
if (isNaN(mode)) {
console.error(`invalid mode argument: ${arg}\nMust be an octal number.`)
process.exit(1)
}
})();
opts.mode = mode
} else
dirs.push(arg)
}

const mkdirp = require('../')
const impl = manual ? mkdirp.manual : mkdirp
if (dirs.length === 0)
console.error(usage())

Promise.all(dirs.map(dir => impl(dir, opts)))
.then(made => print ? made.forEach(m => m && console.log(m)) : null)
.catch(er => {
console.error(er.message)
if (er.code)
console.error(' code: ' + er.code)
process.exit(1)
})
12 changes: 0 additions & 12 deletions bin/usage.txt

This file was deleted.

6 changes: 0 additions & 6 deletions examples/pow.js

This file was deleted.

115 changes: 24 additions & 91 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,98 +1,31 @@
var path = require('path');
var fs = require('fs');
var _0777 = parseInt('0777', 8);
const optsArg = require('./lib/opts-arg.js')
const pathArg = require('./lib/path-arg.js')

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

function mkdirP (p, opts, f, made) {
if (typeof opts === 'function') {
f = opts;
opts = {};
}
else if (!opts || typeof opts !== 'object') {
opts = { mode: opts };
}

var mode = opts.mode;
var xfs = opts.fs || fs;

if (mode === undefined) {
mode = _0777 & (~process.umask());
}
if (!made) made = null;

var cb = f || function () {};
p = path.resolve(p);

xfs.mkdir(p, mode, function (er) {
if (!er) {
made = made || p;
return cb(null, made);
}
switch (er.code) {
case 'ENOENT':
mkdirP(path.dirname(p), opts, function (er, made) {
if (er) cb(er, made);
else mkdirP(p, opts, cb, made);
});
break;

// In the case of any other error, just see if there's a dir
// there already. If so, then hooray! If not, then something
// is borked.
default:
xfs.stat(p, function (er2, stat) {
// if the stat fails, then that's super weird.
// let the original error be the failure reason.
if (er2 || !stat.isDirectory()) cb(er, made)
else cb(null, made);
});
break;
}
});
const mkdirp = (path, opts) => {
path = pathArg(path)
opts = optsArg(opts)
return useNative(opts)
? mkdirpNative(path, opts)
: mkdirpManual(path, opts)
}

mkdirP.sync = function sync (p, opts, made) {
if (!opts || typeof opts !== 'object') {
opts = { mode: opts };
}

var mode = opts.mode;
var xfs = opts.fs || fs;

if (mode === undefined) {
mode = _0777 & (~process.umask());
}
if (!made) made = null;

p = path.resolve(p);

try {
xfs.mkdirSync(p, mode);
made = made || p;
}
catch (err0) {
switch (err0.code) {
case 'ENOENT' :
made = sync(path.dirname(p), opts, made);
sync(p, opts, made);
break;
const mkdirpSync = (path, opts) => {
path = pathArg(path)
opts = optsArg(opts)
return useNativeSync(opts)
? mkdirpNativeSync(path, opts)
: mkdirpManualSync(path, opts)
}

// In the case of any other error, just see if there's a dir
// there already. If so, then hooray! If not, then something
// is borked.
default:
var stat;
try {
stat = xfs.statSync(p);
}
catch (err1) {
throw err0;
}
if (!stat.isDirectory()) throw err0;
break;
}
}
mkdirp.sync = mkdirpSync
mkdirp.native = (path, opts) => mkdirpNative(pathArg(path), optsArg(opts))
mkdirp.manual = (path, opts) => mkdirpManual(pathArg(path), optsArg(opts))
mkdirp.nativeSync = (path, opts) => mkdirpNativeSync(pathArg(path), optsArg(opts))
mkdirp.manualSync = (path, opts) => mkdirpManualSync(pathArg(path), optsArg(opts))

return made;
};
module.exports = mkdirp
29 changes: 29 additions & 0 deletions lib/find-made.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const {dirname} = require('path')

const findMade = (opts, parent, path = undefined) => {
// we never want the 'made' return value to be a root directory
if (path === parent)
return Promise.resolve()

return opts.statAsync(parent).then(
st => st.isDirectory() ? path : undefined, // will fail later
er => er.code === 'ENOENT'
? findMade(opts, dirname(parent), parent)
: undefined
)
}

const findMadeSync = (opts, parent, path = undefined) => {
if (path === parent)
return undefined

try {
return opts.statSync(parent).isDirectory() ? path : undefined
} catch (er) {
return er.code === 'ENOENT'
? findMadeSync(opts, dirname(parent), parent)
: undefined
}
}

module.exports = {findMade, findMadeSync}
64 changes: 64 additions & 0 deletions lib/mkdirp-manual.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
const {dirname} = require('path')

const mkdirpManual = (path, opts, made) => {
opts.recursive = false
const parent = dirname(path)
if (parent === path) {
return opts.mkdirAsync(path, opts).catch(er => {
// swallowed by recursive implementation on posix systems
// any other error is a failure
if (er.code !== 'EISDIR')
throw er
})
}

return opts.mkdirAsync(path, opts).then(() => made || path, er => {
if (er.code === 'ENOENT')
return mkdirpManual(parent, opts)
.then(made => mkdirpManual(path, opts, made))
if (er.code !== 'EEXIST' && er.code !== 'EROFS')
throw er
return opts.statAsync(path).then(st => {
if (st.isDirectory())
return made
else
throw er
}, () => { throw er })
})
}

const mkdirpManualSync = (path, opts, made) => {
const parent = dirname(path)
opts.recursive = false

if (parent === path) {
try {
return opts.mkdirSync(path, opts)
} catch (er) {
// swallowed by recursive implementation on posix systems
// any other error is a failure
if (er.code !== 'EISDIR')
throw er
else
return
}
}

try {
opts.mkdirSync(path, opts)
return made || path
} catch (er) {
if (er.code === 'ENOENT')
return mkdirpManualSync(path, opts, mkdirpManualSync(parent, opts, made))
if (er.code !== 'EEXIST' && er.code !== 'EROFS')
throw er
try {
if (!opts.statSync(path).isDirectory())
throw er
} catch (_) {
throw er
}
}
}

module.exports = {mkdirpManual, mkdirpManualSync}
39 changes: 39 additions & 0 deletions lib/mkdirp-native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const {dirname} = require('path')
const {findMade, findMadeSync} = require('./find-made.js')
const {mkdirpManual, mkdirpManualSync} = require('./mkdirp-manual.js')

const mkdirpNative = (path, opts) => {
opts.recursive = true
const parent = dirname(path)
if (parent === path)
return opts.mkdirAsync(path, opts)

return findMade(opts, path).then(made =>
opts.mkdirAsync(path, opts).then(() => made)
.catch(er => {
if (er.code === 'ENOENT')
return mkdirpManual(path, opts)
else
throw er
}))
}

const mkdirpNativeSync = (path, opts) => {
opts.recursive = true
const parent = dirname(path)
if (parent === path)
return opts.mkdirSync(path, opts)

const made = findMadeSync(opts, path)
try {
opts.mkdirSync(path, opts)
return made
} catch (er) {
if (er.code === 'ENOENT')
return mkdirpManualSync(path, opts)
else
throw er
}
}

module.exports = {mkdirpNative, mkdirpNativeSync}
23 changes: 23 additions & 0 deletions lib/opts-arg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const { promisify } = require('util')
const fs = require('fs')
const optsArg = opts => {
if (!opts)
opts = { mode: 0o777, fs }
else if (typeof opts === 'object')
opts = { mode: 0o777, fs, ...opts }
else if (typeof opts === 'number')
opts = { mode: opts, fs }
else if (typeof opts === 'string')
opts = { mode: parseInt(opts, 8), fs }
else
throw new TypeError('invalid options argument')

opts.mkdir = opts.mkdir || opts.fs.mkdir || fs.mkdir
opts.mkdirAsync = promisify(opts.mkdir)
opts.stat = opts.stat || opts.fs.stat || fs.stat
opts.statAsync = promisify(opts.stat)
opts.statSync = opts.statSync || opts.fs.statSync || fs.statSync
opts.mkdirSync = opts.mkdirSync || opts.fs.mkdirSync || fs.mkdirSync
return opts
}
module.exports = optsArg
29 changes: 29 additions & 0 deletions lib/path-arg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const platform = process.env.__TESTING_MKDIRP_PLATFORM__ || process.platform
const { resolve, parse } = require('path')
const pathArg = path => {
if (/\0/.test(path)) {
// simulate same failure that node raises
throw Object.assign(
new TypeError('path must be a string without null bytes'),
{
path,
code: 'ERR_INVALID_ARG_VALUE',
}
)
}

path = resolve(path)
if (platform === 'win32') {
const badWinChars = /[*|"<>?:]/
const {root} = parse(path)
if (badWinChars.test(path.substr(root.length))) {
throw Object.assign(new Error('Illegal characters in path.'), {
path,
code: 'EINVAL',
})
}
}

return path
}
module.exports = pathArg
10 changes: 10 additions & 0 deletions lib/use-native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const fs = require('fs')

const version = process.env.__TESTING_MKDIRP_NODE_VERSION__ || process.version
const versArr = version.replace(/^v/, '').split('.')
const hasNative = +versArr[0] > 10 || +versArr[0] === 10 && +versArr[1] >= 12

const useNative = !hasNative ? () => false : opts => opts.mkdir === fs.mkdir
const useNativeSync = !hasNative ? () => false : opts => opts.mkdirSync === fs.mkdirSync

module.exports = {useNative, useNativeSync}
6 changes: 6 additions & 0 deletions map.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const {basename} = require('path')
const map = base =>
base === 'index.js' ? 'index.js'
: base === 'cmd.js' ? 'bin/cmd.js'
: `lib/${base}`
module.exports = test => map(basename(test))
3,361 changes: 3,361 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

37 changes: 27 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,27 +1,44 @@
{
"name": "mkdirp",
"description": "Recursively mkdir, like `mkdir -p`",
"version": "0.5.1",
"author": "James Halliday <mail@substack.net> (http://substack.net)",
"version": "1.0.4",
"main": "index.js",
"keywords": [
"mkdir",
"directory"
"directory",
"make dir",
"make",
"dir",
"recursive",
"native"
],
"repository": {
"type": "git",
"url": "https://github.com/substack/node-mkdirp.git"
"url": "https://github.com/isaacs/node-mkdirp.git"
},
"scripts": {
"test": "tap test/*.js"
"test": "tap",
"snap": "tap",
"preversion": "npm test",
"postversion": "npm publish",
"postpublish": "git push origin --follow-tags"
},
"dependencies": {
"minimist": "0.0.8"
"tap": {
"check-coverage": true,
"coverage-map": "map.js"
},
"devDependencies": {
"tap": "1",
"mock-fs": "2 >=2.7.0"
"require-inject": "^1.4.4",
"tap": "^14.10.7"
},
"bin": "bin/cmd.js",
"license": "MIT"
"license": "MIT",
"engines": {
"node": ">=10"
},
"files": [
"bin",
"lib",
"index.js"
]
}
236 changes: 201 additions & 35 deletions readme.markdown
Original file line number Diff line number Diff line change
@@ -1,82 +1,236 @@
# mkdirp

Like `mkdir -p`, but in node.js!
Like `mkdir -p`, but in Node.js!

[![build status](https://secure.travis-ci.org/substack/node-mkdirp.png)](http://travis-ci.org/substack/node-mkdirp)
Now with a modern API and no\* bugs!

<small>\* may contain some bugs</small>

# example

## pow.js

```js
var mkdirp = require('mkdirp');

mkdirp('/tmp/foo/bar/baz', function (err) {
if (err) console.error(err)
else console.log('pow!')
});
const mkdirp = require('mkdirp')

// return value is a Promise resolving to the first directory created
mkdirp('/tmp/foo/bar/baz').then(made =>
console.log(`made directories, starting with ${made}`))
```

Output
Output (where `/tmp/foo` already exists)

```
pow!
made directories, starting with /tmp/foo/bar
```

Or, if you don't have time to wait around for promises:

```js
const mkdirp = require('mkdirp')

// return value is the first directory created
const made = mkdirp.sync('/tmp/foo/bar/baz')
console.log(`made directories, starting with ${made}`)
```

And now /tmp/foo/bar/baz exists, huzzah!

# methods

```js
var mkdirp = require('mkdirp');
const mkdirp = require('mkdirp')
```

## mkdirp(dir, opts, cb)
## mkdirp(dir, [opts]) -> Promise<String | undefined>

Create a new directory and any necessary subdirectories at `dir` with octal
permission string `opts.mode`. If `opts` is a non-object, it will be treated as
the `opts.mode`.
permission string `opts.mode`. If `opts` is a string or number, it will be
treated as the `opts.mode`.

If `opts.mode` isn't specified, it defaults to `0777 & (~process.umask())`.
If `opts.mode` isn't specified, it defaults to `0o777 &
(~process.umask())`.

`cb(err, made)` fires with the error or the first directory `made`
that had to be created, if any.
Promise resolves to first directory `made` that had to be created, or
`undefined` if everything already exists. Promise rejects if any errors
are encountered. Note that, in the case of promise rejection, some
directories _may_ have been created, as recursive directory creation is not
an atomic operation.

You can optionally pass in an alternate `fs` implementation by passing in
`opts.fs`. Your implementation should have `opts.fs.mkdir(path, mode, cb)` and
`opts.fs.stat(path, cb)`.
`opts.fs`. Your implementation should have `opts.fs.mkdir(path, opts, cb)`
and `opts.fs.stat(path, cb)`.

## mkdirp.sync(dir, opts)
You can also override just one or the other of `mkdir` and `stat` by
passing in `opts.stat` or `opts.mkdir`, or providing an `fs` option that
only overrides one of these.

Synchronously create a new directory and any necessary subdirectories at `dir`
with octal permission string `opts.mode`. If `opts` is a non-object, it will be
treated as the `opts.mode`.
## mkdirp.sync(dir, opts) -> String|null

If `opts.mode` isn't specified, it defaults to `0777 & (~process.umask())`.
Synchronously create a new directory and any necessary subdirectories at
`dir` with octal permission string `opts.mode`. If `opts` is a string or
number, it will be treated as the `opts.mode`.

Returns the first directory that had to be created, if any.
If `opts.mode` isn't specified, it defaults to `0o777 &
(~process.umask())`.

Returns the first directory that had to be created, or undefined if
everything already exists.

You can optionally pass in an alternate `fs` implementation by passing in
`opts.fs`. Your implementation should have `opts.fs.mkdirSync(path, mode)` and
`opts.fs.statSync(path)`.
`opts.fs`. Your implementation should have `opts.fs.mkdirSync(path, mode)`
and `opts.fs.statSync(path)`.

You can also override just one or the other of `mkdirSync` and `statSync`
by passing in `opts.statSync` or `opts.mkdirSync`, or providing an `fs`
option that only overrides one of these.

# usage
## mkdirp.manual, mkdirp.manualSync

Use the manual implementation (not the native one). This is the default
when the native implementation is not available or the stat/mkdir
implementation is overridden.

## mkdirp.native, mkdirp.nativeSync

Use the native implementation (not the manual one). This is the default
when the native implementation is available and stat/mkdir are not
overridden.

# implementation

On Node.js v10.12.0 and above, use the native `fs.mkdir(p,
{recursive:true})` option, unless `fs.mkdir`/`fs.mkdirSync` has been
overridden by an option.

## native implementation

- If the path is a root directory, then pass it to the underlying
implementation and return the result/error. (In this case, it'll either
succeed or fail, but we aren't actually creating any dirs.)
- Walk up the path statting each directory, to find the first path that
will be created, `made`.
- Call `fs.mkdir(path, { recursive: true })` (or `fs.mkdirSync`)
- If error, raise it to the caller.
- Return `made`.

## manual implementation

- Call underlying `fs.mkdir` implementation, with `recursive: false`
- If error:
- If path is a root directory, raise to the caller and do not handle it
- If ENOENT, mkdirp parent dir, store result as `made`
- stat(path)
- If error, raise original `mkdir` error
- If directory, return `made`
- Else, raise original `mkdir` error
- else
- return `undefined` if a root dir, or `made` if set, or `path`

## windows vs unix caveat

On Windows file systems, attempts to create a root directory (ie, a drive
letter or root UNC path) will fail. If the root directory exists, then it
will fail with `EPERM`. If the root directory does not exist, then it will
fail with `ENOENT`.

On posix file systems, attempts to create a root directory (in recursive
mode) will succeed silently, as it is treated like just another directory
that already exists. (In non-recursive mode, of course, it fails with
`EEXIST`.)

In order to preserve this system-specific behavior (and because it's not as
if we can create the parent of a root directory anyway), attempts to create
a root directory are passed directly to the `fs` implementation, and any
errors encountered are not handled.

## native error caveat

The native implementation (as of at least Node.js v13.4.0) does not provide
appropriate errors in some cases (see
[nodejs/node#31481](https://github.com/nodejs/node/issues/31481) and
[nodejs/node#28015](https://github.com/nodejs/node/issues/28015)).

In order to work around this issue, the native implementation will fall
back to the manual implementation if an `ENOENT` error is encountered.

# choosing a recursive mkdir implementation

There are a few to choose from! Use the one that suits your needs best :D

## use `fs.mkdir(path, {recursive: true}, cb)` if:

- You wish to optimize performance even at the expense of other factors.
- You don't need to know the first dir created.
- You are ok with getting `ENOENT` as the error when some other problem is
the actual cause.
- You can limit your platforms to Node.js v10.12 and above.
- You're ok with using callbacks instead of promises.
- You don't need/want a CLI.
- You don't need to override the `fs` methods in use.

## use this module (mkdirp 1.x) if:

- You need to know the first directory that was created.
- You wish to use the native implementation if available, but fall back
when it's not.
- You prefer promise-returning APIs to callback-taking APIs.
- You want more useful error messages than the native recursive mkdir
provides (at least as of Node.js v13.4), and are ok with re-trying on
`ENOENT` to achieve this.
- You need (or at least, are ok with) a CLI.
- You need to override the `fs` methods in use.

## use [`make-dir`](http://npm.im/make-dir) if:

- You do not need to know the first dir created (and wish to save a few
`stat` calls when using the native implementation for this reason).
- You wish to use the native implementation if available, but fall back
when it's not.
- You prefer promise-returning APIs to callback-taking APIs.
- You are ok with occasionally getting `ENOENT` errors for failures that
are actually related to something other than a missing file system entry.
- You don't need/want a CLI.
- You need to override the `fs` methods in use.

## use mkdirp 0.x if:

- You need to know the first directory that was created.
- You need (or at least, are ok with) a CLI.
- You need to override the `fs` methods in use.
- You're ok with using callbacks instead of promises.
- You are not running on Windows, where the root-level ENOENT errors can
lead to infinite regress.
- You think vinyl just sounds warmer and richer for some weird reason.
- You are supporting truly ancient Node.js versions, before even the advent
of a `Promise` language primitive. (Please don't. You deserve better.)

# cli

This package also ships with a `mkdirp` command.

```
$ mkdirp -h
usage: mkdirp [DIR1,DIR2..] {OPTIONS}
Create each supplied directory including any necessary parent directories that
don't yet exist.
Create each supplied directory including any necessary parent directories
that don't yet exist.
If the directory already exists, do nothing.
OPTIONS are:
-m, --mode If a directory needs to be created, set the mode as an octal
permission string.
-m<mode> If a directory needs to be created, set the mode as an octal
--mode=<mode> permission string.
-v --version Print the mkdirp version number
-h --help Print this helpful banner
-p --print Print the first directories created for each path provided
--manual Use manual implementation, even if native is available
```

# install
@@ -87,13 +241,25 @@ With [npm](http://npmjs.org) do:
npm install mkdirp
```

to get the library, or
to get the library locally, or

```
npm install -g mkdirp
```

to get the command.
to get the command everywhere, or

```
npx mkdirp ...
```

to run the command without installing it globally.

# platform support

This module works on node v8, but only v10 and above are officially
supported, as Node v8 reached its LTS end of life 2020-01-01, which is in
the past, as of this writing.

# license

104 changes: 104 additions & 0 deletions tap-snapshots/test-cmd.js-TAP.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/* IMPORTANT
* This snapshot file is auto-generated, but designed for humans.
* It should be checked into source control and tracked carefully.
* Re-generate by setting TAP_SNAPSHOT=1 and running tests.
* Make sure to inspect the output below. Do not ignore changes!
*/
'use strict'
exports[`test/cmd.js TAP -h --help prints usage > --help output 1`] = `
Object {
"code": 0,
"signal": null,
"stderr": "",
"stdout": "\\nusage: mkdirp [DIR1,DIR2..] {OPTIONS}\\n\\n Create each supplied directory including any necessary parent directories\\n that don't yet exist.\\n\\n If the directory already exists, do nothing.\\n\\nOPTIONS are:\\n\\n -m<mode> If a directory needs to be created, set the mode as an octal\\n --mode=<mode> permission string.\\n\\n -v --version Print the mkdirp version number\\n\\n -h --help Print this helpful banner\\n\\n -p --print Print the first directories created for each path provided\\n\\n --manual Use manual implementation, even if native is available\\n\\n",
}
`

exports[`test/cmd.js TAP -v --version prints version > --version output 1`] = `
Object {
"code": 0,
"signal": null,
"stderr": "",
"stdout": "4.2.0-69.lol\\n",
}
`

exports[`test/cmd.js TAP failures > expect resolving Promise 1`] = `
Array [
Object {
"code": 1,
"signal": null,
"stderr": "nope\\n",
"stdout": "",
},
Object {
"code": 1,
"signal": null,
"stderr": "fail\\n code: EFAIL\\n",
"stdout": "",
},
]
`

exports[`test/cmd.js TAP invalid mode > expect resolving Promise 1`] = `
Object {
"code": 1,
"signal": null,
"stderr": "invalid mode argument: --mode=XYZ\\nMust be an octal number.\\n",
"stdout": "",
}
`

exports[`test/cmd.js TAP make dir named --help > expect resolving Promise 1`] = `
Object {
"code": 0,
"signal": null,
"stderr": "",
"stdout": "--help 0\\n",
}
`

exports[`test/cmd.js TAP making dirs > expect resolving Promise 1`] = `
Object {
"code": 0,
"signal": null,
"stderr": "",
"stdout": "",
}
`

exports[`test/cmd.js TAP manual > expect resolving Promise 1`] = `
Object {
"code": 0,
"signal": null,
"stderr": "",
"stdout": "MANUAL a 0\\nMANUAL b/c/d 0\\n",
}
`

exports[`test/cmd.js TAP no dirs -> stderr usage > expect resolving Promise 1`] = `
Object {
"code": 0,
"signal": null,
"stderr": "\\nusage: mkdirp [DIR1,DIR2..] {OPTIONS}\\n\\n Create each supplied directory including any necessary parent directories\\n that don't yet exist.\\n\\n If the directory already exists, do nothing.\\n\\nOPTIONS are:\\n\\n -m<mode> If a directory needs to be created, set the mode as an octal\\n --mode=<mode> permission string.\\n\\n -v --version Print the mkdirp version number\\n\\n -h --help Print this helpful banner\\n\\n -p --print Print the first directories created for each path provided\\n\\n --manual Use manual implementation, even if native is available\\n\\n",
"stdout": "",
}
`

exports[`test/cmd.js TAP noisily > expect resolving Promise 1`] = `
Object {
"code": 0,
"signal": null,
"stderr": "",
"stdout": "a 0\\nb/c/d 0\\n",
}
`

exports[`test/cmd.js TAP print modes > expect resolving Promise 1`] = `
Object {
"code": 0,
"signal": null,
"stderr": "",
"stdout": "a 509\\n",
}
`
41 changes: 0 additions & 41 deletions test/chmod.js

This file was deleted.

38 changes: 0 additions & 38 deletions test/clobber.js

This file was deleted.

69 changes: 69 additions & 0 deletions test/cmd.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
const cmd = require.resolve('../bin/cmd.js')
const requireInject = require('require-inject')

const {basename} = require('path')
const fakeMkdirp = (path, opts) =>
basename(path) === 'ERROR' ? Promise.reject(new Error('nope'))
: basename(path) === 'EFAIL' ? Promise.reject(Object.assign(new Error('fail'), { code: 'EFAIL' }))
: Promise.resolve(`${path} ${opts.mode || 0}`)

fakeMkdirp.manual = (path, opts) => fakeMkdirp(`MANUAL ${path}`, opts)

if (process.argv[2] === 'RUN') {
process.argv = [process.execPath, cmd, ...process.argv.slice(3)]
requireInject(cmd, {
'../': fakeMkdirp,
'../package.json': {
version: '4.2.0-69.lol',
},
})
} else {

const t = require('tap')

const {spawn} = require('child_process')
const run = (...args) => new Promise((res, rej) => {
const proc = spawn(process.execPath, [__filename, 'RUN', ...args])
const out = []
const err = []
proc.stdout.on('data', c => out.push(c))
proc.stderr.on('data', c => err.push(c))
proc.on('close', (code, signal) => {
res({
code,
signal,
stdout: Buffer.concat(out).toString('utf8'),
stderr: Buffer.concat(err).toString('utf8'),
})
})
})

t.test('-h --help prints usage', t => Promise.all([
run('-h'),
run('--help'),
]).then(res => {
t.strictSame(res[0], res[1], 'same for -h and --help')
t.matchSnapshot(res[0], '--help output')
}))

t.test('no dirs -> stderr usage', t => t.resolveMatchSnapshot(run()))

t.test('-v --version prints version', t => Promise.all([
run('-v'),
run('--version'),
]).then(res => {
t.strictSame(res[0], res[1], 'same for -v and --version')
t.matchSnapshot(res[0], '--version output')
}))

t.test('making dirs', t => t.resolveMatchSnapshot(run('a', 'b/c/d', 'e')))
t.test('noisily', t => t.resolveMatchSnapshot(run('a', 'b/c/d', '--print')))
t.test('manual', t => t.resolveMatchSnapshot(run('a', 'b/c/d', '-p', '--manual')))
t.test('print modes', t => t.resolveMatchSnapshot(run('a', '-m775', '-p')))
t.test('invalid mode', t => t.resolveMatchSnapshot(run('--mode=XYZ')))
t.test('make dir named --help', t => t.resolveMatchSnapshot(run('-p', '--', '--help')))
t.test('failures', t => t.resolveMatchSnapshot(Promise.all([
run('x/ERROR'),
run('x/EFAIL'),
])))
}
53 changes: 53 additions & 0 deletions test/find-made.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const t = require('tap')
const requireInject = require('require-inject')

const {basename, posix} = require('path')
const {promisify} = require('util')
const fs = require('fs')

const statAsync = (path) =>
basename(path) === 'error'
? Promise.reject(new Error('not a real error'))
: promisify(fs.stat)(path)

const statSync = path => {
if (basename(path) === 'error')
throw new Error('not a real error')
else
return fs.statSync(path)
}

const {findMade, findMadeSync} = requireInject('../lib/find-made.js', {
path: posix,
})

t.test('find what dir will be made', t => {
const dir = t.testdir({
file: 'txt',
subdir: {},
})

const o = {statAsync, statSync}

t.equal(findMadeSync(o, `${dir}/subdir/x/y/z`), `${dir}/subdir/x`)
t.equal(findMadeSync(o, `${dir}/subdir`), undefined)
t.equal(findMadeSync(o, `${dir}/file/x/y/z`), undefined)
t.equal(findMadeSync(o, `${dir}/file`, `${dir}/file/x`), undefined)
t.equal(findMadeSync(o, `${dir}/subdir/error`), undefined)
t.equal(findMadeSync(o, '/', '/'), undefined)
return Promise.all([
findMade(o, `${dir}/subdir/x/y/z`),
findMade(o, `${dir}/subdir`),
findMade(o, `${dir}/file/x/y/z`),
findMade(o, `${dir}/file`, `${dir}/file/x`),
findMade(o, `${dir}/subdir/error`),
findMade(o, '/', '/'),
]).then(made => t.strictSame(made, [
`${dir}/subdir/x`,
undefined,
undefined,
undefined,
undefined,
undefined,
]))
})
61 changes: 61 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
const t = require('tap')
const mkdirp = require('../')

t.test('module shape', t => {
t.isa(mkdirp, Function)
t.isa(mkdirp.sync, Function)
t.isa(mkdirp.manual, Function)
t.isa(mkdirp.manualSync, Function)
t.isa(mkdirp.native, Function)
t.isa(mkdirp.nativeSync, Function)
t.end()
})

t.test('basic making of dirs should work', t => {
const dir = t.testdir({ a: {} })
const {statSync, mkdir, mkdirSync} = require('fs')
const check = d => t.ok(statSync(d).isDirectory())
t.equal(mkdirp.sync(`${dir}/a/sync`), `${dir}/a/sync`)
check(`${dir}/a/sync`)
t.equal(mkdirp.sync(`${dir}/a/sync`), undefined)

t.equal(mkdirp.manualSync(`${dir}/a/manual-sync`), `${dir}/a/manual-sync`)
check(`${dir}/a/manual-sync`)
t.equal(mkdirp.manualSync(`${dir}/a/manual-sync`), undefined)

t.equal(mkdirp.nativeSync(`${dir}/a/native-sync`), `${dir}/a/native-sync`)
check(`${dir}/a/native-sync`)
t.equal(mkdirp.nativeSync(`${dir}/a/native-sync`), undefined)

// override to force the manual option
const myMkdir = (path, opts, cb) => mkdir(path, opts, cb)
const myMkdirSync = (path, opts) => mkdirSync(path, opts)
const opts = { mkdir: myMkdir, mkdirSync: myMkdirSync }
t.equal(mkdirp.sync(`${dir}/a/custom-sync`, opts), `${dir}/a/custom-sync`)
check(`${dir}/a/custom-sync`)
t.equal(mkdirp.sync(`${dir}/a/custom-sync`, opts), undefined)

return Promise.all([
mkdirp(`${dir}/a/async`),
mkdirp.manual(`${dir}/a/manual-async`),
mkdirp.native(`${dir}/a/native-async`),
mkdirp(`${dir}/a/custom-async`, opts),
]).then(made => {
t.strictSame(made, [
`${dir}/a/async`,
`${dir}/a/manual-async`,
`${dir}/a/native-async`,
`${dir}/a/custom-async`,
])
check(`${dir}/a/async`)
check(`${dir}/a/manual-async`)
check(`${dir}/a/native-async`)
check(`${dir}/a/custom-async`)
return Promise.all([
mkdirp(`${dir}/a/async`),
mkdirp.manual(`${dir}/a/manual-async`),
mkdirp.native(`${dir}/a/native-async`),
mkdirp(`${dir}/a/custom-async`, opts),
])
}).then(made => t.strictSame(made, [undefined, undefined, undefined, undefined]))
})
154 changes: 154 additions & 0 deletions test/mkdirp-manual.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
const t = require('tap')
const requireInject = require('require-inject')
const {promisify} = require('util')

const {stat, statSync, mkdir, mkdirSync} = require('fs')
const statAsync = promisify(stat)
const mkdirAsync = promisify(mkdir)

const path = require('path').posix
const {resolve} = path

const {mkdirpManual, mkdirpManualSync} =
requireInject('../lib/mkdirp-manual.js', { path })

t.test('mkdirpManual / just calls implementation', t => {
t.test('success is fine, of course', t => {
const opt = {
mkdirAsync: () => Promise.resolve('mkdirAsync impl'),
mkdirSync: () => 'mkdirSync impl',
recursive: true,
}
t.equal(mkdirpManualSync('/', opt), 'mkdirSync impl')
t.equal(opt.recursive, false)
opt.recursive = true
return mkdirpManual('/', opt).then(res => {
t.equal(res, 'mkdirAsync impl')
t.equal(opt.recursive, false)
})
})

t.test('EISDIR is expected and ignored', t => {
const opt = {
mkdirAsync: () => Promise.reject(Object.assign(new Error('is dir'), { code: 'EISDIR' })),
mkdirSync: () => {
throw Object.assign(new Error('is dir'), { code: 'EISDIR' })
},
// ensure it gets reset
recursive: true,
}
t.equal(mkdirpManualSync('/', opt), undefined)
t.equal(opt.recursive, false)
opt.recursive = true
return mkdirpManual('/', opt).then(made => {
t.equal(made, undefined)
t.equal(opt.recursive, false)
})
})

t.test('other failures are failures', t => {
const opt = {
mkdirAsync: () => Promise.reject(Object.assign(new Error('grolb'), { code: 'blorg' })),
mkdirSync: () => {
throw Object.assign(new Error('grolb'), { code: 'blorg' })
},
// ensure it gets reset
recursive: true,
}
t.throws(() => mkdirpManualSync('/', opt), { code: 'blorg' })
return t.rejects(mkdirpManual('/', opt), { code: 'blorg' })
})

t.end()
})

t.test('read-only file system, still succeed if dir exists', t => {
const dir = t.testdir({ foo: {} })
const opt = {
stat,
statAsync,
statSync,
mkdir,
mkdirAsync: () => Promise.reject(Object.assign(new Error('EROFS'), {
code: 'EROFS',
})),
mkdirSync: () => {
throw Object.assign(new Error('EROFS'), {
code: 'EROFS',
})
},
}
t.equal(mkdirpManualSync(`${dir}/foo`, opt), undefined)
return mkdirpManual(`${dir}/foo`, opt).then(made => t.equal(made, undefined))
})

t.test('recurse and return first dir made', t => {
const dir = t.testdir()
const opt = {
stat,
statAsync,
statSync,
mkdir,
mkdirAsync,
mkdirSync,
}

t.equal(mkdirpManualSync(`${dir}/sync/a/b`, opt), `${dir}/sync`)
t.equal(statSync(`${dir}/sync/a/b`).isDirectory(), true, 'made dir')
t.equal(mkdirpManualSync(`${dir}/sync/a/b`, opt), undefined)

return mkdirpManual(`${dir}/async/a/b`, opt).then(made => {
t.equal(made, `${dir}/async`)
return mkdirpManual(`${dir}/async/a/b`, opt)
}).then(made => t.equal(made, undefined))
})

t.test('unknown failure types are failures', t => {
const opt = {
mkdirAsync: () => Promise.reject(Object.assign(new Error('grolb'), { code: 'blorg' })),
mkdirSync: () => {
throw Object.assign(new Error('grolb'), { code: 'blorg' })
},
// ensure it gets reset
recursive: true,
}
t.throws(() => mkdirpManualSync('/x/y/z', opt), { code: 'blorg' })
return t.rejects(mkdirpManual('/x/y/z', opt), { code: 'blorg' })
})

t.test('cannot make dir over a file', t => {
const dir = t.testdir({ file: 'txt' })
const opt = {
stat,
statAsync,
statSync,
mkdir,
mkdirAsync,
mkdirSync,
}

t.throws(() => mkdirpManualSync(`${dir}/file`, opt), { code: 'EEXIST' })
return t.rejects(mkdirpManual(`${dir}/file`, opt), { code: 'EEXIST' })
})

t.test('try to overwrite a file, then fail to stat it', t => {
const dir = t.testdir({ file: 'txt' })
const file = `${dir}/file`
const er = Object.assign(new Error('nope'), { code: 'grob' })
const opt = {
statAsync: path => path === file
? Promise.reject(er)
: statAsync(path),
statSync: path => {
if (path === file)
throw er
else
return statSync(path)
},
mkdirAsync,
mkdirSync,
}

t.throws(() => mkdirpManualSync(`${dir}/file`, opt), { code: 'EEXIST' })
return t.rejects(mkdirpManual(`${dir}/file`, opt), { code: 'EEXIST' })
})
73 changes: 73 additions & 0 deletions test/mkdirp-native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
const t = require('tap')
const requireInject = require('require-inject')
const {promisify} = require('util')
const {stat, statSync} = require('fs')
const statAsync = promisify(stat)

const {mkdirpNative, mkdirpNativeSync} = requireInject('../lib/mkdirp-native', {
// just return an indicator that it was called
'../lib/mkdirp-manual.js': {
mkdirpManual: () => 'mkdirpManual',
mkdirpManualSync: () => 'mkdirpManualSync',
},
path: require('path').posix
})

const {resolve} = require('path').posix

t.test('mkdirpNative / just calls implementation', t => {
const opt = {
mkdirAsync: () => 'mkdirAsync impl',
mkdirSync: () => 'mkdirSync impl',
}
t.equal(mkdirpNative('/', opt), 'mkdirAsync impl')
t.equal(opt.recursive, true)
delete opt.recursive
t.equal(mkdirpNativeSync('/', opt), 'mkdirSync impl')
t.equal(opt.recursive, true)
t.end()
})

t.test('mkdirpNative calls impl and returns findMade', t => {
const opt = {
mkdirAsync: () => Promise.resolve(),
mkdirSync: () => undefined,
statAsync,
statSync,
}

const dir = t.testdir()
t.equal(mkdirpNativeSync(`${dir}/sync/a/b/c`, opt), `${dir}/sync`)
return mkdirpNative(`${dir}/async/a/b/c`, opt).then(made =>
t.equal(made, `${dir}/async`))
})

t.test('ENOENT error falls back to manual', t => {
const opt = {
mkdirAsync: () => Promise.reject(Object.assign(new Error('poo'), { code: 'ENOENT' })),
mkdirSync: () => {
throw Object.assign(new Error('poo'), { code: 'ENOENT' })
},
statAsync,
statSync,
}

const dir = t.testdir()
t.equal(mkdirpNativeSync(`${dir}/sync/a/b/c`, opt), 'mkdirpManualSync')
return mkdirpNative(`${dir}/async/a/b/c`, opt).then(made =>
t.equal(made, 'mkdirpManual'))
})

t.test('other errors are raised to caller', t => {
const opt = {
mkdirAsync: () => Promise.reject(Object.assign(new Error('poo'), { code: 'blorg' })),
mkdirSync: () => {
throw Object.assign(new Error('poo'), { code: 'blorg' })
},
statAsync,
statSync,
}

t.throws(() => mkdirpNativeSync('anything', opt), {code: 'blorg'})
return t.rejects(mkdirpNative('at/all', opt), {code: 'blorg'})
})
28 changes: 0 additions & 28 deletions test/mkdirp.js

This file was deleted.

37 changes: 37 additions & 0 deletions test/opts-arg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const t = require('tap')
const optsArg = require('../lib/opts-arg.js')
const mode = 0o777
const fs = require('fs')

const defFs = {
fs,
mkdir: fs.mkdir,
mkdirSync: fs.mkdirSync,
stat: fs.stat,
statSync: fs.statSync,
}

const stat = () => {}
stat.fake = true
const statSync = () => {}
statSync.fake = true

// arg, expect
const cases = {
null: [null, { mode, ...defFs }],
false: [false, { mode, ...defFs }],
undefined: [undefined, { mode, ...defFs }],
'empty object': [{}, { mode, ...defFs }],
'numeric mode': [0o775, {mode: 0o775, ...defFs}],
'string mode': ['775', {mode: 0o775, ...defFs}],
'empty custom fs': [{fs: {}}, {mode, ...defFs, fs: {}}],
'custom stat/statSync': [{stat, statSync}, {mode, ...defFs, stat, statSync}],
'custom fs with stat/statSync': [{fs: {stat, statSync}}, {mode, ...defFs, fs: {stat, statSync}, stat, statSync}],
}

for (const [name, c] of Object.entries(cases)) {
const [arg, expect] = c
t.match(optsArg(arg), expect, name)
}

t.throws(() => optsArg(() => {}), TypeError('invalid options argument'))
29 changes: 0 additions & 29 deletions test/opts_fs.js

This file was deleted.

27 changes: 0 additions & 27 deletions test/opts_fs_sync.js

This file was deleted.

40 changes: 40 additions & 0 deletions test/path-arg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const t = require('tap')

if (!process.env.__TESTING_MKDIRP_PLATFORM__) {
const fake = process.platform === 'win32' ? 'posix' : 'win32'
t.spawn(process.execPath, [__filename], {
env: {
...process.env,
__TESTING_MKDIRP_PLATFORM__: fake,
},
})
}

const platform = process.env.__TESTING_MKDIRP_PLATFORM__ || process.platform
const path = require('path').platform || require('path')
const requireInject = require('require-inject')
const pathArg = requireInject('../lib/path-arg.js', {
path,
})
const {resolve} = path

t.equal(pathArg('a/b/c'), resolve('a/b/c'))
t.throws(() => pathArg('a\0b'), Error('path must be a string without null bytes'))
if (platform === 'win32') {
const badPaths = [
'c:\\a\\b:c',
'c:\\a\\b*c',
'c:\\a\\b?c',
'c:\\a\\b<c',
'c:\\a\\b>c',
'c:\\a\\b|c',
'c:\\a\\b"c',
]
for (const path of badPaths) {
const er = Object.assign(new Error('Illegal characters in path'), {
path,
code: 'EINVAL',
})
t.throws(() => pathArg(path), er)
}
}
32 changes: 0 additions & 32 deletions test/perm.js

This file was deleted.

36 changes: 0 additions & 36 deletions test/perm_sync.js

This file was deleted.

37 changes: 0 additions & 37 deletions test/race.js

This file was deleted.

32 changes: 0 additions & 32 deletions test/rel.js

This file was deleted.

25 changes: 0 additions & 25 deletions test/return.js

This file was deleted.

24 changes: 0 additions & 24 deletions test/return_sync.js

This file was deleted.

19 changes: 0 additions & 19 deletions test/root.js

This file was deleted.

32 changes: 0 additions & 32 deletions test/sync.js

This file was deleted.

28 changes: 0 additions & 28 deletions test/umask.js

This file was deleted.

32 changes: 0 additions & 32 deletions test/umask_sync.js

This file was deleted.

30 changes: 30 additions & 0 deletions test/use-native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const t = require('tap')
const {mkdir, mkdirSync} = require('fs')
const {useNative, useNativeSync} = require('../lib/use-native.js')

if (!process.env.__TESTING_MKDIRP_NODE_VERSION__) {
t.spawn(process.execPath, [__filename], {
env: {
...process.env,
__TESTING_MKDIRP_NODE_VERSION__: 'v10.11.12',
},
})

t.spawn(process.execPath, [__filename], {
env: {
...process.env,
__TESTING_MKDIRP_NODE_VERSION__: 'v8.9.10',
},
})

// this one has the native impl
t.equal(useNative({mkdir}), true)
t.equal(useNative({mkdir: 1243}), false)
t.equal(useNativeSync({mkdirSync}), true)
t.equal(useNativeSync({mkdirSync: 1243}), false)
} else {
t.equal(useNative({mkdir}), false)
t.equal(useNative({mkdir: 1243}), false)
t.equal(useNativeSync({mkdirSync}), false)
t.equal(useNativeSync({mkdirSync: 1243}), false)
}