1 | 'use strict';
|
2 | const fs = require('fs');
|
3 | const path = require('path');
|
4 | const {promisify} = require('util');
|
5 | const semver = require('semver');
|
6 |
|
7 | const useNativeRecursiveOption = semver.satisfies(process.version, '>=10.12.0');
|
8 |
|
9 |
|
10 |
|
11 | const checkPath = pth => {
|
12 | if (process.platform === 'win32') {
|
13 | const pathHasInvalidWinCharacters = /[<>:"|?*]/.test(pth.replace(path.parse(pth).root, ''));
|
14 |
|
15 | if (pathHasInvalidWinCharacters) {
|
16 | const error = new Error(`Path contains invalid characters: ${pth}`);
|
17 | error.code = 'EINVAL';
|
18 | throw error;
|
19 | }
|
20 | }
|
21 | };
|
22 |
|
23 | const processOptions = options => {
|
24 |
|
25 | const defaults = {
|
26 | mode: 0o777,
|
27 | fs
|
28 | };
|
29 |
|
30 | return {
|
31 | ...defaults,
|
32 | ...options
|
33 | };
|
34 | };
|
35 |
|
36 | const permissionError = pth => {
|
37 |
|
38 |
|
39 | const error = new Error(`operation not permitted, mkdir '${pth}'`);
|
40 | error.code = 'EPERM';
|
41 | error.errno = -4048;
|
42 | error.path = pth;
|
43 | error.syscall = 'mkdir';
|
44 | return error;
|
45 | };
|
46 |
|
47 | const makeDir = async (input, options) => {
|
48 | checkPath(input);
|
49 | options = processOptions(options);
|
50 |
|
51 | const mkdir = promisify(options.fs.mkdir);
|
52 | const stat = promisify(options.fs.stat);
|
53 |
|
54 | if (useNativeRecursiveOption && options.fs.mkdir === fs.mkdir) {
|
55 | const pth = path.resolve(input);
|
56 |
|
57 | await mkdir(pth, {
|
58 | mode: options.mode,
|
59 | recursive: true
|
60 | });
|
61 |
|
62 | return pth;
|
63 | }
|
64 |
|
65 | const make = async pth => {
|
66 | try {
|
67 | await mkdir(pth, options.mode);
|
68 |
|
69 | return pth;
|
70 | } catch (error) {
|
71 | if (error.code === 'EPERM') {
|
72 | throw error;
|
73 | }
|
74 |
|
75 | if (error.code === 'ENOENT') {
|
76 | if (path.dirname(pth) === pth) {
|
77 | throw permissionError(pth);
|
78 | }
|
79 |
|
80 | if (error.message.includes('null bytes')) {
|
81 | throw error;
|
82 | }
|
83 |
|
84 | await make(path.dirname(pth));
|
85 |
|
86 | return make(pth);
|
87 | }
|
88 |
|
89 | try {
|
90 | const stats = await stat(pth);
|
91 | if (!stats.isDirectory()) {
|
92 | throw new Error('The path is not a directory');
|
93 | }
|
94 | } catch (_) {
|
95 | throw error;
|
96 | }
|
97 |
|
98 | return pth;
|
99 | }
|
100 | };
|
101 |
|
102 | return make(path.resolve(input));
|
103 | };
|
104 |
|
105 | module.exports = makeDir;
|
106 |
|
107 | module.exports.sync = (input, options) => {
|
108 | checkPath(input);
|
109 | options = processOptions(options);
|
110 |
|
111 | if (useNativeRecursiveOption && options.fs.mkdirSync === fs.mkdirSync) {
|
112 | const pth = path.resolve(input);
|
113 |
|
114 | fs.mkdirSync(pth, {
|
115 | mode: options.mode,
|
116 | recursive: true
|
117 | });
|
118 |
|
119 | return pth;
|
120 | }
|
121 |
|
122 | const make = pth => {
|
123 | try {
|
124 | options.fs.mkdirSync(pth, options.mode);
|
125 | } catch (error) {
|
126 | if (error.code === 'EPERM') {
|
127 | throw error;
|
128 | }
|
129 |
|
130 | if (error.code === 'ENOENT') {
|
131 | if (path.dirname(pth) === pth) {
|
132 | throw permissionError(pth);
|
133 | }
|
134 |
|
135 | if (error.message.includes('null bytes')) {
|
136 | throw error;
|
137 | }
|
138 |
|
139 | make(path.dirname(pth));
|
140 | return make(pth);
|
141 | }
|
142 |
|
143 | try {
|
144 | if (!options.fs.statSync(pth).isDirectory()) {
|
145 | throw new Error('The path is not a directory');
|
146 | }
|
147 | } catch (_) {
|
148 | throw error;
|
149 | }
|
150 | }
|
151 |
|
152 | return pth;
|
153 | };
|
154 |
|
155 | return make(path.resolve(input));
|
156 | };
|