Skip to content

Commit

Permalink
Merge pull request #45 from rwjblue/refactor-handleOperation
Browse files Browse the repository at this point in the history
Refactors to simplify the FS proxy implementation
  • Loading branch information
thoov authored Apr 29, 2021
2 parents adb7697 + d5ec75d commit 0d9ffdf
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 41 deletions.
111 changes: 72 additions & 39 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import fs = require('fs-extra');
import path = require('path');
import nodefs = require('fs');
import os = require('os');
const broccoliNodeInfo = require('broccoli-node-info');
import {
InputNode
Expand All @@ -19,7 +20,10 @@ import {

import { entries, Options, Entry } from 'walk-sync';

const WHITELISTEDOPERATION = new Set([
type FileSystemOperation = 'readFileSync' | 'existsSync' | 'lstatSync' | 'statSync' | 'readdirSync' | 'readdir';
type FSMergerOperation = 'readFileMeta' | 'entries' | 'at' | 'relativePathTo';

const AllowedOperations = [
'readFileSync',
'existsSync',
'lstatSync',
Expand All @@ -30,7 +34,17 @@ const WHITELISTEDOPERATION = new Set([
'entries',
'at',
'relativePathTo'
]);
];

let NO_MATCH_TMPDIR: string;

function getEmptyTempDir(): string {
if (NO_MATCH_TMPDIR) return NO_MATCH_TMPDIR;

NO_MATCH_TMPDIR = fs.mkdtempSync(path.join(os.tmpdir(), "fs-merger-empty"));

return NO_MATCH_TMPDIR;
}

function getRootAndPrefix(node: any): FSMerger.FSMergerObject {
let root = '';
Expand Down Expand Up @@ -63,43 +77,46 @@ function getValues(object: {[key:string]: any}) {
}
}

function handleOperation(this: FSMerger & {[key: string]: any}, { target, propertyName }: {
target: {[key: string]: any};
propertyName: string;
}, relativePath: string, ...fsArguments: any[]) {
if (!this.MAP) {
this._generateMap();
}
let fullPath = relativePath
let byPassAbsPathCheck = false;
switch (propertyName) {
case 'relativePathTo':
case 'at':
byPassAbsPathCheck = true;
break;

case 'entries':
if (!relativePath) {
byPassAbsPathCheck = true;
}
break;
function handleFSOperation(
merger: FSMerger & {[key: string]: any},
target: { [key: string]: any },
operation: FileSystemOperation,
relativePath: string,
fsArguments: any[]
) {
if (!merger.MAP) {
merger._generateMap();
}

if (byPassAbsPathCheck || !path.isAbsolute(relativePath)) {
// if property is present in the FSMerge do not hijack it with fs operations
if (this[propertyName]) {
return this[propertyName](relativePath, ...fsArguments);
}
let { _dirList } = this;
let fullPath: string | undefined;

if (path.isAbsolute(relativePath)) {
fullPath = relativePath;
} else {
let { _dirList } = merger;
for (let i=_dirList.length-1; i > -1; i--) {
let { root } = this.PREFIXINDEXMAP[i];
let tempPath = root + '/' + relativePath;
let { root } = merger.PREFIXINDEXMAP[i];
let tempPath = path.join(root, relativePath);

fullPath = tempPath;

if(fs.existsSync(tempPath)) {
fullPath = tempPath;
break;
}
}

// if there are no directories to be searched at all, fullPath will not be populated
// populate it with a fake directory that we **know** is empty
if (fullPath === undefined) {
fullPath = path.join(getEmptyTempDir(), relativePath);
}
}
return target[propertyName](fullPath, ...fsArguments);

return target[operation](fullPath, ...fsArguments);
}

function invalidFSOperation(operation: never): never {
throw new Error(`Operation ${operation} is not allowed with FSMerger.fs. Allowed operations are ${AllowedOperations}`);
}

class FSMerger {
Expand All @@ -116,13 +133,29 @@ class FSMerger {
this.PREFIXINDEXMAP = {};
this.LIST = [];
this._atList = [];
let self: FSMerger & {[key: string]: any} = this;

const merger: FSMerger & {[key: string]: any} = this;
this.fs = <any>new Proxy(nodefs, {
get(target, propertyName: string) {
if(WHITELISTEDOPERATION.has(propertyName)) {
return handleOperation.bind(self, {target, propertyName})
} else {
throw new Error(`Operation ${propertyName} is not allowed with FSMerger.fs. Allowed operations are ${Array.from(WHITELISTEDOPERATION).toString()}`);
get(target, operation: FileSystemOperation & FSMergerOperation) {
switch (operation) {
case 'existsSync':
case 'lstatSync':
case 'statSync':
return function(relativePath: string, ...args: any[]) {
return handleFSOperation(merger, target, operation, relativePath, args)
};
case 'readFileSync':
case 'readdirSync':
case 'readdir':
case 'readFileMeta':
case 'entries':
case 'at':
case 'relativePathTo':
return function(relativePath: string, ...args: any[]) {
return merger[operation](relativePath, ...args);
};
default:
invalidFSOperation(operation);
}
}
});
Expand Down Expand Up @@ -356,4 +389,4 @@ namespace FSMerger {
}
export type Node = FSMergerObject | InputNode;

}
}
39 changes: 37 additions & 2 deletions tests/unit-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ const fixturify = require('fixturify');
const rm = require('rimraf').sync;
const path = require('path');
const fs = require('fs');
const os = require('os');
const { WatchedDir, UnwatchedDir } = require('broccoli-source');

function buildTmpDir() {
return fs.mkdtempSync(`${os.tmpdir()}${path.sep}`);
}

describe('fs-reader', function () {
before(function() {
fixturify.writeSync('fixtures', {
Expand Down Expand Up @@ -69,6 +74,27 @@ describe('fs-reader', function () {
fsMerger.fs.readFileSync('test-1/x.txt', 'utf-8')
}).throw(/ENOENT\: no such file or directory, open.*/);
});

it('last node "wins" (existsSync, lstatSync, statSync)', function() {
let tmpdir = buildTmpDir();
fixturify.writeSync(tmpdir, {
"test-1": {
},
"test-2": {
},
"test-3": {
"shared.txt": "test-3/shared.txt",
},
});

let fsMerger = new FSMerge([
`${tmpdir}/test-1`,
`${tmpdir}/test-2`,
`${tmpdir}/test-3`,
]);

expect(fsMerger.fs.existsSync("shared.txt")).to.be.true;
});
});

describe('Reads file meta details', function() {
Expand Down Expand Up @@ -224,13 +250,22 @@ describe('fs-reader', function () {
});

describe('Verify few fs operations', function() {
let fsMerger = new FSMerge(['fixtures/test-1', 'fixtures/test-2', 'fixtures/test-3']);
let fsMerger;

beforeEach(function() {
fsMerger = new FSMerge(['fixtures/test-1', 'fixtures/test-2', 'fixtures/test-3']);
});

it('existsSync works', function() {
let content = fsMerger.fs.existsSync('test-1');
expect(content).to.be.true;
});

it('existsSync does not bleed through to process.cwd() when the path is missing', function() {
let content = fsMerger.fs.existsSync('src');
expect(content).to.be.false;
});

it('absolute path is accepted', function() {
let filepath = `${__dirname}/../fixtures/test-1`;
expect(fsMerger.fs.existsSync(filepath)).to.be.true;
Expand Down Expand Up @@ -398,4 +433,4 @@ describe('fs-reader', function () {
}).to.throw('relativePathTo expects an absolute path: fixtures/test-3/b.txt');
});
});
});
});

0 comments on commit 0d9ffdf

Please sign in to comment.