@zenfs/core 1.7.2 → 1.8.1
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/dist/backends/backend.js +3 -4
- package/dist/backends/fetch.d.ts +17 -18
- package/dist/backends/fetch.js +95 -58
- package/dist/backends/index.d.ts +2 -1
- package/dist/backends/index.js +2 -1
- package/dist/backends/memory.d.ts +1 -1
- package/dist/backends/overlay.d.ts +8 -14
- package/dist/backends/overlay.js +38 -31
- package/dist/backends/passthrough.d.ts +8 -3
- package/dist/backends/passthrough.js +148 -4
- package/dist/backends/port/fs.d.ts +15 -49
- package/dist/backends/port/fs.js +28 -116
- package/dist/backends/port/rpc.d.ts +13 -6
- package/dist/backends/port/rpc.js +9 -7
- package/dist/backends/store/file_index.d.ts +38 -0
- package/dist/backends/store/file_index.js +76 -0
- package/dist/backends/store/fs.d.ts +39 -34
- package/dist/backends/store/fs.js +407 -238
- package/dist/backends/store/index_fs.d.ts +34 -0
- package/dist/backends/store/index_fs.js +67 -0
- package/dist/backends/store/inode.d.ts +26 -8
- package/dist/backends/store/inode.js +92 -91
- package/dist/backends/store/simple.d.ts +20 -20
- package/dist/backends/store/simple.js +3 -4
- package/dist/backends/store/store.d.ts +12 -12
- package/dist/backends/store/store.js +4 -6
- package/dist/devices.d.ts +44 -21
- package/dist/devices.js +110 -55
- package/dist/file.d.ts +111 -7
- package/dist/file.js +324 -92
- package/dist/filesystem.d.ts +44 -4
- package/dist/mixins/async.js +12 -6
- package/dist/mixins/mutexed.d.ts +8 -3
- package/dist/mixins/mutexed.js +57 -1
- package/dist/mixins/readonly.d.ts +17 -16
- package/dist/mixins/readonly.js +6 -0
- package/dist/mixins/sync.d.ts +1 -1
- package/dist/stats.d.ts +12 -6
- package/dist/stats.js +14 -6
- package/dist/utils.d.ts +23 -3
- package/dist/utils.js +58 -10
- package/dist/vfs/async.js +1 -1
- package/dist/vfs/constants.d.ts +2 -2
- package/dist/vfs/constants.js +2 -2
- package/dist/vfs/dir.js +3 -1
- package/dist/vfs/index.js +4 -1
- package/dist/vfs/promises.js +33 -13
- package/dist/vfs/shared.js +2 -0
- package/dist/vfs/sync.js +25 -13
- package/dist/vfs/types.d.ts +15 -0
- package/eslint.shared.js +1 -0
- package/package.json +2 -3
- package/readme.md +2 -2
- package/scripts/test.js +73 -11
- package/tests/common/mutex.test.ts +1 -1
- package/tests/fetch/run.sh +16 -0
- package/tests/fetch/server.ts +49 -0
- package/tests/fetch/setup.ts +13 -0
- package/tests/fs/read.test.ts +10 -10
- package/tests/fs/times.test.ts +2 -2
- package/tests/fs/write.test.ts +6 -11
- package/tests/setup/index.ts +38 -0
- package/tests/setup/port.ts +15 -0
- package/dist/backends/file_index.d.ts +0 -63
- package/dist/backends/file_index.js +0 -163
- package/tests/common/async.test.ts +0 -31
- package/tests/setup/cow+fetch.ts +0 -45
- /package/tests/fs/{appendFile.test.ts → append.test.ts} +0 -0
package/dist/vfs/shared.js
CHANGED
|
@@ -113,6 +113,7 @@ export function fixError(e, paths) {
|
|
|
113
113
|
e.path = fixPaths(e.path, paths);
|
|
114
114
|
return e;
|
|
115
115
|
}
|
|
116
|
+
/* node:coverage disable */
|
|
116
117
|
/**
|
|
117
118
|
* @internal @deprecated
|
|
118
119
|
*/
|
|
@@ -124,6 +125,7 @@ export function mountObject(mounts) {
|
|
|
124
125
|
mount(point, fs);
|
|
125
126
|
}
|
|
126
127
|
}
|
|
128
|
+
/* node:coverage enable */
|
|
127
129
|
/**
|
|
128
130
|
* @internal @hidden
|
|
129
131
|
*/
|
package/dist/vfs/sync.js
CHANGED
|
@@ -134,7 +134,7 @@ lstatSync;
|
|
|
134
134
|
export function truncateSync(path, len = 0) {
|
|
135
135
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
136
136
|
try {
|
|
137
|
-
const file = __addDisposableResource(env_1, _openSync.call(this, path, 'r+'), false);
|
|
137
|
+
const file = __addDisposableResource(env_1, _openSync.call(this, path, { flag: 'r+' }), false);
|
|
138
138
|
len || (len = 0);
|
|
139
139
|
if (len < 0) {
|
|
140
140
|
throw new ErrnoError(Errno.EINVAL);
|
|
@@ -176,11 +176,11 @@ function applySetId(file, uid, gid) {
|
|
|
176
176
|
file.chownSync(parent.mode & constants.S_ISUID ? parent.uid : uid, // manually apply setuid/setgid
|
|
177
177
|
parent.mode & constants.S_ISGID ? parent.gid : gid);
|
|
178
178
|
}
|
|
179
|
-
function _openSync(path,
|
|
179
|
+
function _openSync(path, opt) {
|
|
180
180
|
var _a;
|
|
181
181
|
path = normalizePath(path);
|
|
182
|
-
const mode = normalizeMode(
|
|
183
|
-
path =
|
|
182
|
+
const mode = normalizeMode(opt.mode, 0o644), flag = parseFlag(opt.flag);
|
|
183
|
+
path = opt.preserveSymlinks ? path : realpathSync.call(this, path);
|
|
184
184
|
const { fs, path: resolved } = resolveMount(path, this);
|
|
185
185
|
let stats;
|
|
186
186
|
try {
|
|
@@ -203,6 +203,8 @@ function _openSync(path, _flag, _mode, resolveSymlinks = true) {
|
|
|
203
203
|
}
|
|
204
204
|
const { euid: uid, egid: gid } = (_a = this === null || this === void 0 ? void 0 : this.credentials) !== null && _a !== void 0 ? _a : credentials;
|
|
205
205
|
const file = fs.createFileSync(resolved, flag, mode, { uid, gid });
|
|
206
|
+
if (!opt.allowDirectory && mode & constants.S_IFDIR)
|
|
207
|
+
throw ErrnoError.With('EISDIR', path, '_open');
|
|
206
208
|
applySetId(file, uid, gid);
|
|
207
209
|
return file;
|
|
208
210
|
}
|
|
@@ -216,6 +218,8 @@ function _openSync(path, _flag, _mode, resolveSymlinks = true) {
|
|
|
216
218
|
if (isTruncating(flag)) {
|
|
217
219
|
file.truncateSync(0);
|
|
218
220
|
}
|
|
221
|
+
if (!opt.allowDirectory && stats.mode & constants.S_IFDIR)
|
|
222
|
+
throw ErrnoError.With('EISDIR', path, '_open');
|
|
219
223
|
return file;
|
|
220
224
|
}
|
|
221
225
|
/**
|
|
@@ -223,7 +227,7 @@ function _openSync(path, _flag, _mode, resolveSymlinks = true) {
|
|
|
223
227
|
* @see http://www.manpagez.com/man/2/open/
|
|
224
228
|
*/
|
|
225
229
|
export function openSync(path, flag, mode = constants.F_OK) {
|
|
226
|
-
return file2fd(_openSync.call(this, path, flag, mode
|
|
230
|
+
return file2fd(_openSync.call(this, path, { flag, mode }));
|
|
227
231
|
}
|
|
228
232
|
openSync;
|
|
229
233
|
/**
|
|
@@ -231,13 +235,13 @@ openSync;
|
|
|
231
235
|
* @internal
|
|
232
236
|
*/
|
|
233
237
|
export function lopenSync(path, flag, mode) {
|
|
234
|
-
return file2fd(_openSync.call(this, path, flag, mode,
|
|
238
|
+
return file2fd(_openSync.call(this, path, { flag, mode, preserveSymlinks: true }));
|
|
235
239
|
}
|
|
236
|
-
function _readFileSync(fname, flag,
|
|
240
|
+
function _readFileSync(fname, flag, preserveSymlinks) {
|
|
237
241
|
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
238
242
|
try {
|
|
239
243
|
// Get file.
|
|
240
|
-
const file = __addDisposableResource(env_2, _openSync.call(this, fname, flag, 0o644,
|
|
244
|
+
const file = __addDisposableResource(env_2, _openSync.call(this, fname, { flag, mode: 0o644, preserveSymlinks }), false);
|
|
241
245
|
const stat = file.statSync();
|
|
242
246
|
// Allocate buffer.
|
|
243
247
|
const data = new Uint8Array(stat.size);
|
|
@@ -258,7 +262,7 @@ export function readFileSync(path, _options = {}) {
|
|
|
258
262
|
if (!isReadable(flag)) {
|
|
259
263
|
throw new ErrnoError(Errno.EINVAL, 'Flag passed to readFile must allow for reading.');
|
|
260
264
|
}
|
|
261
|
-
const data = Buffer.from(_readFileSync.call(this, typeof path == 'number' ? fd2file(path).path : path.toString(), options.flag,
|
|
265
|
+
const data = Buffer.from(_readFileSync.call(this, typeof path == 'number' ? fd2file(path).path : path.toString(), options.flag, false));
|
|
262
266
|
return options.encoding ? data.toString(options.encoding) : data;
|
|
263
267
|
}
|
|
264
268
|
readFileSync;
|
|
@@ -277,7 +281,11 @@ export function writeFileSync(path, data, _options = {}) {
|
|
|
277
281
|
if (!encodedData) {
|
|
278
282
|
throw new ErrnoError(Errno.EINVAL, 'Data not specified');
|
|
279
283
|
}
|
|
280
|
-
const file = __addDisposableResource(env_3, _openSync.call(this, typeof path == 'number' ? fd2file(path).path : path.toString(),
|
|
284
|
+
const file = __addDisposableResource(env_3, _openSync.call(this, typeof path == 'number' ? fd2file(path).path : path.toString(), {
|
|
285
|
+
flag,
|
|
286
|
+
mode: options.mode,
|
|
287
|
+
preserveSymlinks: true,
|
|
288
|
+
}), false);
|
|
281
289
|
file.writeSync(encodedData, 0, encodedData.byteLength, 0);
|
|
282
290
|
emitChange('change', path.toString());
|
|
283
291
|
}
|
|
@@ -308,7 +316,11 @@ export function appendFileSync(filename, data, _options = {}) {
|
|
|
308
316
|
throw new ErrnoError(Errno.EINVAL, 'Encoding not specified');
|
|
309
317
|
}
|
|
310
318
|
const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
311
|
-
const file = __addDisposableResource(env_4, _openSync.call(this, typeof filename == 'number' ? fd2file(filename).path : filename.toString(),
|
|
319
|
+
const file = __addDisposableResource(env_4, _openSync.call(this, typeof filename == 'number' ? fd2file(filename).path : filename.toString(), {
|
|
320
|
+
flag,
|
|
321
|
+
mode: options.mode,
|
|
322
|
+
preserveSymlinks: true,
|
|
323
|
+
}), false);
|
|
312
324
|
file.writeSync(encodedData, 0, encodedData.byteLength);
|
|
313
325
|
}
|
|
314
326
|
catch (e_4) {
|
|
@@ -560,12 +572,12 @@ export function symlinkSync(target, path, type = 'file') {
|
|
|
560
572
|
throw ErrnoError.With('EEXIST', path.toString(), 'symlink');
|
|
561
573
|
}
|
|
562
574
|
writeFileSync.call(this, path, target.toString());
|
|
563
|
-
const file = _openSync.call(this, path, 'r+', 0o644,
|
|
575
|
+
const file = _openSync.call(this, path, { flag: 'r+', mode: 0o644, preserveSymlinks: true });
|
|
564
576
|
file.chmodSync(constants.S_IFLNK);
|
|
565
577
|
}
|
|
566
578
|
symlinkSync;
|
|
567
579
|
export function readlinkSync(path, options) {
|
|
568
|
-
const value = Buffer.from(_readFileSync.call(this, path.toString(), 'r',
|
|
580
|
+
const value = Buffer.from(_readFileSync.call(this, path.toString(), 'r', true));
|
|
569
581
|
const encoding = typeof options == 'object' ? options === null || options === void 0 ? void 0 : options.encoding : options;
|
|
570
582
|
if (encoding == 'buffer') {
|
|
571
583
|
return value;
|
package/dist/vfs/types.d.ts
CHANGED
|
@@ -11,6 +11,21 @@ export interface InternalOptions {
|
|
|
11
11
|
*/
|
|
12
12
|
_isIndirect?: boolean;
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* @internal @hidden Used for the internal `_open` functions
|
|
16
|
+
*/
|
|
17
|
+
export interface OpenOptions {
|
|
18
|
+
flag: fs.OpenMode;
|
|
19
|
+
mode?: fs.Mode | null;
|
|
20
|
+
/**
|
|
21
|
+
* If true, do not resolve symlinks
|
|
22
|
+
*/
|
|
23
|
+
preserveSymlinks?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* If true, allows opening directories
|
|
26
|
+
*/
|
|
27
|
+
allowDirectory?: boolean;
|
|
28
|
+
}
|
|
14
29
|
export interface ReaddirOptions extends InternalOptions {
|
|
15
30
|
withFileTypes?: boolean;
|
|
16
31
|
recursive?: boolean;
|
package/eslint.shared.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenfs/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "A filesystem, anywhere",
|
|
5
5
|
"funding": {
|
|
6
6
|
"type": "individual",
|
|
@@ -57,8 +57,7 @@
|
|
|
57
57
|
"format": "prettier --write .",
|
|
58
58
|
"format:check": "prettier --check .",
|
|
59
59
|
"lint": "eslint src tests",
|
|
60
|
-
"test": "
|
|
61
|
-
"pretest": "npm run build",
|
|
60
|
+
"test": "npx zenfs-test --clean && npx zenfs-test -abcfp && tests/fetch/run.sh && npx zenfs-test --report",
|
|
62
61
|
"build": "tsc -p tsconfig.json",
|
|
63
62
|
"build:docs": "typedoc",
|
|
64
63
|
"dev": "npm run build -- --watch",
|
package/readme.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ZenFS
|
|
2
2
|
|
|
3
|
-
ZenFS is a
|
|
3
|
+
ZenFS is a cross-platform library that emulates the [NodeJS filesystem API](http://nodejs.org/api/fs.html). It works using a system of backends, which are used by ZenFS to store and retrieve data. ZenFS can also integrate with other tools.
|
|
4
4
|
|
|
5
5
|
## Backends
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ ZenFS is modular and extensible. The core includes some built-in backends:
|
|
|
8
8
|
|
|
9
9
|
- `InMemory`: Stores files in-memory. This is cleared when the runtime ends (e.g. a user navigating away from a web page or a Node process exiting)
|
|
10
10
|
- `Overlay`: Use a read-only file system as read-write by overlaying a writable file system on top of it. ([copy-on-write](https://en.wikipedia.org/wiki/Copy-on-write))
|
|
11
|
-
- `Fetch`: Downloads files over HTTP with the `fetch` API
|
|
11
|
+
- `Fetch`: Downloads files over HTTP with the `fetch` API
|
|
12
12
|
- `Port`: Interacts with a remote over a `MessagePort`-like interface (e.g. a worker)
|
|
13
13
|
- `Passthrough`: Use an existing `node:fs` interface with ZenFS
|
|
14
14
|
|
package/scripts/test.js
CHANGED
|
@@ -16,7 +16,10 @@ const { values: options, positionals } = parseArgs({
|
|
|
16
16
|
build: { short: 'b', type: 'boolean', default: false },
|
|
17
17
|
common: { short: 'c', type: 'boolean', default: false },
|
|
18
18
|
coverage: { type: 'string', default: 'tests/.coverage' },
|
|
19
|
+
preserve: { short: 'p', type: 'boolean' },
|
|
19
20
|
'exit-on-fail': { short: 'e', type: 'boolean' },
|
|
21
|
+
report: { type: 'boolean' },
|
|
22
|
+
clean: { type: 'boolean' },
|
|
20
23
|
},
|
|
21
24
|
allowPositionals: true,
|
|
22
25
|
});
|
|
@@ -36,7 +39,12 @@ Options:
|
|
|
36
39
|
-q, --quiet Don't output normal messages
|
|
37
40
|
-t, --test <glob> Which FS test suite(s) to run
|
|
38
41
|
-f, --force Whether to use --test-force-exit
|
|
39
|
-
|
|
42
|
+
|
|
43
|
+
Coverage:
|
|
44
|
+
--coverage <dir> Override the default coverage data directory
|
|
45
|
+
-p,--preserve Do not delete or report coverage data
|
|
46
|
+
--report ONLY report coverage
|
|
47
|
+
--clean ONLY clean up coverage directory`);
|
|
40
48
|
process.exit();
|
|
41
49
|
}
|
|
42
50
|
|
|
@@ -45,6 +53,19 @@ if (options.quiet && options.verbose) {
|
|
|
45
53
|
process.exit(1);
|
|
46
54
|
}
|
|
47
55
|
|
|
56
|
+
process.env.NODE_V8_COVERAGE = options.coverage;
|
|
57
|
+
|
|
58
|
+
if (options.clean) {
|
|
59
|
+
rmSync(options.coverage, { recursive: true, force: true });
|
|
60
|
+
process.exit();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (options.report) {
|
|
64
|
+
execSync('npx c8 report --reporter=text', { stdio: 'inherit' });
|
|
65
|
+
rmSync(options.coverage, { recursive: true, force: true });
|
|
66
|
+
process.exit();
|
|
67
|
+
}
|
|
68
|
+
|
|
48
69
|
options.verbose && options.force && console.debug('Forcing tests to exit (--test-force-exit)');
|
|
49
70
|
|
|
50
71
|
if (options.build) {
|
|
@@ -73,19 +94,56 @@ if (options.auto) {
|
|
|
73
94
|
!options.quiet && console.log(`Auto-detected ${sum} test setup files`);
|
|
74
95
|
}
|
|
75
96
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Colorizes some text
|
|
99
|
+
* @param {string} text Text to color
|
|
100
|
+
* @param {string | number} code ANSI escape code
|
|
101
|
+
* @returns
|
|
102
|
+
*/
|
|
103
|
+
function color(text, code) {
|
|
104
|
+
return `\x1b[${code}m${text}\x1b[0m`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function status(name) {
|
|
108
|
+
const start = performance.now();
|
|
109
|
+
|
|
110
|
+
const time = () => {
|
|
111
|
+
let delta = Math.round(performance.now() - start),
|
|
112
|
+
unit = 'ms';
|
|
113
|
+
|
|
114
|
+
if (delta > 5000) {
|
|
115
|
+
delta /= 1000;
|
|
116
|
+
unit = 's';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return color(`(${delta} ${unit})`, '2;37');
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
pass() {
|
|
124
|
+
if (!options.quiet) console.log(`${color('passed', 32)}: ${name} ${time()}`);
|
|
125
|
+
},
|
|
126
|
+
fail() {
|
|
127
|
+
console.error(`${color('failed', '1;31')}: ${name} ${time()}`);
|
|
128
|
+
process.exitCode = 1;
|
|
129
|
+
if (options['exit-on-fail']) process.exit();
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!options.preserve) rmSync(options.coverage, { force: true, recursive: true });
|
|
135
|
+
mkdirSync(options.coverage, { recursive: true });
|
|
79
136
|
|
|
80
137
|
if (options.common) {
|
|
81
138
|
!options.quiet && console.log('Running common tests...');
|
|
139
|
+
const { pass, fail } = status('Common tests');
|
|
82
140
|
try {
|
|
83
141
|
execSync("tsx --test --experimental-test-coverage 'tests/*.test.ts' 'tests/**/!(fs)/*.test.ts'", {
|
|
84
142
|
stdio: ['ignore', options.verbose ? 'inherit' : 'ignore', 'inherit'],
|
|
85
143
|
});
|
|
144
|
+
pass();
|
|
86
145
|
} catch {
|
|
87
|
-
|
|
88
|
-
if (options['exit-on-fail']) process.exit(1);
|
|
146
|
+
fail();
|
|
89
147
|
}
|
|
90
148
|
}
|
|
91
149
|
|
|
@@ -97,18 +155,22 @@ for (const setupFile of positionals) {
|
|
|
97
155
|
continue;
|
|
98
156
|
}
|
|
99
157
|
|
|
100
|
-
!options.quiet && console.
|
|
158
|
+
!options.quiet && console.log('Running tests:', setupFile);
|
|
101
159
|
process.env.SETUP = setupFile;
|
|
102
160
|
|
|
161
|
+
const { pass, fail } = status(setupFile);
|
|
162
|
+
|
|
103
163
|
try {
|
|
104
164
|
execSync(['tsx --test --experimental-test-coverage', options.force ? '--test-force-exit' : '', testsGlob, process.env.CMD].join(' '), {
|
|
105
165
|
stdio: ['ignore', options.verbose ? 'inherit' : 'ignore', 'inherit'],
|
|
106
166
|
});
|
|
167
|
+
pass();
|
|
107
168
|
} catch {
|
|
108
|
-
|
|
109
|
-
if (options['exit-on-fail']) process.exit(1);
|
|
169
|
+
fail();
|
|
110
170
|
}
|
|
111
171
|
}
|
|
112
172
|
|
|
113
|
-
|
|
114
|
-
|
|
173
|
+
if (!options.preserve) {
|
|
174
|
+
execSync('npx c8 report --reporter=text', { stdio: 'inherit' });
|
|
175
|
+
rmSync(options.coverage, { recursive: true });
|
|
176
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/bash
|
|
2
|
+
|
|
3
|
+
# Credit: Dave Dopson, https://stackoverflow.com/a/246128/17637456
|
|
4
|
+
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
|
5
|
+
|
|
6
|
+
npx tsx $SCRIPT_DIR/server.ts &
|
|
7
|
+
PID=$!
|
|
8
|
+
|
|
9
|
+
echo "Waiting for server to start..."
|
|
10
|
+
until nc -z localhost 26514; do
|
|
11
|
+
sleep 0.5
|
|
12
|
+
done
|
|
13
|
+
|
|
14
|
+
npx zenfs-test $SCRIPT_DIR/setup.ts --preserve --force $@
|
|
15
|
+
|
|
16
|
+
kill $PID
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { createServer } from 'node:http';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { data, tmp } from '../setup.js';
|
|
6
|
+
|
|
7
|
+
// If you change the port please update the setup file as well
|
|
8
|
+
const port = 26514;
|
|
9
|
+
|
|
10
|
+
const statusCodes = {
|
|
11
|
+
ENOENT: 404,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
execSync(`npm exec make-index -- ${data} --output ${tmp}/index.json --quiet`, { stdio: 'inherit' });
|
|
16
|
+
} catch (e: any) {
|
|
17
|
+
if (e.signal == 'SIGINT') {
|
|
18
|
+
console.log('Aborted whilst creating index.');
|
|
19
|
+
process.exit(0);
|
|
20
|
+
} else {
|
|
21
|
+
console.error('Index creation failed: ' + e.message);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const server = createServer((request, response) => {
|
|
27
|
+
const { url = '' } = request;
|
|
28
|
+
|
|
29
|
+
if (url == '/.ping') {
|
|
30
|
+
response.statusCode = 200;
|
|
31
|
+
response.end('pong');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const path = url == '/.index.json' ? join(tmp, 'index.json') : join(data, url.slice(1) || '');
|
|
36
|
+
try {
|
|
37
|
+
response.statusCode = 200;
|
|
38
|
+
response.end(readFileSync(path));
|
|
39
|
+
} catch (e: any) {
|
|
40
|
+
response.statusCode = statusCodes[e.code as keyof typeof statusCodes] || 400;
|
|
41
|
+
response.end();
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
server.listen(port);
|
|
46
|
+
|
|
47
|
+
process.on('beforeExit', () => {
|
|
48
|
+
server.close().unref();
|
|
49
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { configureSingle, Fetch, InMemory, Overlay, resolveMountConfig } from '../../dist/index.js';
|
|
2
|
+
|
|
3
|
+
const baseUrl = 'http://localhost:26514';
|
|
4
|
+
|
|
5
|
+
await configureSingle({
|
|
6
|
+
backend: Overlay,
|
|
7
|
+
readable: await resolveMountConfig({
|
|
8
|
+
backend: Fetch,
|
|
9
|
+
baseUrl,
|
|
10
|
+
index: baseUrl + '/.index.json',
|
|
11
|
+
}),
|
|
12
|
+
writable: InMemory.create({ name: 'cow' }),
|
|
13
|
+
});
|
package/tests/fs/read.test.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
import { suite, test } from 'node:test';
|
|
3
3
|
import { fs } from '../common.js';
|
|
4
|
+
import { Buffer } from 'buffer';
|
|
4
5
|
|
|
5
|
-
const filepath
|
|
6
|
-
const expected
|
|
6
|
+
const filepath = 'x.txt';
|
|
7
|
+
const expected = 'xyz\n';
|
|
7
8
|
|
|
8
9
|
suite('read', () => {
|
|
9
10
|
test('read file asynchronously', async () => {
|
|
10
11
|
const handle = await fs.promises.open(filepath, 'r');
|
|
11
12
|
const { bytesRead, buffer } = await handle.read(Buffer.alloc(expected.length), 0, expected.length, 0);
|
|
12
|
-
|
|
13
13
|
assert.equal(bytesRead, expected.length);
|
|
14
|
-
assert(buffer.toString()
|
|
14
|
+
assert.equal(buffer.toString(), expected);
|
|
15
15
|
});
|
|
16
16
|
|
|
17
17
|
test('read file synchronously', () => {
|
|
@@ -20,19 +20,19 @@ suite('read', () => {
|
|
|
20
20
|
const bytesRead = fs.readSync(fd, buffer, 0, expected.length, 0);
|
|
21
21
|
|
|
22
22
|
assert.equal(bytesRead, expected.length);
|
|
23
|
-
assert(buffer.toString()
|
|
23
|
+
assert.equal(buffer.toString(), expected);
|
|
24
24
|
});
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
suite('read binary', () => {
|
|
28
28
|
test('Read a file and check its binary bytes (asynchronous)', async () => {
|
|
29
29
|
const buff = await fs.promises.readFile('elipses.txt');
|
|
30
|
-
assert((
|
|
30
|
+
assert.strictEqual((buff[1] << 8) | buff[0], 32994);
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
test('Read a file and check its binary bytes (synchronous)', () => {
|
|
34
34
|
const buff = fs.readFileSync('elipses.txt');
|
|
35
|
-
assert((
|
|
35
|
+
assert.strictEqual((buff[1] << 8) | buff[0], 32994);
|
|
36
36
|
});
|
|
37
37
|
});
|
|
38
38
|
|
|
@@ -45,14 +45,14 @@ suite('read buffer', () => {
|
|
|
45
45
|
const { bytesRead } = await handle.read(bufferAsync, 0, expected.length, 0);
|
|
46
46
|
|
|
47
47
|
assert.strictEqual(bytesRead, expected.length);
|
|
48
|
-
assert(bufferAsync.toString()
|
|
48
|
+
assert.strictEqual(bufferAsync.toString(), expected);
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
test('read file synchronously', () => {
|
|
52
52
|
const fd = fs.openSync(filepath, 'r');
|
|
53
53
|
const bytesRead = fs.readSync(fd, bufferSync, 0, expected.length, 0);
|
|
54
54
|
|
|
55
|
-
assert(bufferSync.toString()
|
|
55
|
+
assert.strictEqual(bufferSync.toString(), expected);
|
|
56
56
|
assert.strictEqual(bytesRead, expected.length);
|
|
57
57
|
});
|
|
58
58
|
|
|
@@ -61,7 +61,7 @@ suite('read buffer', () => {
|
|
|
61
61
|
const buffer = Buffer.alloc(expected.length + 10);
|
|
62
62
|
const bytesRead = fs.readSync(fd, buffer, 10, expected.length, 0);
|
|
63
63
|
|
|
64
|
-
assert(buffer.subarray(10, buffer.length).toString()
|
|
64
|
+
assert.strictEqual(buffer.subarray(10, buffer.length).toString(), expected);
|
|
65
65
|
assert.strictEqual(bytesRead, expected.length);
|
|
66
66
|
});
|
|
67
67
|
});
|
package/tests/fs/times.test.ts
CHANGED
|
@@ -69,7 +69,7 @@ suite('times', () => {
|
|
|
69
69
|
test('read changes atime', async () => {
|
|
70
70
|
const before = fs.statSync(path).atimeMs;
|
|
71
71
|
fs.readFileSync(path);
|
|
72
|
-
await wait(
|
|
72
|
+
await wait(25);
|
|
73
73
|
const after = fs.statSync(path).atimeMs;
|
|
74
74
|
assert(before < after);
|
|
75
75
|
});
|
|
@@ -77,7 +77,7 @@ suite('times', () => {
|
|
|
77
77
|
test('write changes mtime', async () => {
|
|
78
78
|
const before = fs.statSync(path).mtimeMs;
|
|
79
79
|
fs.writeFileSync(path, 'cool');
|
|
80
|
-
await wait(
|
|
80
|
+
await wait(25);
|
|
81
81
|
const after = fs.statSync(path).mtimeMs;
|
|
82
82
|
assert(before < after);
|
|
83
83
|
});
|
package/tests/fs/write.test.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
import { suite, test } from 'node:test';
|
|
3
3
|
import { fs } from '../common.js';
|
|
4
|
-
|
|
4
|
+
const fn = 'write.txt';
|
|
5
5
|
suite('write', () => {
|
|
6
6
|
test('write file with specified content', async () => {
|
|
7
|
-
const fn = 'write.txt';
|
|
8
7
|
const expected = 'ümlaut.';
|
|
9
8
|
|
|
10
9
|
const handle = await fs.promises.open(fn, 'w', 0o644);
|
|
@@ -20,10 +19,9 @@ suite('write', () => {
|
|
|
20
19
|
});
|
|
21
20
|
|
|
22
21
|
test('write a buffer to a file', async () => {
|
|
23
|
-
const filename = 'write.txt';
|
|
24
22
|
const expected = Buffer.from('hello');
|
|
25
23
|
|
|
26
|
-
const handle = await fs.promises.open(
|
|
24
|
+
const handle = await fs.promises.open(fn, 'w', 0o644);
|
|
27
25
|
|
|
28
26
|
const written = await handle.write(expected, 0, expected.length, null);
|
|
29
27
|
|
|
@@ -31,15 +29,12 @@ suite('write', () => {
|
|
|
31
29
|
|
|
32
30
|
await handle.close();
|
|
33
31
|
|
|
34
|
-
assert((await fs.promises.readFile(
|
|
32
|
+
assert((await fs.promises.readFile(fn)).equals(expected));
|
|
35
33
|
|
|
36
|
-
await fs.promises.unlink(
|
|
34
|
+
await fs.promises.unlink(fn);
|
|
37
35
|
});
|
|
38
|
-
});
|
|
39
36
|
|
|
40
|
-
|
|
41
|
-
test('write file with specified content', () => {
|
|
42
|
-
const fn = 'write.txt';
|
|
37
|
+
test('writeSync file with specified content', () => {
|
|
43
38
|
const fd = fs.openSync(fn, 'w');
|
|
44
39
|
|
|
45
40
|
let written = fs.writeSync(fd, '');
|
|
@@ -53,6 +48,6 @@ suite('writeSync', () => {
|
|
|
53
48
|
|
|
54
49
|
fs.closeSync(fd);
|
|
55
50
|
|
|
56
|
-
assert(fs.readFileSync(fn, 'utf8')
|
|
51
|
+
assert.strictEqual(fs.readFileSync(fn, 'utf8'), 'foobár');
|
|
57
52
|
});
|
|
58
53
|
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path/posix';
|
|
3
|
+
import { configureSingle, InMemory, InMemoryStore, mounts, Overlay, Readonly, resolveMountConfig, StoreFS } from '../../dist/index.js';
|
|
4
|
+
import { S_IFDIR } from '../../dist/vfs/constants.js';
|
|
5
|
+
import { copy, data } from '../setup.js';
|
|
6
|
+
|
|
7
|
+
copy(data);
|
|
8
|
+
|
|
9
|
+
const index = (mounts.get('/') as StoreFS).createIndexSync();
|
|
10
|
+
|
|
11
|
+
class MockFS extends Readonly(StoreFS) {
|
|
12
|
+
constructor() {
|
|
13
|
+
super(new InMemoryStore());
|
|
14
|
+
this.loadIndexSync(index);
|
|
15
|
+
|
|
16
|
+
using tx = this.store.transaction();
|
|
17
|
+
|
|
18
|
+
for (const [path, node] of index) {
|
|
19
|
+
if (node.mode & S_IFDIR) continue;
|
|
20
|
+
|
|
21
|
+
const content = readFileSync(join(data, path));
|
|
22
|
+
|
|
23
|
+
tx.setSync(node.data, content);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
tx.commitSync();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const readable = new MockFS();
|
|
31
|
+
|
|
32
|
+
await readable.ready();
|
|
33
|
+
|
|
34
|
+
await configureSingle({
|
|
35
|
+
backend: Overlay,
|
|
36
|
+
readable,
|
|
37
|
+
writable: await resolveMountConfig({ backend: InMemory, name: 'cow' }),
|
|
38
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { MessageChannel } from 'node:worker_threads';
|
|
2
|
+
import { InMemory, Port, configureSingle, fs, resolveMountConfig, resolveRemoteMount } from '../../dist/index.js';
|
|
3
|
+
import { copy, data } from '../setup.js';
|
|
4
|
+
|
|
5
|
+
const { port1: localPort, port2: remotePort } = new MessageChannel();
|
|
6
|
+
|
|
7
|
+
fs.umount('/');
|
|
8
|
+
const tmpfs = await resolveMountConfig({ backend: InMemory, name: 'tmp' });
|
|
9
|
+
|
|
10
|
+
fs.mount('/', tmpfs);
|
|
11
|
+
copy(data, fs);
|
|
12
|
+
|
|
13
|
+
await resolveRemoteMount(remotePort, tmpfs);
|
|
14
|
+
|
|
15
|
+
await configureSingle({ backend: Port, port: localPort });
|