pi-bridge 0.3.2 → 0.3.4
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/package.json +1 -1
- package/src/local/fs-patch.js +153 -20
- package/src/remote/handlers.js +26 -0
package/package.json
CHANGED
package/src/local/fs-patch.js
CHANGED
|
@@ -4,6 +4,7 @@ const fs = require('fs');
|
|
|
4
4
|
const fsp = require('fs/promises');
|
|
5
5
|
const http = require('http');
|
|
6
6
|
const path = require('path');
|
|
7
|
+
const { fileURLToPath } = require('url');
|
|
7
8
|
const { isFakePath, toRemotePath } = require('./path-mapper');
|
|
8
9
|
const { httpGet, httpPost } = require('./http-client');
|
|
9
10
|
|
|
@@ -97,6 +98,10 @@ function patchFs(fakeRoot, port, token) {
|
|
|
97
98
|
renameSync: fs.renameSync.bind(fs),
|
|
98
99
|
copyFileSync: fs.copyFileSync.bind(fs),
|
|
99
100
|
realpathSync: fs.realpathSync.bind(fs),
|
|
101
|
+
mkdtempSync: fs.mkdtempSync.bind(fs),
|
|
102
|
+
rmdirSync: fs.rmdirSync.bind(fs),
|
|
103
|
+
chmodSync: fs.chmodSync.bind(fs),
|
|
104
|
+
cpSync: fs.cpSync ? fs.cpSync.bind(fs) : undefined,
|
|
100
105
|
createWriteStream: fs.createWriteStream.bind(fs),
|
|
101
106
|
createReadStream: fs.createReadStream.bind(fs),
|
|
102
107
|
};
|
|
@@ -116,11 +121,35 @@ function patchFs(fakeRoot, port, token) {
|
|
|
116
121
|
rename: fs.promises.rename.bind(fs.promises),
|
|
117
122
|
copyFile: fs.promises.copyFile.bind(fs.promises),
|
|
118
123
|
open: fs.promises.open.bind(fs.promises),
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
124
|
+
mkdtemp: fs.promises.mkdtemp.bind(fs.promises),
|
|
125
|
+
realpath: fs.promises.realpath.bind(fs.promises),
|
|
126
|
+
rmdir: fs.promises.rmdir.bind(fs.promises),
|
|
127
|
+
chmod: fs.promises.chmod.bind(fs.promises),
|
|
128
|
+
cp: fs.promises.cp ? fs.promises.cp.bind(fs.promises) : undefined,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// Resolve any path (relative or absolute) to absolute before checking fake root.
|
|
132
|
+
// file: URLs are accepted too: jiti's bundled ESM resolver probes package
|
|
133
|
+
// mains with fs.statSync(new URL(...)), which must reach the remote.
|
|
134
|
+
// Buffer paths deliberately pass through: Node 20's JS rmSync walks children
|
|
135
|
+
// as Buffers via the public fs.*, and routing those would make removeFakeDir
|
|
136
|
+
// list and delete files on the remote.
|
|
122
137
|
function abs(p) {
|
|
123
|
-
|
|
138
|
+
if (typeof p === 'string') return path.resolve(p);
|
|
139
|
+
if (p instanceof URL) return p.protocol === 'file:' ? path.resolve(fileURLToPath(p)) : null;
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Shared by statSync/lstatSync. Honours { throwIfNoEntry: false }, which the
|
|
144
|
+
// ESM resolver relies on to probe candidate files without try/catch.
|
|
145
|
+
function statRemoteSync(s, opts) {
|
|
146
|
+
try {
|
|
147
|
+
return makeStats(JSON.parse(httpGet(port, token, '/stat?path=' + encodeURIComponent(rpath(s)))));
|
|
148
|
+
} catch (e) {
|
|
149
|
+
const missing = e && e.message && (e.message.includes('ENOENT') || e.message.includes('403'));
|
|
150
|
+
if (missing && opts && opts.throwIfNoEntry === false) return undefined;
|
|
151
|
+
rethrowRemote(e, s);
|
|
152
|
+
}
|
|
124
153
|
}
|
|
125
154
|
|
|
126
155
|
function rpath(fakePath) {
|
|
@@ -220,17 +249,13 @@ function patchFs(fakeRoot, port, token) {
|
|
|
220
249
|
fs.statSync = function patchedStatSync(p, opts) {
|
|
221
250
|
const s = abs(p);
|
|
222
251
|
if (!s || !isFakePath(s, fakeRoot)) return orig.statSync(p, opts);
|
|
223
|
-
|
|
224
|
-
return makeStats(JSON.parse(httpGet(port, token, '/stat?path=' + encodeURIComponent(rpath(s)))));
|
|
225
|
-
} catch (e) { rethrowRemote(e, s); }
|
|
252
|
+
return statRemoteSync(s, opts);
|
|
226
253
|
};
|
|
227
254
|
|
|
228
255
|
fs.lstatSync = function patchedLstatSync(p, opts) {
|
|
229
256
|
const s = abs(p);
|
|
230
257
|
if (!s || !isFakePath(s, fakeRoot)) return orig.lstatSync(p, opts);
|
|
231
|
-
|
|
232
|
-
return makeStats(JSON.parse(httpGet(port, token, '/stat?path=' + encodeURIComponent(rpath(s)))));
|
|
233
|
-
} catch (e) { rethrowRemote(e, s); }
|
|
258
|
+
return statRemoteSync(s, opts);
|
|
234
259
|
};
|
|
235
260
|
|
|
236
261
|
fs.writeFileSync = function patchedWriteFileSync(p, data, opts) {
|
|
@@ -246,6 +271,58 @@ function patchFs(fakeRoot, port, token) {
|
|
|
246
271
|
httpPost(port, token, '/mkdir?path=' + encodeURIComponent(rpath(s)), '');
|
|
247
272
|
};
|
|
248
273
|
|
|
274
|
+
/**
|
|
275
|
+
* The remote runs the real mkdtempSync and returns only the generated
|
|
276
|
+
* directory name, so uniqueness keeps Node's semantics and no remote path has
|
|
277
|
+
* to be mapped back to a local one.
|
|
278
|
+
*/
|
|
279
|
+
fs.mkdtempSync = function patchedMkdtempSync(prefix, opts) {
|
|
280
|
+
const s = abs(prefix);
|
|
281
|
+
if (!s || !isFakePath(s, fakeRoot)) return orig.mkdtempSync(prefix, opts);
|
|
282
|
+
const name = httpPost(port, token, '/mkdtemp?path=' + encodeURIComponent(rpath(s)), '');
|
|
283
|
+
return path.join(path.dirname(s), String(name).trim());
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
fs.rmdirSync = function patchedRmdirSync(p, opts) {
|
|
287
|
+
const s = abs(p);
|
|
288
|
+
if (!s || !isFakePath(s, fakeRoot)) return orig.rmdirSync(p, opts);
|
|
289
|
+
const recursive = (opts && opts.recursive) ? '1' : '0';
|
|
290
|
+
httpPost(port, token, '/rm?path=' + encodeURIComponent(rpath(s)) + '&recursive=' + recursive, '');
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
fs.chmodSync = function patchedChmodSync(p, mode) {
|
|
294
|
+
const s = abs(p);
|
|
295
|
+
if (!s || !isFakePath(s, fakeRoot)) return orig.chmodSync(p, mode);
|
|
296
|
+
// Octal on the wire: String(0o755) is "493", which the server would have to
|
|
297
|
+
// guess at. toString(8) keeps the intent explicit for numbers and strings.
|
|
298
|
+
const octal = typeof mode === 'number' ? mode.toString(8) : String(mode).replace(/^0o?/i, '');
|
|
299
|
+
httpPost(port, token, '/chmod?path=' + encodeURIComponent(rpath(s)) + '&mode=' + octal, '');
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// Recursive copy: the remote /copy route handles one file, so directories are
|
|
303
|
+
// walked here through the already-patched calls.
|
|
304
|
+
fs.cpSync = function patchedCpSync(src, dest, opts) {
|
|
305
|
+
const srcS = abs(src);
|
|
306
|
+
const destS = abs(dest);
|
|
307
|
+
const involved = (srcS && isFakePath(srcS, fakeRoot)) || (destS && isFakePath(destS, fakeRoot));
|
|
308
|
+
if (!involved) {
|
|
309
|
+
if (!orig.cpSync) throw new Error('fs.cpSync is not available in this Node version');
|
|
310
|
+
return orig.cpSync(src, dest, opts);
|
|
311
|
+
}
|
|
312
|
+
const stats = fs.statSync(srcS);
|
|
313
|
+
if (!stats.isDirectory()) {
|
|
314
|
+
fs.copyFileSync(srcS, destS);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
if (!(opts && opts.recursive)) {
|
|
318
|
+
throw Object.assign(new Error('EISDIR: illegal operation on a directory, cp ' + srcS), { code: 'EISDIR' });
|
|
319
|
+
}
|
|
320
|
+
fs.mkdirSync(destS, { recursive: true });
|
|
321
|
+
for (const entry of fs.readdirSync(srcS)) {
|
|
322
|
+
fs.cpSync(path.join(srcS, entry), path.join(destS, entry), opts);
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
|
|
249
326
|
fs.unlinkSync = function patchedUnlinkSync(p) {
|
|
250
327
|
const s = abs(p);
|
|
251
328
|
if (!s || !isFakePath(s, fakeRoot)) return orig.unlinkSync(p);
|
|
@@ -298,7 +375,7 @@ function patchFs(fakeRoot, port, token) {
|
|
|
298
375
|
|
|
299
376
|
// createReadStream: read entire file from remote, pipe as stream
|
|
300
377
|
fs.createReadStream = function patchedCreateReadStream(p, opts) {
|
|
301
|
-
const s =
|
|
378
|
+
const s = abs(p);
|
|
302
379
|
if (!s || !isFakePath(s, fakeRoot)) return orig.createReadStream(p, opts);
|
|
303
380
|
const { Readable } = require('stream');
|
|
304
381
|
const stream = new Readable({ read() {} });
|
|
@@ -311,7 +388,7 @@ function patchFs(fakeRoot, port, token) {
|
|
|
311
388
|
|
|
312
389
|
// createWriteStream: buffer writes, POST on finish
|
|
313
390
|
fs.createWriteStream = function patchedCreateWriteStream(p, opts) {
|
|
314
|
-
const s =
|
|
391
|
+
const s = abs(p);
|
|
315
392
|
if (!s || !isFakePath(s, fakeRoot)) return orig.createWriteStream(p, opts);
|
|
316
393
|
const { Writable } = require('stream');
|
|
317
394
|
const chunks = [];
|
|
@@ -341,7 +418,7 @@ function patchFs(fakeRoot, port, token) {
|
|
|
341
418
|
};
|
|
342
419
|
|
|
343
420
|
fs.promises.readFile = async function patchedReadFile(p, opts) {
|
|
344
|
-
const s =
|
|
421
|
+
const s = abs(p);
|
|
345
422
|
if (!s || !isFakePath(s, fakeRoot)) return origP.readFile(p, opts);
|
|
346
423
|
try {
|
|
347
424
|
const body = await httpGetAsync(port, token, '/read?path=' + encodeURIComponent(rpath(s)));
|
|
@@ -355,7 +432,7 @@ function patchFs(fakeRoot, port, token) {
|
|
|
355
432
|
};
|
|
356
433
|
|
|
357
434
|
fs.promises.readdir = async function patchedReaddir(p, opts) {
|
|
358
|
-
const s =
|
|
435
|
+
const s = abs(p) || p.toString();
|
|
359
436
|
if (!isFakePath(s, fakeRoot)) return origP.readdir(p, opts);
|
|
360
437
|
try {
|
|
361
438
|
const withFileTypes = opts && opts.withFileTypes;
|
|
@@ -404,7 +481,7 @@ function patchFs(fakeRoot, port, token) {
|
|
|
404
481
|
};
|
|
405
482
|
|
|
406
483
|
fs.promises.writeFile = async function patchedWriteFile(p, data, opts) {
|
|
407
|
-
const s =
|
|
484
|
+
const s = abs(p);
|
|
408
485
|
if (!s || !isFakePath(s, fakeRoot)) return origP.writeFile(p, data, opts);
|
|
409
486
|
const content = Buffer.isBuffer(data) ? data.toString('utf8') : String(data);
|
|
410
487
|
await new Promise(function(resolve, reject) {
|
|
@@ -456,7 +533,7 @@ function patchFs(fakeRoot, port, token) {
|
|
|
456
533
|
};
|
|
457
534
|
|
|
458
535
|
fs.promises.appendFile = async function patchedAppendFile(p, data, opts) {
|
|
459
|
-
const s =
|
|
536
|
+
const s = abs(p);
|
|
460
537
|
if (!s || !isFakePath(s, fakeRoot)) return origP.appendFile(p, data, opts);
|
|
461
538
|
const content = Buffer.isBuffer(data) ? data.toString('utf8') : String(data);
|
|
462
539
|
const buf = Buffer.from(content, 'utf8');
|
|
@@ -501,9 +578,59 @@ function patchFs(fakeRoot, port, token) {
|
|
|
501
578
|
});
|
|
502
579
|
};
|
|
503
580
|
|
|
581
|
+
fs.promises.rename = async function patchedRename(from, to) {
|
|
582
|
+
const fromS = abs(from);
|
|
583
|
+
const toS = abs(to);
|
|
584
|
+
if (!fromS || !isFakePath(fromS, fakeRoot)) return origP.rename(from, to);
|
|
585
|
+
await new Promise(function(resolve, reject) {
|
|
586
|
+
const req = http.request(
|
|
587
|
+
{ method: 'POST', hostname: '127.0.0.1', port: port,
|
|
588
|
+
path: '/rename?from=' + encodeURIComponent(rpath(fromS)) + '&to=' + encodeURIComponent(toS ? rpath(toS) : String(to)),
|
|
589
|
+
headers: { 'x-token': token, 'Content-Length': 0 } },
|
|
590
|
+
function(res) { res.resume(); res.on('end', function() { res.statusCode < 300 ? resolve() : reject(new Error('HTTP ' + res.statusCode)); }); }
|
|
591
|
+
);
|
|
592
|
+
req.on('error', reject); req.end();
|
|
593
|
+
});
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
fs.promises.mkdtemp = async function patchedMkdtemp(prefix, opts) {
|
|
597
|
+
const s = abs(prefix);
|
|
598
|
+
if (!s || !isFakePath(s, fakeRoot)) return origP.mkdtemp(prefix, opts);
|
|
599
|
+
return fs.mkdtempSync(prefix, opts);
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
fs.promises.realpath = async function patchedRealpath(p, opts) {
|
|
603
|
+
const s = abs(p);
|
|
604
|
+
if (!s || !isFakePath(s, fakeRoot)) return origP.realpath(p, opts);
|
|
605
|
+
return s;
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
fs.promises.rmdir = async function patchedRmdir(p, opts) {
|
|
609
|
+
const s = abs(p);
|
|
610
|
+
if (!s || !isFakePath(s, fakeRoot)) return origP.rmdir(p, opts);
|
|
611
|
+
return fs.promises.rm(p, Object.assign({}, opts));
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
fs.promises.chmod = async function patchedChmod(p, mode) {
|
|
615
|
+
const s = abs(p);
|
|
616
|
+
if (!s || !isFakePath(s, fakeRoot)) return origP.chmod(p, mode);
|
|
617
|
+
fs.chmodSync(p, mode);
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
fs.promises.cp = async function patchedCp(src, dest, opts) {
|
|
621
|
+
const srcS = abs(src);
|
|
622
|
+
const destS = abs(dest);
|
|
623
|
+
const involved = (srcS && isFakePath(srcS, fakeRoot)) || (destS && isFakePath(destS, fakeRoot));
|
|
624
|
+
if (!involved) {
|
|
625
|
+
if (!origP.cp) throw new Error('fs.promises.cp is not available in this Node version');
|
|
626
|
+
return origP.cp(src, dest, opts);
|
|
627
|
+
}
|
|
628
|
+
fs.cpSync(src, dest, opts);
|
|
629
|
+
};
|
|
630
|
+
|
|
504
631
|
// fs.promises.open: return a minimal FileHandle for reading
|
|
505
632
|
fs.promises.open = async function patchedOpen(p, flags, mode) {
|
|
506
|
-
const s =
|
|
633
|
+
const s = abs(p);
|
|
507
634
|
if (!s || !isFakePath(s, fakeRoot)) return origP.open(p, flags, mode);
|
|
508
635
|
// Fetch the file content eagerly; return a minimal FileHandle-like object
|
|
509
636
|
const content = await httpGetAsync(port, token, '/read?path=' + encodeURIComponent(rpath(s)));
|
|
@@ -535,7 +662,7 @@ function patchFs(fakeRoot, port, token) {
|
|
|
535
662
|
const watchFilePollers = new Map();
|
|
536
663
|
|
|
537
664
|
fs.watch = function patchedWatch(filename, opts, listener) {
|
|
538
|
-
const s = abs(
|
|
665
|
+
const s = abs(filename);
|
|
539
666
|
if (!s || !isFakePath(s, fakeRoot)) return origWatch(filename, opts, listener);
|
|
540
667
|
|
|
541
668
|
if (typeof opts === 'function') { listener = opts; opts = {}; }
|
|
@@ -600,7 +727,7 @@ function patchFs(fakeRoot, port, token) {
|
|
|
600
727
|
};
|
|
601
728
|
|
|
602
729
|
fs.watchFile = function patchedWatchFile(filename, opts, listener) {
|
|
603
|
-
const s = abs(
|
|
730
|
+
const s = abs(filename);
|
|
604
731
|
if (!s || !isFakePath(s, fakeRoot)) {
|
|
605
732
|
return origWatchFile ? origWatchFile(filename, opts, listener) : undefined;
|
|
606
733
|
}
|
|
@@ -630,7 +757,7 @@ function patchFs(fakeRoot, port, token) {
|
|
|
630
757
|
};
|
|
631
758
|
|
|
632
759
|
fs.unwatchFile = function patchedUnwatchFile(filename, listener) {
|
|
633
|
-
const s = abs(
|
|
760
|
+
const s = abs(filename);
|
|
634
761
|
if (!s || !isFakePath(s, fakeRoot)) {
|
|
635
762
|
return origUnwatchFile ? origUnwatchFile(filename, listener) : undefined;
|
|
636
763
|
}
|
|
@@ -651,6 +778,12 @@ function patchFs(fakeRoot, port, token) {
|
|
|
651
778
|
fsp.mkdir = fs.promises.mkdir;
|
|
652
779
|
fsp.rm = fs.promises.rm;
|
|
653
780
|
fsp.unlink = fs.promises.unlink;
|
|
781
|
+
fsp.rename = fs.promises.rename;
|
|
782
|
+
fsp.mkdtemp = fs.promises.mkdtemp;
|
|
783
|
+
fsp.realpath = fs.promises.realpath;
|
|
784
|
+
fsp.rmdir = fs.promises.rmdir;
|
|
785
|
+
fsp.chmod = fs.promises.chmod;
|
|
786
|
+
fsp.cp = fs.promises.cp;
|
|
654
787
|
fsp.copyFile = fs.promises.copyFile;
|
|
655
788
|
fsp.open = fs.promises.open;
|
|
656
789
|
}
|
package/src/remote/handlers.js
CHANGED
|
@@ -123,6 +123,32 @@ function createHandlers() {
|
|
|
123
123
|
return;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
if (req.method === 'POST' && pathname === '/mkdtemp') {
|
|
127
|
+
const prefix = url.searchParams.get('path');
|
|
128
|
+
if (!prefix) return sendError(res, 400, 'Missing path parameter');
|
|
129
|
+
// Real mkdtemp, so uniqueness is the kernel's problem rather than a
|
|
130
|
+
// check-then-create race. Only the generated name is returned; the
|
|
131
|
+
// caller already knows the directory.
|
|
132
|
+
const created = fs.mkdtempSync(prefix);
|
|
133
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
134
|
+
res.end(require('path').basename(created));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (req.method === 'POST' && pathname === '/chmod') {
|
|
139
|
+
const p = url.searchParams.get('path');
|
|
140
|
+
const mode = url.searchParams.get('mode');
|
|
141
|
+
if (!p) return sendError(res, 400, 'Missing path parameter');
|
|
142
|
+
if (!mode) return sendError(res, 400, 'Missing mode parameter');
|
|
143
|
+
// The client always sends octal digits, so the wire value cannot be
|
|
144
|
+
// mistaken for a decimal bitmask.
|
|
145
|
+
if (!/^[0-7]{3,4}$/.test(mode)) return sendError(res, 400, 'Invalid mode parameter');
|
|
146
|
+
fs.chmodSync(p, parseInt(mode, 8));
|
|
147
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
148
|
+
res.end('ok');
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
126
152
|
if (req.method === 'POST' && pathname === '/delete') {
|
|
127
153
|
const p = url.searchParams.get('path');
|
|
128
154
|
if (!p) return sendError(res, 400, 'Missing path parameter');
|