Yang Guo | 4fd355c | 2019-09-19 08:59:03 | [diff] [blame^] | 1 | 'use strict'; |
| 2 | const fs = require('fs'); |
| 3 | const path = require('path'); |
| 4 | const pify = require('pify'); |
| 5 | const semver = require('semver'); |
| 6 | |
| 7 | const defaults = { |
| 8 | mode: 0o777 & (~process.umask()), |
| 9 | fs |
| 10 | }; |
| 11 | |
| 12 | const useNativeRecursiveOption = semver.satisfies(process.version, '>=10.12.0'); |
| 13 | |
| 14 | // https://github.com/nodejs/node/issues/8987 |
| 15 | // https://github.com/libuv/libuv/pull/1088 |
| 16 | const checkPath = pth => { |
| 17 | if (process.platform === 'win32') { |
| 18 | const pathHasInvalidWinCharacters = /[<>:"|?*]/.test(pth.replace(path.parse(pth).root, '')); |
| 19 | |
| 20 | if (pathHasInvalidWinCharacters) { |
| 21 | const error = new Error(`Path contains invalid characters: ${pth}`); |
| 22 | error.code = 'EINVAL'; |
| 23 | throw error; |
| 24 | } |
| 25 | } |
| 26 | }; |
| 27 | |
| 28 | const permissionError = pth => { |
| 29 | // This replicates the exception of `fs.mkdir` with native the |
| 30 | // `recusive` option when run on an invalid drive under Windows. |
| 31 | const error = new Error(`operation not permitted, mkdir '${pth}'`); |
| 32 | error.code = 'EPERM'; |
| 33 | error.errno = -4048; |
| 34 | error.path = pth; |
| 35 | error.syscall = 'mkdir'; |
| 36 | return error; |
| 37 | }; |
| 38 | |
| 39 | const makeDir = (input, options) => Promise.resolve().then(() => { |
| 40 | checkPath(input); |
| 41 | options = Object.assign({}, defaults, options); |
| 42 | |
| 43 | // TODO: Use util.promisify when targeting Node.js 8 |
| 44 | const mkdir = pify(options.fs.mkdir); |
| 45 | const stat = pify(options.fs.stat); |
| 46 | |
| 47 | if (useNativeRecursiveOption && options.fs.mkdir === fs.mkdir) { |
| 48 | const pth = path.resolve(input); |
| 49 | |
| 50 | return mkdir(pth, { |
| 51 | mode: options.mode, |
| 52 | recursive: true |
| 53 | }).then(() => pth); |
| 54 | } |
| 55 | |
| 56 | const make = pth => { |
| 57 | return mkdir(pth, options.mode) |
| 58 | .then(() => pth) |
| 59 | .catch(error => { |
| 60 | if (error.code === 'EPERM') { |
| 61 | throw error; |
| 62 | } |
| 63 | |
| 64 | if (error.code === 'ENOENT') { |
| 65 | if (path.dirname(pth) === pth) { |
| 66 | throw permissionError(pth); |
| 67 | } |
| 68 | |
| 69 | if (error.message.includes('null bytes')) { |
| 70 | throw error; |
| 71 | } |
| 72 | |
| 73 | return make(path.dirname(pth)).then(() => make(pth)); |
| 74 | } |
| 75 | |
| 76 | return stat(pth) |
| 77 | .then(stats => stats.isDirectory() ? pth : Promise.reject()) |
| 78 | .catch(() => { |
| 79 | throw error; |
| 80 | }); |
| 81 | }); |
| 82 | }; |
| 83 | |
| 84 | return make(path.resolve(input)); |
| 85 | }); |
| 86 | |
| 87 | module.exports = makeDir; |
| 88 | module.exports.default = makeDir; |
| 89 | |
| 90 | module.exports.sync = (input, options) => { |
| 91 | checkPath(input); |
| 92 | options = Object.assign({}, defaults, options); |
| 93 | |
| 94 | if (useNativeRecursiveOption && options.fs.mkdirSync === fs.mkdirSync) { |
| 95 | const pth = path.resolve(input); |
| 96 | |
| 97 | fs.mkdirSync(pth, { |
| 98 | mode: options.mode, |
| 99 | recursive: true |
| 100 | }); |
| 101 | |
| 102 | return pth; |
| 103 | } |
| 104 | |
| 105 | const make = pth => { |
| 106 | try { |
| 107 | options.fs.mkdirSync(pth, options.mode); |
| 108 | } catch (error) { |
| 109 | if (error.code === 'EPERM') { |
| 110 | throw error; |
| 111 | } |
| 112 | |
| 113 | if (error.code === 'ENOENT') { |
| 114 | if (path.dirname(pth) === pth) { |
| 115 | throw permissionError(pth); |
| 116 | } |
| 117 | |
| 118 | if (error.message.includes('null bytes')) { |
| 119 | throw error; |
| 120 | } |
| 121 | |
| 122 | make(path.dirname(pth)); |
| 123 | return make(pth); |
| 124 | } |
| 125 | |
| 126 | try { |
| 127 | if (!options.fs.statSync(pth).isDirectory()) { |
| 128 | throw new Error('The path is not a directory'); |
| 129 | } |
| 130 | } catch (_) { |
| 131 | throw error; |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | return pth; |
| 136 | }; |
| 137 | |
| 138 | return make(path.resolve(input)); |
| 139 | }; |