secure-exec 0.0.1 → 0.1.0-rc.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/LICENSE +191 -0
- package/README.md +7 -0
- package/dist/bridge/active-handles.d.ts +21 -0
- package/dist/bridge/active-handles.js +60 -0
- package/dist/bridge/child-process.d.ts +87 -0
- package/dist/bridge/child-process.js +523 -0
- package/dist/bridge/fs.d.ts +227 -0
- package/dist/bridge/fs.js +1572 -0
- package/dist/bridge/index.d.ts +10 -0
- package/dist/bridge/index.js +41 -0
- package/dist/bridge/module.d.ts +73 -0
- package/dist/bridge/module.js +329 -0
- package/dist/bridge/network.d.ts +208 -0
- package/dist/bridge/network.js +1117 -0
- package/dist/bridge/os.d.ts +13 -0
- package/dist/bridge/os.js +257 -0
- package/dist/bridge/polyfills.d.ts +2 -0
- package/dist/bridge/polyfills.js +12 -0
- package/dist/bridge/process.d.ts +64 -0
- package/dist/bridge/process.js +916 -0
- package/dist/bridge-loader.d.ts +1 -0
- package/dist/bridge-loader.js +2 -0
- package/dist/bridge-setup.d.ts +1 -0
- package/dist/bridge-setup.js +2 -0
- package/dist/bridge.js +10586 -0
- package/dist/browser/driver.d.ts +42 -0
- package/dist/browser/driver.js +263 -0
- package/dist/browser/index.d.ts +5 -0
- package/dist/browser/index.js +4 -0
- package/dist/browser/worker.d.ts +1 -0
- package/dist/browser/worker.js +3 -0
- package/dist/browser-runtime.d.ts +6 -0
- package/dist/browser-runtime.js +4 -0
- package/dist/esm-compiler.d.ts +1 -0
- package/dist/esm-compiler.js +2 -0
- package/dist/execution.d.ts +1 -0
- package/dist/execution.js +2 -0
- package/dist/fs-helpers.d.ts +2 -0
- package/dist/fs-helpers.js +1 -0
- package/dist/generated/isolate-runtime.d.ts +19 -0
- package/dist/generated/isolate-runtime.js +21 -0
- package/dist/generated/polyfills.d.ts +82 -0
- package/dist/generated/polyfills.js +82 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +11 -0
- package/dist/isolate-runtime/apply-custom-global-policy.js +54 -0
- package/dist/isolate-runtime/apply-timing-mitigation-freeze.js +44 -0
- package/dist/isolate-runtime/apply-timing-mitigation-off.js +14 -0
- package/dist/isolate-runtime/bridge-attach.js +29 -0
- package/dist/isolate-runtime/bridge-initial-globals.js +78 -0
- package/dist/isolate-runtime/eval-script-result.js +8 -0
- package/dist/isolate-runtime/global-exposure-helpers.js +36 -0
- package/dist/isolate-runtime/init-commonjs-module-globals.js +28 -0
- package/dist/isolate-runtime/override-process-cwd.js +8 -0
- package/dist/isolate-runtime/override-process-env.js +8 -0
- package/dist/isolate-runtime/require-setup.js +606 -0
- package/dist/isolate-runtime/set-commonjs-file-globals.js +36 -0
- package/dist/isolate-runtime/set-stdin-data.js +10 -0
- package/dist/isolate-runtime/setup-dynamic-import.js +64 -0
- package/dist/isolate-runtime/setup-fs-facade.js +40 -0
- package/dist/isolate.d.ts +1 -0
- package/dist/isolate.js +2 -0
- package/dist/module-resolver.d.ts +1 -0
- package/dist/module-resolver.js +2 -0
- package/dist/node/bridge-setup.d.ts +1 -0
- package/dist/node/bridge-setup.js +2 -0
- package/dist/node/driver.d.ts +2 -0
- package/dist/node/driver.js +2 -0
- package/dist/node/esm-compiler.d.ts +1 -0
- package/dist/node/esm-compiler.js +2 -0
- package/dist/node/execution-driver.d.ts +2 -0
- package/dist/node/execution-driver.js +2 -0
- package/dist/node/execution-lifecycle.d.ts +1 -0
- package/dist/node/execution-lifecycle.js +2 -0
- package/dist/node/isolate-bootstrap.d.ts +2 -0
- package/dist/node/isolate-bootstrap.js +1 -0
- package/dist/node/module-access.d.ts +2 -0
- package/dist/node/module-access.js +2 -0
- package/dist/node/module-resolver.d.ts +1 -0
- package/dist/node/module-resolver.js +2 -0
- package/dist/package-bundler.d.ts +2 -0
- package/dist/package-bundler.js +1 -0
- package/dist/polyfills.d.ts +1 -0
- package/dist/polyfills.js +2 -0
- package/dist/python-runtime.d.ts +2 -0
- package/dist/python-runtime.js +2 -0
- package/dist/runtime-driver.d.ts +1 -0
- package/dist/runtime-driver.js +1 -0
- package/dist/runtime.d.ts +2 -0
- package/dist/runtime.js +2 -0
- package/dist/shared/api-types.d.ts +1 -0
- package/dist/shared/api-types.js +1 -0
- package/dist/shared/bridge-contract.d.ts +2 -0
- package/dist/shared/bridge-contract.js +1 -0
- package/dist/shared/console-formatter.d.ts +2 -0
- package/dist/shared/console-formatter.js +1 -0
- package/dist/shared/errors.d.ts +2 -0
- package/dist/shared/errors.js +1 -0
- package/dist/shared/esm-utils.d.ts +1 -0
- package/dist/shared/esm-utils.js +2 -0
- package/dist/shared/global-exposure.d.ts +2 -0
- package/dist/shared/global-exposure.js +1 -0
- package/dist/shared/in-memory-fs.d.ts +1 -0
- package/dist/shared/in-memory-fs.js +2 -0
- package/dist/shared/permissions.d.ts +1 -0
- package/dist/shared/permissions.js +2 -0
- package/dist/shared/require-setup.d.ts +1 -0
- package/dist/shared/require-setup.js +2 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +1 -0
- package/package.json +51 -4
- package/index.js +0 -1
|
@@ -0,0 +1,1572 @@
|
|
|
1
|
+
// fs polyfill module for isolated-vm
|
|
2
|
+
// This module runs inside the isolate and provides Node.js fs API compatibility
|
|
3
|
+
// It communicates with the host via the _fs Reference object
|
|
4
|
+
import { Buffer } from "buffer";
|
|
5
|
+
// File descriptor table
|
|
6
|
+
const fdTable = new Map();
|
|
7
|
+
let nextFd = 3;
|
|
8
|
+
const O_RDONLY = 0;
|
|
9
|
+
const O_WRONLY = 1;
|
|
10
|
+
const O_RDWR = 2;
|
|
11
|
+
const O_ACCMODE = 3;
|
|
12
|
+
const O_CREAT = 64;
|
|
13
|
+
const O_EXCL = 128;
|
|
14
|
+
const O_TRUNC = 512;
|
|
15
|
+
const O_APPEND = 1024;
|
|
16
|
+
// Stats class
|
|
17
|
+
class Stats {
|
|
18
|
+
dev;
|
|
19
|
+
ino;
|
|
20
|
+
mode;
|
|
21
|
+
nlink;
|
|
22
|
+
uid;
|
|
23
|
+
gid;
|
|
24
|
+
rdev;
|
|
25
|
+
size;
|
|
26
|
+
blksize;
|
|
27
|
+
blocks;
|
|
28
|
+
atimeMs;
|
|
29
|
+
mtimeMs;
|
|
30
|
+
ctimeMs;
|
|
31
|
+
birthtimeMs;
|
|
32
|
+
atime;
|
|
33
|
+
mtime;
|
|
34
|
+
ctime;
|
|
35
|
+
birthtime;
|
|
36
|
+
constructor(init) {
|
|
37
|
+
this.dev = init.dev ?? 0;
|
|
38
|
+
this.ino = init.ino ?? 0;
|
|
39
|
+
this.mode = init.mode;
|
|
40
|
+
this.nlink = init.nlink ?? 1;
|
|
41
|
+
this.uid = init.uid ?? 0;
|
|
42
|
+
this.gid = init.gid ?? 0;
|
|
43
|
+
this.rdev = init.rdev ?? 0;
|
|
44
|
+
this.size = init.size;
|
|
45
|
+
this.blksize = init.blksize ?? 4096;
|
|
46
|
+
this.blocks = init.blocks ?? Math.ceil(init.size / 512);
|
|
47
|
+
this.atimeMs = init.atimeMs ?? Date.now();
|
|
48
|
+
this.mtimeMs = init.mtimeMs ?? Date.now();
|
|
49
|
+
this.ctimeMs = init.ctimeMs ?? Date.now();
|
|
50
|
+
this.birthtimeMs = init.birthtimeMs ?? Date.now();
|
|
51
|
+
this.atime = new Date(this.atimeMs);
|
|
52
|
+
this.mtime = new Date(this.mtimeMs);
|
|
53
|
+
this.ctime = new Date(this.ctimeMs);
|
|
54
|
+
this.birthtime = new Date(this.birthtimeMs);
|
|
55
|
+
}
|
|
56
|
+
isFile() {
|
|
57
|
+
return (this.mode & 61440) === 32768;
|
|
58
|
+
}
|
|
59
|
+
isDirectory() {
|
|
60
|
+
return (this.mode & 61440) === 16384;
|
|
61
|
+
}
|
|
62
|
+
isSymbolicLink() {
|
|
63
|
+
return (this.mode & 61440) === 40960;
|
|
64
|
+
}
|
|
65
|
+
isBlockDevice() {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
isCharacterDevice() {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
isFIFO() {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
isSocket() {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Dirent class for readdir with withFileTypes
|
|
79
|
+
class Dirent {
|
|
80
|
+
name;
|
|
81
|
+
parentPath;
|
|
82
|
+
path; // Deprecated alias for parentPath
|
|
83
|
+
_isDir;
|
|
84
|
+
constructor(name, isDir, parentPath = "") {
|
|
85
|
+
this.name = name;
|
|
86
|
+
this._isDir = isDir;
|
|
87
|
+
this.parentPath = parentPath;
|
|
88
|
+
this.path = parentPath;
|
|
89
|
+
}
|
|
90
|
+
isFile() {
|
|
91
|
+
return !this._isDir;
|
|
92
|
+
}
|
|
93
|
+
isDirectory() {
|
|
94
|
+
return this._isDir;
|
|
95
|
+
}
|
|
96
|
+
isSymbolicLink() {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
isBlockDevice() {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
isCharacterDevice() {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
isFIFO() {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
isSocket() {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// ReadStream class for createReadStream
|
|
113
|
+
// Provides a proper readable stream implementation that works with stream.pipeline
|
|
114
|
+
class ReadStream {
|
|
115
|
+
_options;
|
|
116
|
+
// ReadStream-specific properties
|
|
117
|
+
bytesRead = 0;
|
|
118
|
+
path;
|
|
119
|
+
pending = true;
|
|
120
|
+
// Readable stream properties
|
|
121
|
+
readable = true;
|
|
122
|
+
readableAborted = false;
|
|
123
|
+
readableDidRead = false;
|
|
124
|
+
readableEncoding = null;
|
|
125
|
+
readableEnded = false;
|
|
126
|
+
readableFlowing = null;
|
|
127
|
+
readableHighWaterMark = 65536;
|
|
128
|
+
readableLength = 0;
|
|
129
|
+
readableObjectMode = false;
|
|
130
|
+
destroyed = false;
|
|
131
|
+
closed = false;
|
|
132
|
+
errored = null;
|
|
133
|
+
// Internal state
|
|
134
|
+
_content = null;
|
|
135
|
+
_position = 0;
|
|
136
|
+
_listeners = new Map();
|
|
137
|
+
_started = false;
|
|
138
|
+
constructor(filePath, _options) {
|
|
139
|
+
this._options = _options;
|
|
140
|
+
this.path = filePath;
|
|
141
|
+
if (_options?.encoding) {
|
|
142
|
+
this.readableEncoding = _options.encoding;
|
|
143
|
+
}
|
|
144
|
+
if (_options?.highWaterMark) {
|
|
145
|
+
this.readableHighWaterMark = _options.highWaterMark;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
_loadContent() {
|
|
149
|
+
if (this._content === null) {
|
|
150
|
+
const pathStr = typeof this.path === 'string' ? this.path : this.path.toString();
|
|
151
|
+
// readFileSync already normalizes the path
|
|
152
|
+
this._content = fs.readFileSync(pathStr);
|
|
153
|
+
this.pending = false;
|
|
154
|
+
}
|
|
155
|
+
return this._content;
|
|
156
|
+
}
|
|
157
|
+
// Start reading - called when 'data' listener is added or resume() is called
|
|
158
|
+
_startReading() {
|
|
159
|
+
if (this._started || this.destroyed)
|
|
160
|
+
return;
|
|
161
|
+
this._started = true;
|
|
162
|
+
this.readableFlowing = true;
|
|
163
|
+
Promise.resolve().then(() => {
|
|
164
|
+
try {
|
|
165
|
+
const content = this._loadContent();
|
|
166
|
+
this.readableDidRead = true;
|
|
167
|
+
// Determine start/end positions
|
|
168
|
+
const start = this._options?.start ?? 0;
|
|
169
|
+
const end = this._options?.end ?? content.length;
|
|
170
|
+
const chunk = content.slice(start, end);
|
|
171
|
+
this.bytesRead = chunk.length;
|
|
172
|
+
// Emit data event
|
|
173
|
+
this.emit('data', chunk);
|
|
174
|
+
// Emit end and close
|
|
175
|
+
Promise.resolve().then(() => {
|
|
176
|
+
this.readable = false;
|
|
177
|
+
this.readableEnded = true;
|
|
178
|
+
this.emit('end');
|
|
179
|
+
Promise.resolve().then(() => {
|
|
180
|
+
this.closed = true;
|
|
181
|
+
this.emit('close');
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
this.errored = err;
|
|
187
|
+
this.emit('error', err);
|
|
188
|
+
this.destroy(err);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
// Event handling
|
|
193
|
+
on(event, listener) {
|
|
194
|
+
if (!this._listeners.has(event)) {
|
|
195
|
+
this._listeners.set(event, []);
|
|
196
|
+
}
|
|
197
|
+
this._listeners.get(event).push(listener);
|
|
198
|
+
// Start reading when 'data' listener is added (flowing mode)
|
|
199
|
+
if (event === 'data' && !this._started) {
|
|
200
|
+
this._startReading();
|
|
201
|
+
}
|
|
202
|
+
return this;
|
|
203
|
+
}
|
|
204
|
+
once(event, listener) {
|
|
205
|
+
const wrapper = (...args) => {
|
|
206
|
+
this.off(event, wrapper);
|
|
207
|
+
listener(...args);
|
|
208
|
+
};
|
|
209
|
+
wrapper._originalListener = listener;
|
|
210
|
+
return this.on(event, wrapper);
|
|
211
|
+
}
|
|
212
|
+
off(event, listener) {
|
|
213
|
+
const listeners = this._listeners.get(event);
|
|
214
|
+
if (listeners) {
|
|
215
|
+
const idx = listeners.findIndex(fn => fn === listener || fn._originalListener === listener);
|
|
216
|
+
if (idx !== -1)
|
|
217
|
+
listeners.splice(idx, 1);
|
|
218
|
+
}
|
|
219
|
+
return this;
|
|
220
|
+
}
|
|
221
|
+
removeListener(event, listener) {
|
|
222
|
+
return this.off(event, listener);
|
|
223
|
+
}
|
|
224
|
+
removeAllListeners(event) {
|
|
225
|
+
if (event) {
|
|
226
|
+
this._listeners.delete(event);
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
this._listeners.clear();
|
|
230
|
+
}
|
|
231
|
+
return this;
|
|
232
|
+
}
|
|
233
|
+
emit(event, ...args) {
|
|
234
|
+
const listeners = this._listeners.get(event);
|
|
235
|
+
if (listeners && listeners.length > 0) {
|
|
236
|
+
listeners.slice().forEach(fn => fn(...args));
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
// Readable methods
|
|
242
|
+
read(_size) {
|
|
243
|
+
if (this.readableEnded || this.destroyed)
|
|
244
|
+
return null;
|
|
245
|
+
try {
|
|
246
|
+
const content = this._loadContent();
|
|
247
|
+
const start = this._options?.start ?? 0;
|
|
248
|
+
const end = this._options?.end ?? content.length;
|
|
249
|
+
const chunk = content.slice(start, end);
|
|
250
|
+
this.bytesRead = chunk.length;
|
|
251
|
+
this.readableDidRead = true;
|
|
252
|
+
this.readable = false;
|
|
253
|
+
this.readableEnded = true;
|
|
254
|
+
// Schedule end event
|
|
255
|
+
Promise.resolve().then(() => {
|
|
256
|
+
this.emit('end');
|
|
257
|
+
Promise.resolve().then(() => {
|
|
258
|
+
this.closed = true;
|
|
259
|
+
this.emit('close');
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
return this.readableEncoding ? chunk.toString(this.readableEncoding) : chunk;
|
|
263
|
+
}
|
|
264
|
+
catch (err) {
|
|
265
|
+
this.errored = err;
|
|
266
|
+
this.emit('error', err);
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
pipe(destination, _options) {
|
|
271
|
+
const content = this._loadContent();
|
|
272
|
+
const start = this._options?.start ?? 0;
|
|
273
|
+
const end = this._options?.end ?? content.length;
|
|
274
|
+
const chunk = content.slice(start, end);
|
|
275
|
+
this.bytesRead = chunk.length;
|
|
276
|
+
this.readableDidRead = true;
|
|
277
|
+
if (typeof destination.write === 'function') {
|
|
278
|
+
destination.write(chunk);
|
|
279
|
+
}
|
|
280
|
+
if (typeof destination.end === 'function') {
|
|
281
|
+
Promise.resolve().then(() => destination.end());
|
|
282
|
+
}
|
|
283
|
+
this.readable = false;
|
|
284
|
+
this.readableEnded = true;
|
|
285
|
+
this.closed = true;
|
|
286
|
+
Promise.resolve().then(() => {
|
|
287
|
+
this.emit('end');
|
|
288
|
+
this.emit('close');
|
|
289
|
+
});
|
|
290
|
+
return destination;
|
|
291
|
+
}
|
|
292
|
+
unpipe(_destination) {
|
|
293
|
+
return this;
|
|
294
|
+
}
|
|
295
|
+
pause() {
|
|
296
|
+
this.readableFlowing = false;
|
|
297
|
+
return this;
|
|
298
|
+
}
|
|
299
|
+
resume() {
|
|
300
|
+
this.readableFlowing = true;
|
|
301
|
+
if (!this._started) {
|
|
302
|
+
this._startReading();
|
|
303
|
+
}
|
|
304
|
+
return this;
|
|
305
|
+
}
|
|
306
|
+
setEncoding(encoding) {
|
|
307
|
+
this.readableEncoding = encoding;
|
|
308
|
+
return this;
|
|
309
|
+
}
|
|
310
|
+
destroy(error) {
|
|
311
|
+
if (this.destroyed)
|
|
312
|
+
return this;
|
|
313
|
+
this.destroyed = true;
|
|
314
|
+
this.readable = false;
|
|
315
|
+
if (error) {
|
|
316
|
+
this.errored = error;
|
|
317
|
+
this.emit('error', error);
|
|
318
|
+
}
|
|
319
|
+
this.emit('close');
|
|
320
|
+
this.closed = true;
|
|
321
|
+
return this;
|
|
322
|
+
}
|
|
323
|
+
close(callback) {
|
|
324
|
+
if (this.closed) {
|
|
325
|
+
if (callback)
|
|
326
|
+
Promise.resolve().then(() => callback(null));
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
this.closed = true;
|
|
330
|
+
this.readable = false;
|
|
331
|
+
this.destroyed = true;
|
|
332
|
+
Promise.resolve().then(() => {
|
|
333
|
+
this.emit('close');
|
|
334
|
+
if (callback)
|
|
335
|
+
callback(null);
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
// Symbol.asyncIterator for async iteration
|
|
339
|
+
async *[Symbol.asyncIterator]() {
|
|
340
|
+
const content = this._loadContent();
|
|
341
|
+
const start = this._options?.start ?? 0;
|
|
342
|
+
const end = this._options?.end ?? content.length;
|
|
343
|
+
const chunk = content.slice(start, end);
|
|
344
|
+
yield this.readableEncoding ? chunk.toString(this.readableEncoding) : chunk;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
// WriteStream class for createWriteStream
|
|
348
|
+
// This provides a type-safe implementation that satisfies nodeFs.WriteStream
|
|
349
|
+
// We use 'as' assertion at the return site since the full interface is complex
|
|
350
|
+
class WriteStream {
|
|
351
|
+
// WriteStream-specific properties
|
|
352
|
+
bytesWritten = 0;
|
|
353
|
+
path;
|
|
354
|
+
pending = false;
|
|
355
|
+
// Writable stream properties
|
|
356
|
+
writable = true;
|
|
357
|
+
writableAborted = false;
|
|
358
|
+
writableEnded = false;
|
|
359
|
+
writableFinished = false;
|
|
360
|
+
writableHighWaterMark = 16384;
|
|
361
|
+
writableLength = 0;
|
|
362
|
+
writableObjectMode = false;
|
|
363
|
+
writableCorked = 0;
|
|
364
|
+
destroyed = false;
|
|
365
|
+
closed = false;
|
|
366
|
+
errored = null;
|
|
367
|
+
writableNeedDrain = false;
|
|
368
|
+
// Internal state
|
|
369
|
+
_chunks = [];
|
|
370
|
+
_listeners = new Map();
|
|
371
|
+
constructor(filePath, _options) {
|
|
372
|
+
this.path = filePath;
|
|
373
|
+
}
|
|
374
|
+
// WriteStream-specific methods
|
|
375
|
+
close(callback) {
|
|
376
|
+
if (this.closed) {
|
|
377
|
+
if (callback)
|
|
378
|
+
Promise.resolve().then(() => callback(null));
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
this.closed = true;
|
|
382
|
+
this.writable = false;
|
|
383
|
+
Promise.resolve().then(() => {
|
|
384
|
+
this.emit("close");
|
|
385
|
+
if (callback)
|
|
386
|
+
callback(null);
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
// Writable methods
|
|
390
|
+
write(chunk, encodingOrCallback, callback) {
|
|
391
|
+
if (this.writableEnded || this.destroyed) {
|
|
392
|
+
const err = new Error("write after end");
|
|
393
|
+
if (typeof encodingOrCallback === "function") {
|
|
394
|
+
Promise.resolve().then(() => encodingOrCallback(err));
|
|
395
|
+
}
|
|
396
|
+
else if (callback) {
|
|
397
|
+
Promise.resolve().then(() => callback(err));
|
|
398
|
+
}
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
let data;
|
|
402
|
+
if (typeof chunk === "string") {
|
|
403
|
+
data = Buffer.from(chunk, typeof encodingOrCallback === "string" ? encodingOrCallback : "utf8");
|
|
404
|
+
}
|
|
405
|
+
else if (Buffer.isBuffer(chunk)) {
|
|
406
|
+
data = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
407
|
+
}
|
|
408
|
+
else if (chunk instanceof Uint8Array) {
|
|
409
|
+
data = chunk;
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
data = Buffer.from(String(chunk));
|
|
413
|
+
}
|
|
414
|
+
this._chunks.push(data);
|
|
415
|
+
this.bytesWritten += data.length;
|
|
416
|
+
this.writableLength += data.length;
|
|
417
|
+
const cb = typeof encodingOrCallback === "function" ? encodingOrCallback : callback;
|
|
418
|
+
if (cb)
|
|
419
|
+
Promise.resolve().then(() => cb(null));
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
end(chunkOrCb, encodingOrCallback, callback) {
|
|
423
|
+
if (this.writableEnded)
|
|
424
|
+
return this;
|
|
425
|
+
let cb;
|
|
426
|
+
if (typeof chunkOrCb === "function") {
|
|
427
|
+
cb = chunkOrCb;
|
|
428
|
+
}
|
|
429
|
+
else if (typeof encodingOrCallback === "function") {
|
|
430
|
+
cb = encodingOrCallback;
|
|
431
|
+
if (chunkOrCb !== undefined && chunkOrCb !== null) {
|
|
432
|
+
this.write(chunkOrCb);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
cb = callback;
|
|
437
|
+
if (chunkOrCb !== undefined && chunkOrCb !== null) {
|
|
438
|
+
this.write(chunkOrCb, encodingOrCallback);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
this.writableEnded = true;
|
|
442
|
+
// Concatenate and write all chunks
|
|
443
|
+
const totalLength = this._chunks.reduce((sum, c) => sum + c.length, 0);
|
|
444
|
+
const result = new Uint8Array(totalLength);
|
|
445
|
+
let offset = 0;
|
|
446
|
+
for (const c of this._chunks) {
|
|
447
|
+
result.set(c, offset);
|
|
448
|
+
offset += c.length;
|
|
449
|
+
}
|
|
450
|
+
// Write to filesystem
|
|
451
|
+
const pathStr = typeof this.path === "string" ? this.path : this.path.toString();
|
|
452
|
+
fs.writeFileSync(pathStr, result);
|
|
453
|
+
this.writable = false;
|
|
454
|
+
this.writableFinished = true;
|
|
455
|
+
this.writableLength = 0;
|
|
456
|
+
Promise.resolve().then(() => {
|
|
457
|
+
this.emit("finish");
|
|
458
|
+
this.emit("close");
|
|
459
|
+
this.closed = true;
|
|
460
|
+
if (cb)
|
|
461
|
+
cb();
|
|
462
|
+
});
|
|
463
|
+
return this;
|
|
464
|
+
}
|
|
465
|
+
setDefaultEncoding(_encoding) {
|
|
466
|
+
return this;
|
|
467
|
+
}
|
|
468
|
+
cork() {
|
|
469
|
+
this.writableCorked++;
|
|
470
|
+
}
|
|
471
|
+
uncork() {
|
|
472
|
+
if (this.writableCorked > 0)
|
|
473
|
+
this.writableCorked--;
|
|
474
|
+
}
|
|
475
|
+
destroy(error) {
|
|
476
|
+
if (this.destroyed)
|
|
477
|
+
return this;
|
|
478
|
+
this.destroyed = true;
|
|
479
|
+
this.writable = false;
|
|
480
|
+
if (error) {
|
|
481
|
+
this.errored = error;
|
|
482
|
+
Promise.resolve().then(() => {
|
|
483
|
+
this.emit("error", error);
|
|
484
|
+
this.emit("close");
|
|
485
|
+
this.closed = true;
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
Promise.resolve().then(() => {
|
|
490
|
+
this.emit("close");
|
|
491
|
+
this.closed = true;
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
return this;
|
|
495
|
+
}
|
|
496
|
+
// Internal methods (required by Writable interface but not typically called directly)
|
|
497
|
+
_write(_chunk, _encoding, callback) {
|
|
498
|
+
callback();
|
|
499
|
+
}
|
|
500
|
+
_destroy(_error, callback) {
|
|
501
|
+
callback();
|
|
502
|
+
}
|
|
503
|
+
_final(callback) {
|
|
504
|
+
callback();
|
|
505
|
+
}
|
|
506
|
+
// EventEmitter methods
|
|
507
|
+
addListener(event, listener) {
|
|
508
|
+
return this.on(event, listener);
|
|
509
|
+
}
|
|
510
|
+
on(event, listener) {
|
|
511
|
+
const listeners = this._listeners.get(event) || [];
|
|
512
|
+
listeners.push(listener);
|
|
513
|
+
this._listeners.set(event, listeners);
|
|
514
|
+
return this;
|
|
515
|
+
}
|
|
516
|
+
once(event, listener) {
|
|
517
|
+
const wrapper = (...args) => {
|
|
518
|
+
this.removeListener(event, wrapper);
|
|
519
|
+
listener(...args);
|
|
520
|
+
};
|
|
521
|
+
return this.on(event, wrapper);
|
|
522
|
+
}
|
|
523
|
+
prependListener(event, listener) {
|
|
524
|
+
const listeners = this._listeners.get(event) || [];
|
|
525
|
+
listeners.unshift(listener);
|
|
526
|
+
this._listeners.set(event, listeners);
|
|
527
|
+
return this;
|
|
528
|
+
}
|
|
529
|
+
prependOnceListener(event, listener) {
|
|
530
|
+
const wrapper = (...args) => {
|
|
531
|
+
this.removeListener(event, wrapper);
|
|
532
|
+
listener(...args);
|
|
533
|
+
};
|
|
534
|
+
return this.prependListener(event, wrapper);
|
|
535
|
+
}
|
|
536
|
+
removeListener(event, listener) {
|
|
537
|
+
const listeners = this._listeners.get(event);
|
|
538
|
+
if (listeners) {
|
|
539
|
+
const idx = listeners.indexOf(listener);
|
|
540
|
+
if (idx !== -1)
|
|
541
|
+
listeners.splice(idx, 1);
|
|
542
|
+
}
|
|
543
|
+
return this;
|
|
544
|
+
}
|
|
545
|
+
off(event, listener) {
|
|
546
|
+
return this.removeListener(event, listener);
|
|
547
|
+
}
|
|
548
|
+
removeAllListeners(event) {
|
|
549
|
+
if (event !== undefined) {
|
|
550
|
+
this._listeners.delete(event);
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
this._listeners.clear();
|
|
554
|
+
}
|
|
555
|
+
return this;
|
|
556
|
+
}
|
|
557
|
+
emit(event, ...args) {
|
|
558
|
+
const listeners = this._listeners.get(event);
|
|
559
|
+
if (listeners && listeners.length > 0) {
|
|
560
|
+
listeners.slice().forEach(l => l(...args));
|
|
561
|
+
return true;
|
|
562
|
+
}
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
listeners(event) {
|
|
566
|
+
return [...(this._listeners.get(event) || [])];
|
|
567
|
+
}
|
|
568
|
+
rawListeners(event) {
|
|
569
|
+
return this.listeners(event);
|
|
570
|
+
}
|
|
571
|
+
listenerCount(event) {
|
|
572
|
+
return (this._listeners.get(event) || []).length;
|
|
573
|
+
}
|
|
574
|
+
eventNames() {
|
|
575
|
+
return [...this._listeners.keys()];
|
|
576
|
+
}
|
|
577
|
+
getMaxListeners() {
|
|
578
|
+
return 10;
|
|
579
|
+
}
|
|
580
|
+
setMaxListeners(_n) {
|
|
581
|
+
return this;
|
|
582
|
+
}
|
|
583
|
+
// Pipe methods (minimal implementation)
|
|
584
|
+
pipe(destination, _options) {
|
|
585
|
+
return destination;
|
|
586
|
+
}
|
|
587
|
+
unpipe(_destination) {
|
|
588
|
+
return this;
|
|
589
|
+
}
|
|
590
|
+
// Additional required methods
|
|
591
|
+
compose(_stream, _options) {
|
|
592
|
+
throw new Error("compose not implemented in sandbox");
|
|
593
|
+
}
|
|
594
|
+
[Symbol.asyncDispose]() {
|
|
595
|
+
return Promise.resolve();
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
// Parse flags string to number
|
|
599
|
+
function parseFlags(flags) {
|
|
600
|
+
if (typeof flags === "number")
|
|
601
|
+
return flags;
|
|
602
|
+
const flagMap = {
|
|
603
|
+
r: O_RDONLY,
|
|
604
|
+
"r+": O_RDWR,
|
|
605
|
+
w: O_WRONLY | O_CREAT | O_TRUNC,
|
|
606
|
+
"w+": O_RDWR | O_CREAT | O_TRUNC,
|
|
607
|
+
a: O_WRONLY | O_APPEND | O_CREAT,
|
|
608
|
+
"a+": O_RDWR | O_APPEND | O_CREAT,
|
|
609
|
+
wx: O_WRONLY | O_CREAT | O_TRUNC | O_EXCL,
|
|
610
|
+
xw: O_WRONLY | O_CREAT | O_TRUNC | O_EXCL,
|
|
611
|
+
"wx+": O_RDWR | O_CREAT | O_TRUNC | O_EXCL,
|
|
612
|
+
"xw+": O_RDWR | O_CREAT | O_TRUNC | O_EXCL,
|
|
613
|
+
ax: O_WRONLY | O_APPEND | O_CREAT | O_EXCL,
|
|
614
|
+
xa: O_WRONLY | O_APPEND | O_CREAT | O_EXCL,
|
|
615
|
+
"ax+": O_RDWR | O_APPEND | O_CREAT | O_EXCL,
|
|
616
|
+
"xa+": O_RDWR | O_APPEND | O_CREAT | O_EXCL,
|
|
617
|
+
};
|
|
618
|
+
if (flags in flagMap)
|
|
619
|
+
return flagMap[flags];
|
|
620
|
+
throw new Error("Unknown file flag: " + flags);
|
|
621
|
+
}
|
|
622
|
+
// Check if flags allow reading
|
|
623
|
+
function canRead(flags) {
|
|
624
|
+
const mode = flags & O_ACCMODE;
|
|
625
|
+
return mode === 0 || mode === 2;
|
|
626
|
+
}
|
|
627
|
+
// Check if flags allow writing
|
|
628
|
+
function canWrite(flags) {
|
|
629
|
+
const mode = flags & O_ACCMODE;
|
|
630
|
+
return mode === 1 || mode === 2;
|
|
631
|
+
}
|
|
632
|
+
// Helper to create fs errors
|
|
633
|
+
function createFsError(code, message, syscall, path) {
|
|
634
|
+
const err = new Error(message);
|
|
635
|
+
err.code = code;
|
|
636
|
+
err.errno = code === "ENOENT" ? -2 : code === "EBADF" ? -9 : -1;
|
|
637
|
+
err.syscall = syscall;
|
|
638
|
+
if (path)
|
|
639
|
+
err.path = path;
|
|
640
|
+
return err;
|
|
641
|
+
}
|
|
642
|
+
// Helper to convert PathLike to string
|
|
643
|
+
function toPathString(path) {
|
|
644
|
+
if (typeof path === "string")
|
|
645
|
+
return path;
|
|
646
|
+
if (Buffer.isBuffer(path))
|
|
647
|
+
return path.toString("utf8");
|
|
648
|
+
if (path instanceof URL)
|
|
649
|
+
return path.pathname;
|
|
650
|
+
return String(path);
|
|
651
|
+
}
|
|
652
|
+
// Note: Path normalization is handled by VirtualFileSystem, not here.
|
|
653
|
+
// The VFS expects /data/* paths for Directory access, so we pass paths through unchanged.
|
|
654
|
+
// The fs module implementation
|
|
655
|
+
const fs = {
|
|
656
|
+
// Constants
|
|
657
|
+
constants: {
|
|
658
|
+
// File Access Constants
|
|
659
|
+
F_OK: 0,
|
|
660
|
+
R_OK: 4,
|
|
661
|
+
W_OK: 2,
|
|
662
|
+
X_OK: 1,
|
|
663
|
+
// File Copy Constants
|
|
664
|
+
COPYFILE_EXCL: 1,
|
|
665
|
+
COPYFILE_FICLONE: 2,
|
|
666
|
+
COPYFILE_FICLONE_FORCE: 4,
|
|
667
|
+
// File Open Constants
|
|
668
|
+
O_RDONLY,
|
|
669
|
+
O_WRONLY,
|
|
670
|
+
O_RDWR,
|
|
671
|
+
O_CREAT,
|
|
672
|
+
O_EXCL,
|
|
673
|
+
O_NOCTTY: 256,
|
|
674
|
+
O_TRUNC,
|
|
675
|
+
O_APPEND,
|
|
676
|
+
O_DIRECTORY: 65536,
|
|
677
|
+
O_NOATIME: 262144,
|
|
678
|
+
O_NOFOLLOW: 131072,
|
|
679
|
+
O_SYNC: 1052672,
|
|
680
|
+
O_DSYNC: 4096,
|
|
681
|
+
O_SYMLINK: 2097152,
|
|
682
|
+
O_DIRECT: 16384,
|
|
683
|
+
O_NONBLOCK: 2048,
|
|
684
|
+
// File Type Constants
|
|
685
|
+
S_IFMT: 61440,
|
|
686
|
+
S_IFREG: 32768,
|
|
687
|
+
S_IFDIR: 16384,
|
|
688
|
+
S_IFCHR: 8192,
|
|
689
|
+
S_IFBLK: 24576,
|
|
690
|
+
S_IFIFO: 4096,
|
|
691
|
+
S_IFLNK: 40960,
|
|
692
|
+
S_IFSOCK: 49152,
|
|
693
|
+
// File Mode Constants
|
|
694
|
+
S_IRWXU: 448,
|
|
695
|
+
S_IRUSR: 256,
|
|
696
|
+
S_IWUSR: 128,
|
|
697
|
+
S_IXUSR: 64,
|
|
698
|
+
S_IRWXG: 56,
|
|
699
|
+
S_IRGRP: 32,
|
|
700
|
+
S_IWGRP: 16,
|
|
701
|
+
S_IXGRP: 8,
|
|
702
|
+
S_IRWXO: 7,
|
|
703
|
+
S_IROTH: 4,
|
|
704
|
+
S_IWOTH: 2,
|
|
705
|
+
S_IXOTH: 1,
|
|
706
|
+
UV_FS_O_FILEMAP: 536870912,
|
|
707
|
+
},
|
|
708
|
+
Stats,
|
|
709
|
+
Dirent,
|
|
710
|
+
// Sync methods
|
|
711
|
+
readFileSync(path, options) {
|
|
712
|
+
const rawPath = typeof path === "number" ? fdTable.get(path)?.path : toPathString(path);
|
|
713
|
+
if (!rawPath)
|
|
714
|
+
throw createFsError("EBADF", "EBADF: bad file descriptor", "read");
|
|
715
|
+
const pathStr = rawPath;
|
|
716
|
+
const encoding = typeof options === "string" ? options : options?.encoding;
|
|
717
|
+
try {
|
|
718
|
+
if (encoding) {
|
|
719
|
+
// Text mode - use text read
|
|
720
|
+
const content = _fs.readFile.applySyncPromise(undefined, [pathStr]);
|
|
721
|
+
return content;
|
|
722
|
+
}
|
|
723
|
+
else {
|
|
724
|
+
// Binary mode - use binary read with base64 encoding
|
|
725
|
+
const base64Content = _fs.readFileBinary.applySyncPromise(undefined, [pathStr]);
|
|
726
|
+
return Buffer.from(base64Content, "base64");
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
catch (err) {
|
|
730
|
+
// Convert "entry not found" and similar errors to proper ENOENT
|
|
731
|
+
const errMsg = err.message || String(err);
|
|
732
|
+
if (errMsg.includes("entry not found") || errMsg.includes("not found")) {
|
|
733
|
+
throw createFsError("ENOENT", `ENOENT: no such file or directory, read '${rawPath}'`, "read", rawPath);
|
|
734
|
+
}
|
|
735
|
+
throw err;
|
|
736
|
+
}
|
|
737
|
+
},
|
|
738
|
+
writeFileSync(file, data, _options) {
|
|
739
|
+
const rawPath = typeof file === "number" ? fdTable.get(file)?.path : toPathString(file);
|
|
740
|
+
if (!rawPath)
|
|
741
|
+
throw createFsError("EBADF", "EBADF: bad file descriptor", "write");
|
|
742
|
+
const pathStr = rawPath;
|
|
743
|
+
if (typeof data === "string") {
|
|
744
|
+
// Text mode - use text write
|
|
745
|
+
_fs.writeFile.applySyncPromise(undefined, [pathStr, data]);
|
|
746
|
+
}
|
|
747
|
+
else if (ArrayBuffer.isView(data)) {
|
|
748
|
+
// Binary mode - convert to base64 and use binary write
|
|
749
|
+
const uint8 = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
750
|
+
const base64 = Buffer.from(uint8).toString("base64");
|
|
751
|
+
_fs.writeFileBinary.applySyncPromise(undefined, [pathStr, base64]);
|
|
752
|
+
}
|
|
753
|
+
else {
|
|
754
|
+
// Fallback to text mode
|
|
755
|
+
_fs.writeFile.applySyncPromise(undefined, [pathStr, String(data)]);
|
|
756
|
+
}
|
|
757
|
+
},
|
|
758
|
+
appendFileSync(path, data, options) {
|
|
759
|
+
const existing = fs.existsSync(path)
|
|
760
|
+
? fs.readFileSync(path, "utf8")
|
|
761
|
+
: "";
|
|
762
|
+
const content = typeof data === "string" ? data : String(data);
|
|
763
|
+
fs.writeFileSync(path, existing + content, options);
|
|
764
|
+
},
|
|
765
|
+
readdirSync(path, options) {
|
|
766
|
+
const rawPath = toPathString(path);
|
|
767
|
+
const pathStr = rawPath;
|
|
768
|
+
let entriesJson;
|
|
769
|
+
try {
|
|
770
|
+
entriesJson = _fs.readDir.applySyncPromise(undefined, [pathStr]);
|
|
771
|
+
}
|
|
772
|
+
catch (err) {
|
|
773
|
+
// Convert "entry not found" and similar errors to proper ENOENT
|
|
774
|
+
const errMsg = err.message || String(err);
|
|
775
|
+
if (errMsg.includes("entry not found") || errMsg.includes("not found")) {
|
|
776
|
+
throw createFsError("ENOENT", `ENOENT: no such file or directory, scandir '${rawPath}'`, "scandir", rawPath);
|
|
777
|
+
}
|
|
778
|
+
throw err;
|
|
779
|
+
}
|
|
780
|
+
const entries = JSON.parse(entriesJson);
|
|
781
|
+
if (options?.withFileTypes) {
|
|
782
|
+
return entries.map((e) => new Dirent(e.name, e.isDirectory, rawPath));
|
|
783
|
+
}
|
|
784
|
+
return entries.map((e) => e.name);
|
|
785
|
+
},
|
|
786
|
+
mkdirSync(path, options) {
|
|
787
|
+
const rawPath = toPathString(path);
|
|
788
|
+
const pathStr = rawPath;
|
|
789
|
+
const recursive = typeof options === "object" ? options?.recursive ?? false : false;
|
|
790
|
+
_fs.mkdir.applySyncPromise(undefined, [pathStr, recursive]);
|
|
791
|
+
return recursive ? rawPath : undefined;
|
|
792
|
+
},
|
|
793
|
+
rmdirSync(path, _options) {
|
|
794
|
+
const pathStr = toPathString(path);
|
|
795
|
+
_fs.rmdir.applySyncPromise(undefined, [pathStr]);
|
|
796
|
+
},
|
|
797
|
+
rmSync(path, options) {
|
|
798
|
+
const pathStr = toPathString(path);
|
|
799
|
+
const opts = options || {};
|
|
800
|
+
try {
|
|
801
|
+
const stats = fs.statSync(pathStr);
|
|
802
|
+
if (stats.isDirectory()) {
|
|
803
|
+
if (opts.recursive) {
|
|
804
|
+
// Recursively remove directory contents
|
|
805
|
+
const entries = fs.readdirSync(pathStr);
|
|
806
|
+
for (const entry of entries) {
|
|
807
|
+
const entryPath = pathStr.endsWith("/") ? pathStr + entry : pathStr + "/" + entry;
|
|
808
|
+
const entryStats = fs.statSync(entryPath);
|
|
809
|
+
if (entryStats.isDirectory()) {
|
|
810
|
+
fs.rmSync(entryPath, { recursive: true });
|
|
811
|
+
}
|
|
812
|
+
else {
|
|
813
|
+
fs.unlinkSync(entryPath);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
fs.rmdirSync(pathStr);
|
|
817
|
+
}
|
|
818
|
+
else {
|
|
819
|
+
fs.rmdirSync(pathStr);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
else {
|
|
823
|
+
fs.unlinkSync(pathStr);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
catch (e) {
|
|
827
|
+
if (opts.force && e.code === "ENOENT") {
|
|
828
|
+
return; // Ignore ENOENT when force is true
|
|
829
|
+
}
|
|
830
|
+
throw e;
|
|
831
|
+
}
|
|
832
|
+
},
|
|
833
|
+
existsSync(path) {
|
|
834
|
+
const pathStr = toPathString(path);
|
|
835
|
+
return _fs.exists.applySyncPromise(undefined, [pathStr]);
|
|
836
|
+
},
|
|
837
|
+
statSync(path, _options) {
|
|
838
|
+
const rawPath = toPathString(path);
|
|
839
|
+
const pathStr = rawPath;
|
|
840
|
+
let statJson;
|
|
841
|
+
try {
|
|
842
|
+
statJson = _fs.stat.applySyncPromise(undefined, [pathStr]);
|
|
843
|
+
}
|
|
844
|
+
catch (err) {
|
|
845
|
+
// Convert various "not found" errors to proper ENOENT
|
|
846
|
+
const errMsg = err.message || String(err);
|
|
847
|
+
if (errMsg.includes("entry not found") ||
|
|
848
|
+
errMsg.includes("not found") ||
|
|
849
|
+
errMsg.includes("ENOENT") ||
|
|
850
|
+
errMsg.includes("no such file or directory")) {
|
|
851
|
+
throw createFsError("ENOENT", `ENOENT: no such file or directory, stat '${rawPath}'`, "stat", rawPath);
|
|
852
|
+
}
|
|
853
|
+
throw err;
|
|
854
|
+
}
|
|
855
|
+
const stat = JSON.parse(statJson);
|
|
856
|
+
return new Stats(stat);
|
|
857
|
+
},
|
|
858
|
+
lstatSync(path, _options) {
|
|
859
|
+
// In our virtual fs, lstat is the same as stat (no symlinks)
|
|
860
|
+
return fs.statSync(path);
|
|
861
|
+
},
|
|
862
|
+
unlinkSync(path) {
|
|
863
|
+
const pathStr = toPathString(path);
|
|
864
|
+
_fs.unlink.applySyncPromise(undefined, [pathStr]);
|
|
865
|
+
},
|
|
866
|
+
renameSync(oldPath, newPath) {
|
|
867
|
+
const oldPathStr = toPathString(oldPath);
|
|
868
|
+
const newPathStr = toPathString(newPath);
|
|
869
|
+
_fs.rename.applySyncPromise(undefined, [oldPathStr, newPathStr]);
|
|
870
|
+
},
|
|
871
|
+
copyFileSync(src, dest, _mode) {
|
|
872
|
+
// readFileSync and writeFileSync already normalize paths
|
|
873
|
+
const content = fs.readFileSync(src);
|
|
874
|
+
fs.writeFileSync(dest, content);
|
|
875
|
+
},
|
|
876
|
+
// File descriptor methods
|
|
877
|
+
openSync(path, flags, _mode) {
|
|
878
|
+
const rawPath = toPathString(path);
|
|
879
|
+
const pathStr = rawPath;
|
|
880
|
+
const numFlags = parseFlags(flags);
|
|
881
|
+
const fd = nextFd++;
|
|
882
|
+
// Check if file exists (existsSync already normalizes)
|
|
883
|
+
const exists = fs.existsSync(path);
|
|
884
|
+
// Handle O_CREAT - create file if it doesn't exist
|
|
885
|
+
if (numFlags & 64 && !exists) {
|
|
886
|
+
fs.writeFileSync(path, "");
|
|
887
|
+
}
|
|
888
|
+
else if (!exists && !(numFlags & 64)) {
|
|
889
|
+
throw createFsError("ENOENT", `ENOENT: no such file or directory, open '${rawPath}'`, "open", rawPath);
|
|
890
|
+
}
|
|
891
|
+
// Handle O_TRUNC - truncate file
|
|
892
|
+
if (numFlags & 512 && exists) {
|
|
893
|
+
fs.writeFileSync(path, "");
|
|
894
|
+
}
|
|
895
|
+
// Store normalized path in fd table for subsequent operations
|
|
896
|
+
fdTable.set(fd, { path: pathStr, flags: numFlags, position: 0 });
|
|
897
|
+
return fd;
|
|
898
|
+
},
|
|
899
|
+
closeSync(fd) {
|
|
900
|
+
if (!fdTable.has(fd)) {
|
|
901
|
+
throw createFsError("EBADF", "EBADF: bad file descriptor, close", "close");
|
|
902
|
+
}
|
|
903
|
+
fdTable.delete(fd);
|
|
904
|
+
},
|
|
905
|
+
readSync(fd, buffer, offset, length, position) {
|
|
906
|
+
const entry = fdTable.get(fd);
|
|
907
|
+
if (!entry) {
|
|
908
|
+
throw createFsError("EBADF", "EBADF: bad file descriptor, read", "read");
|
|
909
|
+
}
|
|
910
|
+
if (!canRead(entry.flags)) {
|
|
911
|
+
throw createFsError("EBADF", "EBADF: bad file descriptor, read", "read");
|
|
912
|
+
}
|
|
913
|
+
const content = fs.readFileSync(entry.path, "utf8");
|
|
914
|
+
const readOffset = offset ?? 0;
|
|
915
|
+
const readLength = length ?? (buffer.byteLength - readOffset);
|
|
916
|
+
const pos = position !== null && position !== undefined ? Number(position) : entry.position;
|
|
917
|
+
const toRead = content.slice(pos, pos + readLength);
|
|
918
|
+
const bytes = Buffer.from(toRead);
|
|
919
|
+
const targetBuffer = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
920
|
+
for (let i = 0; i < bytes.length && i < readLength; i++) {
|
|
921
|
+
targetBuffer[readOffset + i] = bytes[i];
|
|
922
|
+
}
|
|
923
|
+
if (position === null || position === undefined) {
|
|
924
|
+
entry.position += bytes.length;
|
|
925
|
+
}
|
|
926
|
+
return bytes.length;
|
|
927
|
+
},
|
|
928
|
+
writeSync(fd, buffer, offsetOrPosition, lengthOrEncoding, position) {
|
|
929
|
+
const entry = fdTable.get(fd);
|
|
930
|
+
if (!entry) {
|
|
931
|
+
throw createFsError("EBADF", "EBADF: bad file descriptor, write", "write");
|
|
932
|
+
}
|
|
933
|
+
// fs.writeSync
|
|
934
|
+
if (!canWrite(entry.flags)) {
|
|
935
|
+
throw createFsError("EBADF", "EBADF: bad file descriptor, write", "write");
|
|
936
|
+
}
|
|
937
|
+
// Handle string or buffer
|
|
938
|
+
let data;
|
|
939
|
+
let writePosition;
|
|
940
|
+
if (typeof buffer === "string") {
|
|
941
|
+
data = buffer;
|
|
942
|
+
writePosition = offsetOrPosition;
|
|
943
|
+
}
|
|
944
|
+
else {
|
|
945
|
+
const offset = offsetOrPosition ?? 0;
|
|
946
|
+
const length = (typeof lengthOrEncoding === "number" ? lengthOrEncoding : null) ?? (buffer.byteLength - offset);
|
|
947
|
+
const view = new Uint8Array(buffer.buffer, buffer.byteOffset + offset, length);
|
|
948
|
+
data = new TextDecoder().decode(view);
|
|
949
|
+
writePosition = position;
|
|
950
|
+
}
|
|
951
|
+
// Read existing content
|
|
952
|
+
let content = "";
|
|
953
|
+
if (fs.existsSync(entry.path)) {
|
|
954
|
+
content = fs.readFileSync(entry.path, "utf8");
|
|
955
|
+
}
|
|
956
|
+
// Determine write position
|
|
957
|
+
let writePos;
|
|
958
|
+
if (entry.flags & 1024) {
|
|
959
|
+
// O_APPEND
|
|
960
|
+
writePos = content.length;
|
|
961
|
+
}
|
|
962
|
+
else if (writePosition !== null && writePosition !== undefined) {
|
|
963
|
+
writePos = writePosition;
|
|
964
|
+
}
|
|
965
|
+
else {
|
|
966
|
+
writePos = entry.position;
|
|
967
|
+
}
|
|
968
|
+
// Pad with nulls if writing past end
|
|
969
|
+
while (content.length < writePos) {
|
|
970
|
+
content += "\0";
|
|
971
|
+
}
|
|
972
|
+
// Write data
|
|
973
|
+
const newContent = content.slice(0, writePos) + data + content.slice(writePos + data.length);
|
|
974
|
+
fs.writeFileSync(entry.path, newContent);
|
|
975
|
+
// Update position if not using explicit position
|
|
976
|
+
if (writePosition === null || writePosition === undefined) {
|
|
977
|
+
entry.position = writePos + data.length;
|
|
978
|
+
}
|
|
979
|
+
return data.length;
|
|
980
|
+
},
|
|
981
|
+
fstatSync(fd) {
|
|
982
|
+
const entry = fdTable.get(fd);
|
|
983
|
+
if (!entry) {
|
|
984
|
+
throw createFsError("EBADF", "EBADF: bad file descriptor, fstat", "fstat");
|
|
985
|
+
}
|
|
986
|
+
return fs.statSync(entry.path);
|
|
987
|
+
},
|
|
988
|
+
ftruncateSync(fd, len) {
|
|
989
|
+
const entry = fdTable.get(fd);
|
|
990
|
+
if (!entry) {
|
|
991
|
+
throw createFsError("EBADF", "EBADF: bad file descriptor, ftruncate", "ftruncate");
|
|
992
|
+
}
|
|
993
|
+
const content = fs.existsSync(entry.path)
|
|
994
|
+
? fs.readFileSync(entry.path, "utf8")
|
|
995
|
+
: "";
|
|
996
|
+
const newLen = len ?? 0;
|
|
997
|
+
if (content.length > newLen) {
|
|
998
|
+
fs.writeFileSync(entry.path, content.slice(0, newLen));
|
|
999
|
+
}
|
|
1000
|
+
else {
|
|
1001
|
+
let padded = content;
|
|
1002
|
+
while (padded.length < newLen)
|
|
1003
|
+
padded += "\0";
|
|
1004
|
+
fs.writeFileSync(entry.path, padded);
|
|
1005
|
+
}
|
|
1006
|
+
},
|
|
1007
|
+
// Async methods - wrap sync methods in callbacks/promises
|
|
1008
|
+
//
|
|
1009
|
+
// IMPORTANT: Low-level fd operations (open, close, read, write) and operations commonly
|
|
1010
|
+
// used by streaming libraries (stat, lstat, rename, unlink) must defer their callbacks
|
|
1011
|
+
// using queueMicrotask(). This is critical for proper stream operation.
|
|
1012
|
+
//
|
|
1013
|
+
// Why: Node.js streams (like tar, minipass, fs-minipass) use callback chains where each
|
|
1014
|
+
// callback triggers the next read/write operation. These streams also rely on events like
|
|
1015
|
+
// 'drain' to know when to resume writing. If callbacks fire synchronously, the event loop
|
|
1016
|
+
// never gets a chance to process these events, causing streams to stall after the first chunk.
|
|
1017
|
+
//
|
|
1018
|
+
// Example problem without queueMicrotask:
|
|
1019
|
+
// 1. tar calls fs.read() with callback
|
|
1020
|
+
// 2. Our sync implementation calls callback immediately
|
|
1021
|
+
// 3. Callback writes to stream, stream buffer fills, returns false (needs drain)
|
|
1022
|
+
// 4. Code sets up 'drain' listener and returns
|
|
1023
|
+
// 5. But we never returned to event loop, so 'drain' never fires
|
|
1024
|
+
// 6. Stream hangs forever
|
|
1025
|
+
//
|
|
1026
|
+
// With queueMicrotask, step 2 defers the callback, allowing the event loop to process
|
|
1027
|
+
// pending events (including 'drain') before the next operation starts.
|
|
1028
|
+
readFile(path, options, callback) {
|
|
1029
|
+
if (typeof options === "function") {
|
|
1030
|
+
callback = options;
|
|
1031
|
+
options = undefined;
|
|
1032
|
+
}
|
|
1033
|
+
if (callback) {
|
|
1034
|
+
try {
|
|
1035
|
+
callback(null, fs.readFileSync(path, options));
|
|
1036
|
+
}
|
|
1037
|
+
catch (e) {
|
|
1038
|
+
callback(e);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
else {
|
|
1042
|
+
return Promise.resolve(fs.readFileSync(path, options));
|
|
1043
|
+
}
|
|
1044
|
+
},
|
|
1045
|
+
writeFile(path, data, options, callback) {
|
|
1046
|
+
if (typeof options === "function") {
|
|
1047
|
+
callback = options;
|
|
1048
|
+
options = undefined;
|
|
1049
|
+
}
|
|
1050
|
+
if (callback) {
|
|
1051
|
+
try {
|
|
1052
|
+
fs.writeFileSync(path, data, options);
|
|
1053
|
+
callback(null);
|
|
1054
|
+
}
|
|
1055
|
+
catch (e) {
|
|
1056
|
+
callback(e);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
else {
|
|
1060
|
+
return Promise.resolve(fs.writeFileSync(path, data, options));
|
|
1061
|
+
}
|
|
1062
|
+
},
|
|
1063
|
+
appendFile(path, data, options, callback) {
|
|
1064
|
+
if (typeof options === "function") {
|
|
1065
|
+
callback = options;
|
|
1066
|
+
options = undefined;
|
|
1067
|
+
}
|
|
1068
|
+
if (callback) {
|
|
1069
|
+
try {
|
|
1070
|
+
fs.appendFileSync(path, data, options);
|
|
1071
|
+
callback(null);
|
|
1072
|
+
}
|
|
1073
|
+
catch (e) {
|
|
1074
|
+
callback(e);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
else {
|
|
1078
|
+
return Promise.resolve(fs.appendFileSync(path, data, options));
|
|
1079
|
+
}
|
|
1080
|
+
},
|
|
1081
|
+
readdir(path, options, callback) {
|
|
1082
|
+
if (typeof options === "function") {
|
|
1083
|
+
callback = options;
|
|
1084
|
+
options = undefined;
|
|
1085
|
+
}
|
|
1086
|
+
if (callback) {
|
|
1087
|
+
try {
|
|
1088
|
+
callback(null, fs.readdirSync(path, options));
|
|
1089
|
+
}
|
|
1090
|
+
catch (e) {
|
|
1091
|
+
callback(e);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
else {
|
|
1095
|
+
return Promise.resolve(fs.readdirSync(path, options));
|
|
1096
|
+
}
|
|
1097
|
+
},
|
|
1098
|
+
mkdir(path, options, callback) {
|
|
1099
|
+
if (typeof options === "function") {
|
|
1100
|
+
callback = options;
|
|
1101
|
+
options = undefined;
|
|
1102
|
+
}
|
|
1103
|
+
if (callback) {
|
|
1104
|
+
try {
|
|
1105
|
+
fs.mkdirSync(path, options);
|
|
1106
|
+
callback(null);
|
|
1107
|
+
}
|
|
1108
|
+
catch (e) {
|
|
1109
|
+
callback(e);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
else {
|
|
1113
|
+
fs.mkdirSync(path, options);
|
|
1114
|
+
return Promise.resolve();
|
|
1115
|
+
}
|
|
1116
|
+
},
|
|
1117
|
+
rmdir(path, callback) {
|
|
1118
|
+
if (callback) {
|
|
1119
|
+
// Defer callback to next tick to allow event loop to process stream events
|
|
1120
|
+
const cb = callback;
|
|
1121
|
+
try {
|
|
1122
|
+
fs.rmdirSync(path);
|
|
1123
|
+
queueMicrotask(() => cb(null));
|
|
1124
|
+
}
|
|
1125
|
+
catch (e) {
|
|
1126
|
+
queueMicrotask(() => cb(e));
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
else {
|
|
1130
|
+
return Promise.resolve(fs.rmdirSync(path));
|
|
1131
|
+
}
|
|
1132
|
+
},
|
|
1133
|
+
// rm - remove files or directories (with recursive support)
|
|
1134
|
+
rm(path, options, callback) {
|
|
1135
|
+
let opts = {};
|
|
1136
|
+
let cb;
|
|
1137
|
+
if (typeof options === "function") {
|
|
1138
|
+
cb = options;
|
|
1139
|
+
}
|
|
1140
|
+
else if (options) {
|
|
1141
|
+
opts = options;
|
|
1142
|
+
cb = callback;
|
|
1143
|
+
}
|
|
1144
|
+
else {
|
|
1145
|
+
cb = callback;
|
|
1146
|
+
}
|
|
1147
|
+
const doRm = () => {
|
|
1148
|
+
try {
|
|
1149
|
+
const stats = fs.statSync(path);
|
|
1150
|
+
if (stats.isDirectory()) {
|
|
1151
|
+
if (opts.recursive) {
|
|
1152
|
+
// Recursively remove directory contents
|
|
1153
|
+
const entries = fs.readdirSync(path);
|
|
1154
|
+
for (const entry of entries) {
|
|
1155
|
+
const entryPath = path.endsWith("/") ? path + entry : path + "/" + entry;
|
|
1156
|
+
const entryStats = fs.statSync(entryPath);
|
|
1157
|
+
if (entryStats.isDirectory()) {
|
|
1158
|
+
fs.rmSync(entryPath, { recursive: true });
|
|
1159
|
+
}
|
|
1160
|
+
else {
|
|
1161
|
+
fs.unlinkSync(entryPath);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
fs.rmdirSync(path);
|
|
1165
|
+
}
|
|
1166
|
+
else {
|
|
1167
|
+
fs.rmdirSync(path);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
else {
|
|
1171
|
+
fs.unlinkSync(path);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
catch (e) {
|
|
1175
|
+
if (opts.force && e.code === "ENOENT") {
|
|
1176
|
+
return; // Ignore ENOENT when force is true
|
|
1177
|
+
}
|
|
1178
|
+
throw e;
|
|
1179
|
+
}
|
|
1180
|
+
};
|
|
1181
|
+
if (cb) {
|
|
1182
|
+
// Defer callback to next tick to allow event loop to process stream events
|
|
1183
|
+
try {
|
|
1184
|
+
doRm();
|
|
1185
|
+
queueMicrotask(() => cb(null));
|
|
1186
|
+
}
|
|
1187
|
+
catch (e) {
|
|
1188
|
+
queueMicrotask(() => cb(e));
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
else {
|
|
1192
|
+
doRm();
|
|
1193
|
+
return Promise.resolve();
|
|
1194
|
+
}
|
|
1195
|
+
},
|
|
1196
|
+
exists(path, callback) {
|
|
1197
|
+
if (callback) {
|
|
1198
|
+
callback(fs.existsSync(path));
|
|
1199
|
+
}
|
|
1200
|
+
else {
|
|
1201
|
+
return Promise.resolve(fs.existsSync(path));
|
|
1202
|
+
}
|
|
1203
|
+
},
|
|
1204
|
+
stat(path, callback) {
|
|
1205
|
+
if (callback) {
|
|
1206
|
+
// Defer callback to next tick to allow event loop to process stream events
|
|
1207
|
+
const cb = callback;
|
|
1208
|
+
try {
|
|
1209
|
+
const stats = fs.statSync(path);
|
|
1210
|
+
queueMicrotask(() => cb(null, stats));
|
|
1211
|
+
}
|
|
1212
|
+
catch (e) {
|
|
1213
|
+
queueMicrotask(() => cb(e));
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
else {
|
|
1217
|
+
return Promise.resolve(fs.statSync(path));
|
|
1218
|
+
}
|
|
1219
|
+
},
|
|
1220
|
+
lstat(path, callback) {
|
|
1221
|
+
if (callback) {
|
|
1222
|
+
// Defer callback to next tick to allow event loop to process stream events
|
|
1223
|
+
const cb = callback;
|
|
1224
|
+
try {
|
|
1225
|
+
const stats = fs.lstatSync(path);
|
|
1226
|
+
queueMicrotask(() => cb(null, stats));
|
|
1227
|
+
}
|
|
1228
|
+
catch (e) {
|
|
1229
|
+
queueMicrotask(() => cb(e));
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
else {
|
|
1233
|
+
return Promise.resolve(fs.lstatSync(path));
|
|
1234
|
+
}
|
|
1235
|
+
},
|
|
1236
|
+
unlink(path, callback) {
|
|
1237
|
+
if (callback) {
|
|
1238
|
+
// Defer callback to next tick to allow event loop to process stream events
|
|
1239
|
+
const cb = callback;
|
|
1240
|
+
try {
|
|
1241
|
+
fs.unlinkSync(path);
|
|
1242
|
+
queueMicrotask(() => cb(null));
|
|
1243
|
+
}
|
|
1244
|
+
catch (e) {
|
|
1245
|
+
queueMicrotask(() => cb(e));
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
else {
|
|
1249
|
+
return Promise.resolve(fs.unlinkSync(path));
|
|
1250
|
+
}
|
|
1251
|
+
},
|
|
1252
|
+
rename(oldPath, newPath, callback) {
|
|
1253
|
+
if (callback) {
|
|
1254
|
+
// Defer callback to next tick to allow event loop to process stream events
|
|
1255
|
+
const cb = callback;
|
|
1256
|
+
try {
|
|
1257
|
+
fs.renameSync(oldPath, newPath);
|
|
1258
|
+
queueMicrotask(() => cb(null));
|
|
1259
|
+
}
|
|
1260
|
+
catch (e) {
|
|
1261
|
+
queueMicrotask(() => cb(e));
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
else {
|
|
1265
|
+
return Promise.resolve(fs.renameSync(oldPath, newPath));
|
|
1266
|
+
}
|
|
1267
|
+
},
|
|
1268
|
+
copyFile(src, dest, callback) {
|
|
1269
|
+
if (callback) {
|
|
1270
|
+
try {
|
|
1271
|
+
fs.copyFileSync(src, dest);
|
|
1272
|
+
callback(null);
|
|
1273
|
+
}
|
|
1274
|
+
catch (e) {
|
|
1275
|
+
callback(e);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
else {
|
|
1279
|
+
return Promise.resolve(fs.copyFileSync(src, dest));
|
|
1280
|
+
}
|
|
1281
|
+
},
|
|
1282
|
+
open(path, flags, mode, callback) {
|
|
1283
|
+
if (typeof mode === "function") {
|
|
1284
|
+
callback = mode;
|
|
1285
|
+
mode = undefined;
|
|
1286
|
+
}
|
|
1287
|
+
if (callback) {
|
|
1288
|
+
// Defer callback to next tick to allow event loop to process stream events
|
|
1289
|
+
const cb = callback;
|
|
1290
|
+
try {
|
|
1291
|
+
const fd = fs.openSync(path, flags, mode);
|
|
1292
|
+
queueMicrotask(() => cb(null, fd));
|
|
1293
|
+
}
|
|
1294
|
+
catch (e) {
|
|
1295
|
+
queueMicrotask(() => cb(e));
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
else {
|
|
1299
|
+
return Promise.resolve(fs.openSync(path, flags, mode));
|
|
1300
|
+
}
|
|
1301
|
+
},
|
|
1302
|
+
close(fd, callback) {
|
|
1303
|
+
if (callback) {
|
|
1304
|
+
// Defer callback to next tick to allow event loop to process stream events
|
|
1305
|
+
const cb = callback;
|
|
1306
|
+
try {
|
|
1307
|
+
fs.closeSync(fd);
|
|
1308
|
+
queueMicrotask(() => cb(null));
|
|
1309
|
+
}
|
|
1310
|
+
catch (e) {
|
|
1311
|
+
queueMicrotask(() => cb(e));
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
else {
|
|
1315
|
+
return Promise.resolve(fs.closeSync(fd));
|
|
1316
|
+
}
|
|
1317
|
+
},
|
|
1318
|
+
read(fd, buffer, offset, length, position, callback) {
|
|
1319
|
+
if (callback) {
|
|
1320
|
+
// Defer callback to next tick to allow event loop to process stream events
|
|
1321
|
+
const cb = callback;
|
|
1322
|
+
try {
|
|
1323
|
+
const bytesRead = fs.readSync(fd, buffer, offset, length, position);
|
|
1324
|
+
queueMicrotask(() => cb(null, bytesRead, buffer));
|
|
1325
|
+
}
|
|
1326
|
+
catch (e) {
|
|
1327
|
+
queueMicrotask(() => cb(e));
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
else {
|
|
1331
|
+
return Promise.resolve(fs.readSync(fd, buffer, offset, length, position));
|
|
1332
|
+
}
|
|
1333
|
+
},
|
|
1334
|
+
write(fd, buffer, offset, length, position, callback) {
|
|
1335
|
+
if (typeof offset === "function") {
|
|
1336
|
+
callback = offset;
|
|
1337
|
+
offset = undefined;
|
|
1338
|
+
length = undefined;
|
|
1339
|
+
position = undefined;
|
|
1340
|
+
}
|
|
1341
|
+
else if (typeof length === "function") {
|
|
1342
|
+
callback = length;
|
|
1343
|
+
length = undefined;
|
|
1344
|
+
position = undefined;
|
|
1345
|
+
}
|
|
1346
|
+
else if (typeof position === "function") {
|
|
1347
|
+
callback = position;
|
|
1348
|
+
position = undefined;
|
|
1349
|
+
}
|
|
1350
|
+
if (callback) {
|
|
1351
|
+
// Defer callback to next tick to allow event loop to process stream events
|
|
1352
|
+
const cb = callback;
|
|
1353
|
+
try {
|
|
1354
|
+
const bytesWritten = fs.writeSync(fd, buffer, offset, length, position);
|
|
1355
|
+
queueMicrotask(() => cb(null, bytesWritten));
|
|
1356
|
+
}
|
|
1357
|
+
catch (e) {
|
|
1358
|
+
queueMicrotask(() => cb(e));
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
else {
|
|
1362
|
+
return Promise.resolve(fs.writeSync(fd, buffer, offset, length, position));
|
|
1363
|
+
}
|
|
1364
|
+
},
|
|
1365
|
+
// writev - write multiple buffers to a file descriptor
|
|
1366
|
+
writev(fd, buffers, position, callback) {
|
|
1367
|
+
if (typeof position === "function") {
|
|
1368
|
+
callback = position;
|
|
1369
|
+
position = null;
|
|
1370
|
+
}
|
|
1371
|
+
if (callback) {
|
|
1372
|
+
try {
|
|
1373
|
+
const bytesWritten = fs.writevSync(fd, buffers, position);
|
|
1374
|
+
callback(null, bytesWritten, buffers);
|
|
1375
|
+
}
|
|
1376
|
+
catch (e) {
|
|
1377
|
+
callback(e);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
},
|
|
1381
|
+
writevSync(fd, buffers, position) {
|
|
1382
|
+
let totalBytesWritten = 0;
|
|
1383
|
+
for (const buffer of buffers) {
|
|
1384
|
+
const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
1385
|
+
totalBytesWritten += fs.writeSync(fd, bytes, 0, bytes.length, position);
|
|
1386
|
+
if (position !== null && position !== undefined) {
|
|
1387
|
+
position += bytes.length;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
return totalBytesWritten;
|
|
1391
|
+
},
|
|
1392
|
+
fstat(fd, callback) {
|
|
1393
|
+
if (callback) {
|
|
1394
|
+
try {
|
|
1395
|
+
callback(null, fs.fstatSync(fd));
|
|
1396
|
+
}
|
|
1397
|
+
catch (e) {
|
|
1398
|
+
callback(e);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
else {
|
|
1402
|
+
return Promise.resolve(fs.fstatSync(fd));
|
|
1403
|
+
}
|
|
1404
|
+
},
|
|
1405
|
+
// fs.promises API
|
|
1406
|
+
// Note: Using async functions to properly catch sync errors and return rejected promises
|
|
1407
|
+
promises: {
|
|
1408
|
+
async readFile(path, options) {
|
|
1409
|
+
return fs.readFileSync(path, options);
|
|
1410
|
+
},
|
|
1411
|
+
async writeFile(path, data, options) {
|
|
1412
|
+
return fs.writeFileSync(path, data, options);
|
|
1413
|
+
},
|
|
1414
|
+
async appendFile(path, data, options) {
|
|
1415
|
+
return fs.appendFileSync(path, data, options);
|
|
1416
|
+
},
|
|
1417
|
+
async readdir(path, options) {
|
|
1418
|
+
return fs.readdirSync(path, options);
|
|
1419
|
+
},
|
|
1420
|
+
async mkdir(path, options) {
|
|
1421
|
+
return fs.mkdirSync(path, options);
|
|
1422
|
+
},
|
|
1423
|
+
async rmdir(path) {
|
|
1424
|
+
return fs.rmdirSync(path);
|
|
1425
|
+
},
|
|
1426
|
+
async stat(path) {
|
|
1427
|
+
return fs.statSync(path);
|
|
1428
|
+
},
|
|
1429
|
+
async lstat(path) {
|
|
1430
|
+
return fs.lstatSync(path);
|
|
1431
|
+
},
|
|
1432
|
+
async unlink(path) {
|
|
1433
|
+
return fs.unlinkSync(path);
|
|
1434
|
+
},
|
|
1435
|
+
async rename(oldPath, newPath) {
|
|
1436
|
+
return fs.renameSync(oldPath, newPath);
|
|
1437
|
+
},
|
|
1438
|
+
async copyFile(src, dest) {
|
|
1439
|
+
return fs.copyFileSync(src, dest);
|
|
1440
|
+
},
|
|
1441
|
+
async access(path) {
|
|
1442
|
+
if (!fs.existsSync(path)) {
|
|
1443
|
+
throw createFsError("ENOENT", `ENOENT: no such file or directory, access '${path}'`, "access", path);
|
|
1444
|
+
}
|
|
1445
|
+
},
|
|
1446
|
+
async rm(path, options) {
|
|
1447
|
+
return fs.rmSync(path, options);
|
|
1448
|
+
},
|
|
1449
|
+
async chmod() {
|
|
1450
|
+
throw new Error("fs.chmod is not supported in sandbox");
|
|
1451
|
+
},
|
|
1452
|
+
async chown() {
|
|
1453
|
+
throw new Error("fs.chown is not supported in sandbox");
|
|
1454
|
+
},
|
|
1455
|
+
async link() {
|
|
1456
|
+
throw new Error("fs.link is not supported in sandbox");
|
|
1457
|
+
},
|
|
1458
|
+
async symlink() {
|
|
1459
|
+
throw new Error("fs.symlink is not supported in sandbox");
|
|
1460
|
+
},
|
|
1461
|
+
async readlink() {
|
|
1462
|
+
throw new Error("fs.readlink is not supported in sandbox");
|
|
1463
|
+
},
|
|
1464
|
+
async truncate() {
|
|
1465
|
+
throw new Error("fs.truncate is not supported in sandbox");
|
|
1466
|
+
},
|
|
1467
|
+
async utimes() {
|
|
1468
|
+
throw new Error("fs.utimes is not supported in sandbox");
|
|
1469
|
+
},
|
|
1470
|
+
},
|
|
1471
|
+
// Compatibility methods
|
|
1472
|
+
accessSync(path) {
|
|
1473
|
+
// existsSync already normalizes the path
|
|
1474
|
+
if (!fs.existsSync(path)) {
|
|
1475
|
+
throw createFsError("ENOENT", `ENOENT: no such file or directory, access '${path}'`, "access", path);
|
|
1476
|
+
}
|
|
1477
|
+
},
|
|
1478
|
+
access(path, mode, callback) {
|
|
1479
|
+
if (typeof mode === "function") {
|
|
1480
|
+
callback = mode;
|
|
1481
|
+
mode = undefined;
|
|
1482
|
+
}
|
|
1483
|
+
if (callback) {
|
|
1484
|
+
try {
|
|
1485
|
+
fs.accessSync(path);
|
|
1486
|
+
callback(null);
|
|
1487
|
+
}
|
|
1488
|
+
catch (e) {
|
|
1489
|
+
callback(e);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
else {
|
|
1493
|
+
return fs.promises.access(path);
|
|
1494
|
+
}
|
|
1495
|
+
},
|
|
1496
|
+
realpathSync: Object.assign(function realpathSync(path) {
|
|
1497
|
+
// In our virtual fs, just normalize the path (keep /data prefix for consistency)
|
|
1498
|
+
return toPathString(path)
|
|
1499
|
+
.replace(/\/\/+/g, "/")
|
|
1500
|
+
.replace(/\/$/, "") || "/";
|
|
1501
|
+
}, {
|
|
1502
|
+
native(path) {
|
|
1503
|
+
return toPathString(path)
|
|
1504
|
+
.replace(/\/\/+/g, "/")
|
|
1505
|
+
.replace(/\/$/, "") || "/";
|
|
1506
|
+
}
|
|
1507
|
+
}),
|
|
1508
|
+
realpath: Object.assign(function realpath(path, callback) {
|
|
1509
|
+
if (callback) {
|
|
1510
|
+
callback(null, fs.realpathSync(path));
|
|
1511
|
+
}
|
|
1512
|
+
else {
|
|
1513
|
+
return Promise.resolve(fs.realpathSync(path));
|
|
1514
|
+
}
|
|
1515
|
+
}, {
|
|
1516
|
+
native(path, callback) {
|
|
1517
|
+
if (callback) {
|
|
1518
|
+
callback(null, fs.realpathSync.native(path));
|
|
1519
|
+
}
|
|
1520
|
+
else {
|
|
1521
|
+
return Promise.resolve(fs.realpathSync.native(path));
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
}),
|
|
1525
|
+
createReadStream(path, options) {
|
|
1526
|
+
const pathStr = typeof path === "string" ? path : path instanceof Buffer ? path.toString() : String(path);
|
|
1527
|
+
const opts = typeof options === "string" ? { encoding: options } : options;
|
|
1528
|
+
// Use type assertion since our ReadStream has all the methods npm needs
|
|
1529
|
+
// but not all the complex overloaded signatures of the full Node.js interface
|
|
1530
|
+
return new ReadStream(pathStr, opts);
|
|
1531
|
+
},
|
|
1532
|
+
createWriteStream(path, options) {
|
|
1533
|
+
const pathStr = typeof path === "string" ? path : path instanceof Buffer ? path.toString() : String(path);
|
|
1534
|
+
const opts = typeof options === "string" ? { encoding: options } : options;
|
|
1535
|
+
// Use type assertion since our WriteStream has all the methods npm needs
|
|
1536
|
+
// but not all the complex overloaded signatures of the full Node.js interface
|
|
1537
|
+
return new WriteStream(pathStr, opts);
|
|
1538
|
+
},
|
|
1539
|
+
// Get unsupported fs APIs as deterministic errors
|
|
1540
|
+
watch(..._args) {
|
|
1541
|
+
throw new Error("fs.watch is not supported in sandbox");
|
|
1542
|
+
},
|
|
1543
|
+
watchFile(..._args) {
|
|
1544
|
+
throw new Error("fs.watchFile is not supported in sandbox");
|
|
1545
|
+
},
|
|
1546
|
+
unwatchFile(..._args) {
|
|
1547
|
+
throw new Error("fs.unwatchFile is not supported in sandbox");
|
|
1548
|
+
},
|
|
1549
|
+
chmod(..._args) {
|
|
1550
|
+
throw new Error("fs.chmod is not supported in sandbox");
|
|
1551
|
+
},
|
|
1552
|
+
chown(..._args) {
|
|
1553
|
+
throw new Error("fs.chown is not supported in sandbox");
|
|
1554
|
+
},
|
|
1555
|
+
link(..._args) {
|
|
1556
|
+
throw new Error("fs.link is not supported in sandbox");
|
|
1557
|
+
},
|
|
1558
|
+
symlink(..._args) {
|
|
1559
|
+
throw new Error("fs.symlink is not supported in sandbox");
|
|
1560
|
+
},
|
|
1561
|
+
readlink(..._args) {
|
|
1562
|
+
throw new Error("fs.readlink is not supported in sandbox");
|
|
1563
|
+
},
|
|
1564
|
+
truncate(..._args) {
|
|
1565
|
+
throw new Error("fs.truncate is not supported in sandbox");
|
|
1566
|
+
},
|
|
1567
|
+
utimes(..._args) {
|
|
1568
|
+
throw new Error("fs.utimes is not supported in sandbox");
|
|
1569
|
+
},
|
|
1570
|
+
};
|
|
1571
|
+
// Export the fs module
|
|
1572
|
+
export default fs;
|