pi-bridge 0.3.2 → 0.3.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-bridge",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Transparent filesystem bridge that makes pi run natively on a remote machine over SSH",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -97,6 +97,10 @@ function patchFs(fakeRoot, port, token) {
97
97
  renameSync: fs.renameSync.bind(fs),
98
98
  copyFileSync: fs.copyFileSync.bind(fs),
99
99
  realpathSync: fs.realpathSync.bind(fs),
100
+ mkdtempSync: fs.mkdtempSync.bind(fs),
101
+ rmdirSync: fs.rmdirSync.bind(fs),
102
+ chmodSync: fs.chmodSync.bind(fs),
103
+ cpSync: fs.cpSync ? fs.cpSync.bind(fs) : undefined,
100
104
  createWriteStream: fs.createWriteStream.bind(fs),
101
105
  createReadStream: fs.createReadStream.bind(fs),
102
106
  };
@@ -116,6 +120,11 @@ function patchFs(fakeRoot, port, token) {
116
120
  rename: fs.promises.rename.bind(fs.promises),
117
121
  copyFile: fs.promises.copyFile.bind(fs.promises),
118
122
  open: fs.promises.open.bind(fs.promises),
123
+ mkdtemp: fs.promises.mkdtemp.bind(fs.promises),
124
+ realpath: fs.promises.realpath.bind(fs.promises),
125
+ rmdir: fs.promises.rmdir.bind(fs.promises),
126
+ chmod: fs.promises.chmod.bind(fs.promises),
127
+ cp: fs.promises.cp ? fs.promises.cp.bind(fs.promises) : undefined,
119
128
  };
120
129
 
121
130
  // Resolve any path (relative or absolute) to absolute before checking fake root
@@ -246,6 +255,58 @@ function patchFs(fakeRoot, port, token) {
246
255
  httpPost(port, token, '/mkdir?path=' + encodeURIComponent(rpath(s)), '');
247
256
  };
248
257
 
258
+ /**
259
+ * The remote runs the real mkdtempSync and returns only the generated
260
+ * directory name, so uniqueness keeps Node's semantics and no remote path has
261
+ * to be mapped back to a local one.
262
+ */
263
+ fs.mkdtempSync = function patchedMkdtempSync(prefix, opts) {
264
+ const s = abs(prefix);
265
+ if (!s || !isFakePath(s, fakeRoot)) return orig.mkdtempSync(prefix, opts);
266
+ const name = httpPost(port, token, '/mkdtemp?path=' + encodeURIComponent(rpath(s)), '');
267
+ return path.join(path.dirname(s), String(name).trim());
268
+ };
269
+
270
+ fs.rmdirSync = function patchedRmdirSync(p, opts) {
271
+ const s = abs(p);
272
+ if (!s || !isFakePath(s, fakeRoot)) return orig.rmdirSync(p, opts);
273
+ const recursive = (opts && opts.recursive) ? '1' : '0';
274
+ httpPost(port, token, '/rm?path=' + encodeURIComponent(rpath(s)) + '&recursive=' + recursive, '');
275
+ };
276
+
277
+ fs.chmodSync = function patchedChmodSync(p, mode) {
278
+ const s = abs(p);
279
+ if (!s || !isFakePath(s, fakeRoot)) return orig.chmodSync(p, mode);
280
+ // Octal on the wire: String(0o755) is "493", which the server would have to
281
+ // guess at. toString(8) keeps the intent explicit for numbers and strings.
282
+ const octal = typeof mode === 'number' ? mode.toString(8) : String(mode).replace(/^0o?/i, '');
283
+ httpPost(port, token, '/chmod?path=' + encodeURIComponent(rpath(s)) + '&mode=' + octal, '');
284
+ };
285
+
286
+ // Recursive copy: the remote /copy route handles one file, so directories are
287
+ // walked here through the already-patched calls.
288
+ fs.cpSync = function patchedCpSync(src, dest, opts) {
289
+ const srcS = abs(src);
290
+ const destS = abs(dest);
291
+ const involved = (srcS && isFakePath(srcS, fakeRoot)) || (destS && isFakePath(destS, fakeRoot));
292
+ if (!involved) {
293
+ if (!orig.cpSync) throw new Error('fs.cpSync is not available in this Node version');
294
+ return orig.cpSync(src, dest, opts);
295
+ }
296
+ const stats = fs.statSync(srcS);
297
+ if (!stats.isDirectory()) {
298
+ fs.copyFileSync(srcS, destS);
299
+ return;
300
+ }
301
+ if (!(opts && opts.recursive)) {
302
+ throw Object.assign(new Error('EISDIR: illegal operation on a directory, cp ' + srcS), { code: 'EISDIR' });
303
+ }
304
+ fs.mkdirSync(destS, { recursive: true });
305
+ for (const entry of fs.readdirSync(srcS)) {
306
+ fs.cpSync(path.join(srcS, entry), path.join(destS, entry), opts);
307
+ }
308
+ };
309
+
249
310
  fs.unlinkSync = function patchedUnlinkSync(p) {
250
311
  const s = abs(p);
251
312
  if (!s || !isFakePath(s, fakeRoot)) return orig.unlinkSync(p);
@@ -501,6 +562,56 @@ function patchFs(fakeRoot, port, token) {
501
562
  });
502
563
  };
503
564
 
565
+ fs.promises.rename = async function patchedRename(from, to) {
566
+ const fromS = abs(from);
567
+ const toS = abs(to);
568
+ if (!fromS || !isFakePath(fromS, fakeRoot)) return origP.rename(from, to);
569
+ await new Promise(function(resolve, reject) {
570
+ const req = http.request(
571
+ { method: 'POST', hostname: '127.0.0.1', port: port,
572
+ path: '/rename?from=' + encodeURIComponent(rpath(fromS)) + '&to=' + encodeURIComponent(toS ? rpath(toS) : String(to)),
573
+ headers: { 'x-token': token, 'Content-Length': 0 } },
574
+ function(res) { res.resume(); res.on('end', function() { res.statusCode < 300 ? resolve() : reject(new Error('HTTP ' + res.statusCode)); }); }
575
+ );
576
+ req.on('error', reject); req.end();
577
+ });
578
+ };
579
+
580
+ fs.promises.mkdtemp = async function patchedMkdtemp(prefix, opts) {
581
+ const s = abs(prefix);
582
+ if (!s || !isFakePath(s, fakeRoot)) return origP.mkdtemp(prefix, opts);
583
+ return fs.mkdtempSync(prefix, opts);
584
+ };
585
+
586
+ fs.promises.realpath = async function patchedRealpath(p, opts) {
587
+ const s = abs(p);
588
+ if (!s || !isFakePath(s, fakeRoot)) return origP.realpath(p, opts);
589
+ return s;
590
+ };
591
+
592
+ fs.promises.rmdir = async function patchedRmdir(p, opts) {
593
+ const s = abs(p);
594
+ if (!s || !isFakePath(s, fakeRoot)) return origP.rmdir(p, opts);
595
+ return fs.promises.rm(p, Object.assign({}, opts));
596
+ };
597
+
598
+ fs.promises.chmod = async function patchedChmod(p, mode) {
599
+ const s = abs(p);
600
+ if (!s || !isFakePath(s, fakeRoot)) return origP.chmod(p, mode);
601
+ fs.chmodSync(p, mode);
602
+ };
603
+
604
+ fs.promises.cp = async function patchedCp(src, dest, opts) {
605
+ const srcS = abs(src);
606
+ const destS = abs(dest);
607
+ const involved = (srcS && isFakePath(srcS, fakeRoot)) || (destS && isFakePath(destS, fakeRoot));
608
+ if (!involved) {
609
+ if (!origP.cp) throw new Error('fs.promises.cp is not available in this Node version');
610
+ return origP.cp(src, dest, opts);
611
+ }
612
+ fs.cpSync(src, dest, opts);
613
+ };
614
+
504
615
  // fs.promises.open: return a minimal FileHandle for reading
505
616
  fs.promises.open = async function patchedOpen(p, flags, mode) {
506
617
  const s = typeof p === 'string' ? abs(p) : null;
@@ -651,6 +762,12 @@ function patchFs(fakeRoot, port, token) {
651
762
  fsp.mkdir = fs.promises.mkdir;
652
763
  fsp.rm = fs.promises.rm;
653
764
  fsp.unlink = fs.promises.unlink;
765
+ fsp.rename = fs.promises.rename;
766
+ fsp.mkdtemp = fs.promises.mkdtemp;
767
+ fsp.realpath = fs.promises.realpath;
768
+ fsp.rmdir = fs.promises.rmdir;
769
+ fsp.chmod = fs.promises.chmod;
770
+ fsp.cp = fs.promises.cp;
654
771
  fsp.copyFile = fs.promises.copyFile;
655
772
  fsp.open = fs.promises.open;
656
773
  }
@@ -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');