@atlaspack/fs 2.12.1-canary.3354
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/index.d.ts +23 -0
- package/lib/browser.js +1134 -0
- package/lib/browser.js.map +1 -0
- package/lib/index.js +2278 -0
- package/lib/index.js.map +1 -0
- package/lib/types.d.ts +0 -0
- package/package.json +73 -0
- package/src/MemoryFS.js +1036 -0
- package/src/NodeFS.browser.js +9 -0
- package/src/NodeFS.js +290 -0
- package/src/OverlayFS.js +439 -0
- package/src/find.js +81 -0
- package/src/index.js +45 -0
- package/test/OverlayFS.test.js +232 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import type {FileSystem} from '@atlaspack/types-internal';
|
|
3
|
+
|
|
4
|
+
// $FlowFixMe[prop-missing] handled by the throwing constructor
|
|
5
|
+
export class NodeFS implements FileSystem {
|
|
6
|
+
constructor() {
|
|
7
|
+
throw new Error("NodeFS isn't available in the browser");
|
|
8
|
+
}
|
|
9
|
+
}
|
package/src/NodeFS.js
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import type {ReadStream, Stats} from 'fs';
|
|
3
|
+
import type {Writable} from 'stream';
|
|
4
|
+
import type {
|
|
5
|
+
FilePath,
|
|
6
|
+
Encoding,
|
|
7
|
+
FileOptions,
|
|
8
|
+
FileSystem,
|
|
9
|
+
} from '@atlaspack/types-internal';
|
|
10
|
+
import type {
|
|
11
|
+
Event,
|
|
12
|
+
Options as WatcherOptions,
|
|
13
|
+
AsyncSubscription,
|
|
14
|
+
} from '@parcel/watcher';
|
|
15
|
+
|
|
16
|
+
import fs from 'graceful-fs';
|
|
17
|
+
import nativeFS from 'fs';
|
|
18
|
+
import ncp from 'ncp';
|
|
19
|
+
import path from 'path';
|
|
20
|
+
import {tmpdir} from 'os';
|
|
21
|
+
import {promisify} from 'util';
|
|
22
|
+
import {registerSerializableClass} from '@atlaspack/core';
|
|
23
|
+
import {hashFile} from '@atlaspack/utils';
|
|
24
|
+
import {getFeatureFlag} from '@atlaspack/feature-flags';
|
|
25
|
+
import watcher from '@parcel/watcher';
|
|
26
|
+
import packageJSON from '../package.json';
|
|
27
|
+
|
|
28
|
+
import * as searchNative from '@atlaspack/rust';
|
|
29
|
+
import * as searchJS from './find';
|
|
30
|
+
|
|
31
|
+
// Most of this can go away once we only support Node 10+, which includes
|
|
32
|
+
// require('fs').promises
|
|
33
|
+
|
|
34
|
+
const realpath = promisify(
|
|
35
|
+
process.platform === 'win32' ? fs.realpath : fs.realpath.native,
|
|
36
|
+
);
|
|
37
|
+
const isPnP = process.versions.pnp != null;
|
|
38
|
+
|
|
39
|
+
function getWatchmanWatcher(): typeof watcher {
|
|
40
|
+
// This is here to trick atlaspack into ignoring this require...
|
|
41
|
+
const packageName = ['@atlaspack', 'watcher-watchman-js'].join('/');
|
|
42
|
+
|
|
43
|
+
// $FlowFixMe
|
|
44
|
+
return require(packageName);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class NodeFS implements FileSystem {
|
|
48
|
+
readFile: any = promisify(fs.readFile);
|
|
49
|
+
copyFile: any = promisify(fs.copyFile);
|
|
50
|
+
stat: any = promisify(fs.stat);
|
|
51
|
+
readdir: any = promisify(fs.readdir);
|
|
52
|
+
symlink: any = promisify(fs.symlink);
|
|
53
|
+
unlink: any = promisify(fs.unlink);
|
|
54
|
+
utimes: any = promisify(fs.utimes);
|
|
55
|
+
ncp: any = promisify(ncp);
|
|
56
|
+
createReadStream: (path: string, options?: any) => ReadStream =
|
|
57
|
+
fs.createReadStream;
|
|
58
|
+
cwd: () => string = () => process.cwd();
|
|
59
|
+
chdir: (directory: string) => void = directory => process.chdir(directory);
|
|
60
|
+
|
|
61
|
+
statSync: (path: string) => Stats = path => fs.statSync(path);
|
|
62
|
+
realpathSync: (path: string, cache?: any) => string =
|
|
63
|
+
process.platform === 'win32' ? fs.realpathSync : fs.realpathSync.native;
|
|
64
|
+
existsSync: (path: string) => boolean = fs.existsSync;
|
|
65
|
+
readdirSync: any = (fs.readdirSync: any);
|
|
66
|
+
findAncestorFile: any = isPnP
|
|
67
|
+
? (...args) => searchJS.findAncestorFile(this, ...args)
|
|
68
|
+
: searchNative.findAncestorFile;
|
|
69
|
+
findNodeModule: any = isPnP
|
|
70
|
+
? (...args) => searchJS.findNodeModule(this, ...args)
|
|
71
|
+
: searchNative.findNodeModule;
|
|
72
|
+
findFirstFile: any = isPnP
|
|
73
|
+
? (...args) => searchJS.findFirstFile(this, ...args)
|
|
74
|
+
: searchNative.findFirstFile;
|
|
75
|
+
|
|
76
|
+
watcher(): typeof watcher {
|
|
77
|
+
return getFeatureFlag('useWatchmanWatcher')
|
|
78
|
+
? getWatchmanWatcher()
|
|
79
|
+
: watcher;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
createWriteStream(filePath: string, options: any): Writable {
|
|
83
|
+
// Make createWriteStream atomic
|
|
84
|
+
let tmpFilePath = getTempFilePath(filePath);
|
|
85
|
+
let failed = false;
|
|
86
|
+
|
|
87
|
+
const move = async () => {
|
|
88
|
+
if (!failed) {
|
|
89
|
+
try {
|
|
90
|
+
await fs.promises.rename(tmpFilePath, filePath);
|
|
91
|
+
} catch (e) {
|
|
92
|
+
// This is adapted from fs-write-stream-atomic. Apparently
|
|
93
|
+
// Windows doesn't like renaming when the target already exists.
|
|
94
|
+
if (
|
|
95
|
+
process.platform === 'win32' &&
|
|
96
|
+
e.syscall &&
|
|
97
|
+
e.syscall === 'rename' &&
|
|
98
|
+
e.code &&
|
|
99
|
+
e.code === 'EPERM'
|
|
100
|
+
) {
|
|
101
|
+
let [hashTmp, hashTarget] = await Promise.all([
|
|
102
|
+
hashFile(this, tmpFilePath),
|
|
103
|
+
hashFile(this, filePath),
|
|
104
|
+
]);
|
|
105
|
+
|
|
106
|
+
await this.unlink(tmpFilePath);
|
|
107
|
+
|
|
108
|
+
if (hashTmp != hashTarget) {
|
|
109
|
+
throw e;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
let writeStream = fs.createWriteStream(tmpFilePath, {
|
|
117
|
+
...options,
|
|
118
|
+
fs: {
|
|
119
|
+
...fs,
|
|
120
|
+
close: (fd, cb) => {
|
|
121
|
+
fs.close(fd, err => {
|
|
122
|
+
if (err) {
|
|
123
|
+
cb(err);
|
|
124
|
+
} else {
|
|
125
|
+
move().then(
|
|
126
|
+
() => cb(),
|
|
127
|
+
err => cb(err),
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
writeStream.once('error', () => {
|
|
136
|
+
failed = true;
|
|
137
|
+
fs.unlinkSync(tmpFilePath);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return writeStream;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async writeFile(
|
|
144
|
+
filePath: FilePath,
|
|
145
|
+
contents: Buffer | string,
|
|
146
|
+
options: ?FileOptions,
|
|
147
|
+
): Promise<void> {
|
|
148
|
+
let tmpFilePath = getTempFilePath(filePath);
|
|
149
|
+
await fs.promises.writeFile(tmpFilePath, contents, options);
|
|
150
|
+
await fs.promises.rename(tmpFilePath, filePath);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
readFileSync(filePath: FilePath, encoding?: Encoding): any {
|
|
154
|
+
if (encoding != null) {
|
|
155
|
+
return fs.readFileSync(filePath, encoding);
|
|
156
|
+
}
|
|
157
|
+
return fs.readFileSync(filePath);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async realpath(originalPath: string): Promise<string> {
|
|
161
|
+
try {
|
|
162
|
+
return await realpath(originalPath, 'utf8');
|
|
163
|
+
} catch (e) {
|
|
164
|
+
// do nothing
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return originalPath;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
exists(filePath: FilePath): Promise<boolean> {
|
|
171
|
+
return new Promise(resolve => {
|
|
172
|
+
fs.exists(filePath, resolve);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
watch(
|
|
177
|
+
dir: FilePath,
|
|
178
|
+
fn: (err: ?Error, events: Array<Event>) => mixed,
|
|
179
|
+
opts: WatcherOptions,
|
|
180
|
+
): Promise<AsyncSubscription> {
|
|
181
|
+
return this.watcher().subscribe(dir, fn, opts);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
getEventsSince(
|
|
185
|
+
dir: FilePath,
|
|
186
|
+
snapshot: FilePath,
|
|
187
|
+
opts: WatcherOptions,
|
|
188
|
+
): Promise<Array<Event>> {
|
|
189
|
+
return this.watcher().getEventsSince(dir, snapshot, opts);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async writeSnapshot(
|
|
193
|
+
dir: FilePath,
|
|
194
|
+
snapshot: FilePath,
|
|
195
|
+
opts: WatcherOptions,
|
|
196
|
+
): Promise<void> {
|
|
197
|
+
await this.watcher().writeSnapshot(dir, snapshot, opts);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
static deserialize(): NodeFS {
|
|
201
|
+
return new NodeFS();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
serialize(): null {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async mkdirp(filePath: FilePath): Promise<void> {
|
|
209
|
+
await nativeFS.promises.mkdir(filePath, {recursive: true});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async rimraf(filePath: FilePath): Promise<void> {
|
|
213
|
+
if (fs.promises.rm) {
|
|
214
|
+
await fs.promises.rm(filePath, {recursive: true, force: true});
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// fs.promises.rm is not supported in node 12...
|
|
219
|
+
let stat;
|
|
220
|
+
try {
|
|
221
|
+
stat = await this.stat(filePath);
|
|
222
|
+
} catch (err) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (stat.isDirectory()) {
|
|
227
|
+
// $FlowFixMe
|
|
228
|
+
await nativeFS.promises.rmdir(filePath, {recursive: true});
|
|
229
|
+
} else {
|
|
230
|
+
await nativeFS.promises.unlink(filePath);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
registerSerializableClass(`${packageJSON.version}:NodeFS`, NodeFS);
|
|
236
|
+
|
|
237
|
+
let writeStreamCalls = 0;
|
|
238
|
+
|
|
239
|
+
let threadId;
|
|
240
|
+
try {
|
|
241
|
+
({threadId} = require('worker_threads'));
|
|
242
|
+
} catch {
|
|
243
|
+
//
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let useOsTmpDir;
|
|
247
|
+
|
|
248
|
+
function shouldUseOsTmpDir(filePath) {
|
|
249
|
+
if (useOsTmpDir != null) {
|
|
250
|
+
return useOsTmpDir;
|
|
251
|
+
}
|
|
252
|
+
try {
|
|
253
|
+
const tmpDir = tmpdir();
|
|
254
|
+
nativeFS.accessSync(
|
|
255
|
+
tmpDir,
|
|
256
|
+
nativeFS.constants.R_OK | nativeFS.constants.W_OK,
|
|
257
|
+
);
|
|
258
|
+
const tmpDirStats = nativeFS.statSync(tmpDir);
|
|
259
|
+
const filePathStats = nativeFS.statSync(filePath);
|
|
260
|
+
// Check the tmpdir is on the same partition as the target directory.
|
|
261
|
+
// This is required to ensure renaming is an atomic operation.
|
|
262
|
+
useOsTmpDir = tmpDirStats.dev === filePathStats.dev;
|
|
263
|
+
} catch (e) {
|
|
264
|
+
// We don't have read/write access to the OS tmp directory
|
|
265
|
+
useOsTmpDir = false;
|
|
266
|
+
}
|
|
267
|
+
return useOsTmpDir;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Generate a temporary file path used for atomic writing of files.
|
|
271
|
+
function getTempFilePath(filePath: FilePath) {
|
|
272
|
+
writeStreamCalls = writeStreamCalls % Number.MAX_SAFE_INTEGER;
|
|
273
|
+
|
|
274
|
+
let tmpFilePath = filePath;
|
|
275
|
+
|
|
276
|
+
// If possible, write the tmp file to the OS tmp directory
|
|
277
|
+
// This reduces the amount of FS events the watcher needs to process during the build
|
|
278
|
+
if (shouldUseOsTmpDir(filePath)) {
|
|
279
|
+
tmpFilePath = path.join(tmpdir(), path.basename(filePath));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
tmpFilePath +
|
|
284
|
+
'.' +
|
|
285
|
+
process.pid +
|
|
286
|
+
(threadId != null ? '.' + threadId : '') +
|
|
287
|
+
'.' +
|
|
288
|
+
(writeStreamCalls++).toString(36)
|
|
289
|
+
);
|
|
290
|
+
}
|