arc-resolver
Advanced tools
Comparing version 1.0.1 to 2.0.0-alpha.0
299
index.js
@@ -1,183 +0,180 @@ | ||
var fs = require('fs'); | ||
var path = require('path'); | ||
var resolve = require('resolve'); | ||
var directoryListings = {}; | ||
var fileMatches = {}; | ||
var configs = {}; | ||
let path = require('path'); | ||
let util = require('util'); | ||
let parseFlags = require('arc-flag-parser').parse; | ||
let flaggedPathRegex = /\[(.*)\]/; | ||
module.exports.adaptResource = adaptResource; | ||
module.exports.joinFlags = joinFlags; | ||
module.exports.loadArcConfig = loadArcConfig; | ||
module.exports.resolveFrom = resolveFrom; | ||
module.exports.getFileMatches = getFileMatches; | ||
module.exports.getBestMatch = getBestMatch; | ||
function getIndexedFlags(flags) { | ||
if (!Array.isArray(flags)) return flags; //assume indexed flagset | ||
if (flags.indexedFlags) return flags.indexedFlags; | ||
var indexedFlags = {}; | ||
for (var i = 0; i < flags.length; i++) { | ||
indexedFlags[flags[i]] = true; | ||
class Resolver { | ||
constructor(fs = require('fs')) { | ||
validateFS(fs); | ||
this.fs = fs; | ||
this.fs.stat = util.promisify(this.fs.stat); | ||
this.fs.readdir = util.promisify(this.fs.readdir); | ||
this.dirCache = {}; | ||
this.matchCache = {}; | ||
} | ||
clearCache() { | ||
this.dirCache = {}; | ||
this.matchCache = {}; | ||
} | ||
getMatchesSync(filepath, path) { | ||
if (typeof filepath !== 'string') { | ||
throw new TypeError('Filepath must be a string.'); | ||
} | ||
Object.defineProperty(flags, 'indexedFlags', { value: indexedFlags }); | ||
return indexedFlags; | ||
} | ||
let matches = this.matchCache[filepath]; | ||
function getDirectoryListing(dirname) { | ||
if (directoryListings[dirname]) { | ||
return directoryListings[dirname]; | ||
} | ||
if (!matches) { | ||
path = path || getPathHelper(filepath); | ||
let parsed = path.parse(filepath); | ||
if (parsed.root === filepath || flaggedPathRegex.test(parsed.base)) { | ||
matches = [{ flags: [], value: filepath }]; | ||
} else { | ||
let parentMatches = this.getMatchesSync(parsed.dir, path).raw; | ||
matches = parentMatches.reduce( | ||
(matches, parent) => { | ||
let childMatches = this.getDirMatchesSync( | ||
parent.value, | ||
parsed.base, | ||
path | ||
); | ||
if (parent.flags.length) { | ||
childMatches = childMatches.map(child => ({ | ||
flags: Array.from(new Set(parent.flags.concat(child.flags))), | ||
value: child.value | ||
})); | ||
} | ||
matches.push.apply(matches, childMatches); | ||
return matches; | ||
}, | ||
[] | ||
); | ||
} | ||
return directoryListings[dirname] = fs.readdirSync(dirname); | ||
} | ||
function loadArcConfig(filepath) { | ||
if (configs[filepath]) { | ||
return configs[filepath]; | ||
matches = this.matchCache[filepath] = new MatchSet(matches); | ||
} | ||
var content = fs.readFileSync(filepath, 'utf8'); | ||
var config = configs[filepath] = JSON.parse(content); | ||
return matches; | ||
} | ||
getDirMatchesSync(dir, request, path) { | ||
let fs = this.fs; | ||
let cache = this.dirCache[dir]; | ||
return config; | ||
} | ||
if (!cache) { | ||
let entries = fs.readdirSync(dir); | ||
function getFileMatches(filepath, extensions) { | ||
if (fileMatches[filepath]) { | ||
return fileMatches[filepath]; | ||
} | ||
cache = {}; | ||
var dirname = path.dirname(filepath); | ||
var filename = path.basename(filepath); | ||
var extStart = filename.lastIndexOf('.'); | ||
var basename = filename.slice(0, extStart); | ||
var extension = filename.slice(extStart + 1); | ||
var files = getDirectoryListing(dirname); | ||
var isIndexAdaptive = filename === 'index.arc'; | ||
var matches = []; | ||
var hasDefault = false; | ||
var defaultName; | ||
var config; | ||
var pattern; | ||
if (isIndexAdaptive) { | ||
pattern = /([\w\d-]+(?:\.[\w\d-]+)*)/; | ||
config = loadArcConfig(filepath); | ||
defaultName = config && config.default || 'default'; | ||
} else { | ||
pattern = new RegExp('^' + basename + '((?:\\.[\\w\\d-]+)*)' + '\\.' + extension + '$'); | ||
} | ||
files.forEach(file => { | ||
var match = pattern.exec(file); | ||
entries.forEach(entryName => { | ||
let entryPath = path.join(dir, entryName); | ||
let match = flaggedPathRegex.exec(entryName); | ||
if (match) { | ||
var fullpath = path.join(dirname, file); | ||
var stat = fs.statSync(fullpath); | ||
var flags = match[1].split('.'); | ||
if (isIndexAdaptive) { | ||
if (!stat.isDirectory()) return; | ||
} else { | ||
if (!stat.isFile()) return; | ||
flags = flags.slice(1); | ||
} | ||
let canonicalName = entryName.replace(match[0], ''); | ||
let entryCache = (cache[canonicalName] = cache[canonicalName] || []); | ||
let flagsets = parseFlags(match[1]); | ||
flagsets.forEach(flags => | ||
entryCache.push({ flags, value: entryPath }) | ||
); | ||
} else { | ||
let entryCache = (cache[entryName] = cache[entryName] || []); | ||
entryCache.push({ flags: [], value: entryPath }); | ||
} | ||
}); | ||
if (file === defaultName) { | ||
flags = []; | ||
} | ||
Object.values(cache).forEach(entryCache => { | ||
entryCache.sort((a, b) => b.flags.length - a.flags.length); | ||
}); | ||
hasDefault = hasDefault || !flags.length; | ||
this.dirCache[dir] = cache; | ||
} | ||
matches.push({ file: fullpath, flags }); | ||
} | ||
}); | ||
let matches = cache[request]; | ||
if (!hasDefault) { | ||
throw new Error('No default found for ' + filepath); | ||
if (!matches) { | ||
throw new Error(path.resolve(dir, request) + ' does not exist'); | ||
} | ||
return fileMatches[filepath] = matches; | ||
} | ||
return matches; | ||
} | ||
resolveSync(filepath, flags) { | ||
if (typeof filepath !== 'string') { | ||
throw new TypeError('Filepath must be a string.'); | ||
} | ||
// Utility function to get directory matches | ||
function getDirMatches(filepath) { | ||
if (fileMatches[filepath]) { | ||
return fileMatches[filepath]; | ||
if (!flags || typeof flags !== 'object') { | ||
throw new TypeError('Flags must be an object.'); | ||
} | ||
var parentDir = path.dirname(filepath); | ||
var basename = path.basename(filepath); | ||
var contents = getDirectoryListing(parentDir); | ||
var matches = []; | ||
return this.getMatchesSync(filepath).match(flags); | ||
} | ||
isAdaptiveSync(filepath) { | ||
if (typeof filepath !== 'string') { | ||
throw new TypeError('Filepath must be a string.'); | ||
} | ||
contents.forEach(dir => { | ||
var fullpath = path.join(parentDir, dir); | ||
// We only want to operate on the directories | ||
var stat = fs.statSync(fullpath); | ||
if (!stat.isDirectory()) return; | ||
return this.getMatchesSync(filepath).count > 1; | ||
} | ||
}; | ||
var flags = dir.split('.'); | ||
if (dir === basename) { | ||
flags = []; | ||
} | ||
matches.push({ file: fullpath, flags }); | ||
}); | ||
return fileMatches[filepath] = matches; | ||
function getPathHelper(filepath) { | ||
if (path.posix.isAbsolute(filepath)) { | ||
return path.posix; | ||
} else if (path.win32.isAbsolute(filepath)) { | ||
return path.win32; | ||
} else { | ||
throw new Error( | ||
'Filepath must be a fully resolved filepath. Got: ' + filepath | ||
); | ||
} | ||
} | ||
function adaptResource(filepath, flags) { | ||
var stat = fs.statSync(filepath); | ||
var matches = []; | ||
function validateFS(fs) { | ||
if (!fs) { | ||
throw new Error( | ||
'You must pass a filesystem to resolve, or undefined to use the node fs module' | ||
); | ||
} | ||
if (stat.isFile()) { | ||
matches = getFileMatches(filepath); | ||
} else if (stat.isDirectory()) { | ||
matches = getDirMatches(filepath); | ||
} | ||
let missing = ['stat', 'statSync', 'readdir', 'readdirSync'].filter( | ||
method => !fs[method] | ||
); | ||
return getBestMatch(matches, flags).file; | ||
if (missing.length) { | ||
throw new Error( | ||
'The passed filesystem is missing the following methods: ' + | ||
missing.join(', ') + | ||
'.' | ||
); | ||
} | ||
} | ||
class MatchSet { | ||
constructor(matches) { | ||
this.raw = matches; | ||
} | ||
get default() { | ||
return this.raw[this.raw.length-1].value; | ||
} | ||
get count() { | ||
return this.raw.length | ||
} | ||
map(fn) { | ||
return new MatchSet(this.raw.map(({ flags, value }, index) => ({ | ||
flags: flags, | ||
value: fn(value, flags, index) | ||
}))); | ||
} | ||
match(flags) { | ||
let match = this.raw.find(match => match.flags.every(flag => flags[flag])); | ||
function resolveFrom(requestingFile, targetFile, options) { | ||
var flags = options.flags; | ||
var extensions = (options.extensions || []).concat('.arc'); | ||
var resolvedFile = resolve.sync(targetFile, { | ||
basedir: path.dirname(requestingFile), | ||
extensions: extensions || ['.js'] | ||
}); | ||
if (getFileMatches(resolvedFile).some(match => match.file === requestingFile)) { | ||
return resolvedFile; | ||
if (!match) { | ||
throw new Error('No match found'); | ||
} | ||
return adaptResource(resolvedFile, flags); | ||
return match.value; | ||
} | ||
[Symbol.iterator]() { | ||
return this.raw[Symbol.iterator](); | ||
} | ||
} | ||
// Alphabetize flags before joining them | ||
function joinFlags(flags) { | ||
flags.sort(); | ||
return flags.join('.'); | ||
} | ||
// Return best matching filepath | ||
function getBestMatch(matches, flags) { | ||
var indexedFlags = getIndexedFlags(flags); | ||
var bestMatchObj = {}; | ||
var bestMatchFile = ''; | ||
matches.sort((a, b) => ( | ||
b.flags.length - a.flags.length | ||
)); | ||
bestMatchObj = matches.find(match => { | ||
return match.flags.every(flag => indexedFlags[flag]); | ||
}); | ||
return bestMatchObj; | ||
} | ||
module.exports = Resolver; | ||
module.exports.MatchSet = MatchSet; |
{ | ||
"name": "arc-resolver", | ||
"version": "1.0.1", | ||
"version": "2.0.0-alpha.0", | ||
"description": "", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "mocha" | ||
"test": "nyc mocha test.js" | ||
}, | ||
"author": "Michael Rawlings <ml.rawlings@gmail.com>", | ||
"license": "ISC", | ||
"license": "MIT", | ||
"dependencies": { | ||
"resolve": "^1.1.7" | ||
"arc-flag-parser": "^2.0.0-alpha.0", | ||
"cachedfs": "^0.3.3" | ||
}, | ||
"devDependencies": { | ||
"chai": "^3.5.0", | ||
"lerna": "^2.0.0-rc.5", | ||
"mocha": "^3.2.0" | ||
} | ||
"memory-fs": "^0.4.1", | ||
"mocha": "^3.2.0", | ||
"nyc": "^11.3.0" | ||
}, | ||
"gitHead": "4772d05c45bc1bfbc988b7ac97a96295b452c650" | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
No README
QualityPackage does not have a README. This may indicate a failed publish or a low quality package.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
1
108
2
7159
2
4
3
156
2
+ Addedcachedfs@^0.3.3
+ Addedarc-flag-parser@2.0.0(transitive)
+ Addedbufferedstream@1.6.0(transitive)
+ Addedcachedfs@0.3.3(transitive)
- Removedresolve@^1.1.7
- Removedfunction-bind@1.1.2(transitive)
- Removedhasown@2.0.2(transitive)
- Removedis-core-module@2.15.1(transitive)
- Removedpath-parse@1.0.7(transitive)
- Removedresolve@1.22.8(transitive)
- Removedsupports-preserve-symlinks-flag@1.0.0(transitive)