138 lines
3.7 KiB
JavaScript
138 lines
3.7 KiB
JavaScript
const { resolve, join, sep } = require('path');
|
|
const fs = require('fs');
|
|
|
|
// Detect existing gears
|
|
const gearPaths = [
|
|
join(__dirname, '../../gears'),
|
|
join(__dirname, '../../private_gears')
|
|
]
|
|
.filter(gearPath => fs.existsSync(gearPath))
|
|
.reduce((gears, gearsDir) => {
|
|
fs.readdirSync(gearsDir).forEach((gearName) => { gears[gearName] = resolve(gearsDir, gearName); });
|
|
return gears;
|
|
}, {})
|
|
;
|
|
|
|
const fileCache = {};
|
|
const gearResolved = new Set();
|
|
const ROOT_DIR = resolve(__dirname, '../../');
|
|
|
|
function rootDir (file, opts = {}) {
|
|
if (fileCache[file] === undefined || opts.skipCache) {
|
|
// Try to find the file in gears
|
|
for (let [gearName, gearPath] of Object.entries(gearPaths)) {
|
|
let fileGearPath = join(gearPath, file);
|
|
let fileExistsAndBoundedToGear =
|
|
fs.existsSync(fileGearPath) &&
|
|
fs.lstatSync(fileGearPath).isFile() &&
|
|
// Check that the file is not from outside the gear path
|
|
// PE, using ../..
|
|
resolve(fileGearPath).startsWith(gearPath)
|
|
;
|
|
|
|
// Try to look for full folder overriding, preceded by @
|
|
if (!fileExistsAndBoundedToGear) {
|
|
let pathParts = file.split(sep);
|
|
|
|
pathParts.some((pathPart, pathPartIdx) => {
|
|
fileGearPath = pathParts.slice(0, pathPartIdx).join(sep);
|
|
|
|
if (fileGearPath) fileGearPath += sep;
|
|
|
|
fileGearPath +=
|
|
'@' + pathPart + sep +
|
|
pathParts.slice(pathPartIdx + 1).join(sep)
|
|
;
|
|
|
|
fileGearPath = join(gearPath, fileGearPath);
|
|
|
|
fileExistsAndBoundedToGear =
|
|
fs.existsSync(fileGearPath) &&
|
|
fs.lstatSync(fileGearPath).isDirectory() &&
|
|
// Check that the file is not from outside the gear path
|
|
// PE, using ../..
|
|
resolve(fileGearPath).startsWith(gearPath)
|
|
;
|
|
|
|
return fileExistsAndBoundedToGear;
|
|
});
|
|
}
|
|
|
|
if (fileExistsAndBoundedToGear) {
|
|
fileCache[file] = {
|
|
path: fileGearPath,
|
|
gear: gearName
|
|
};
|
|
gearResolved.add(fileCache[file].path);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Find the file in project path if not in gear
|
|
if (fileCache[file] === undefined) {
|
|
fileCache[file] = {
|
|
path: resolve(ROOT_DIR, file),
|
|
gear: null
|
|
};
|
|
gearResolved.add(fileCache[file].path);
|
|
}
|
|
}
|
|
|
|
file = fileCache[file];
|
|
|
|
if (!opts.includeGear) {
|
|
file = file.path;
|
|
}
|
|
|
|
return file;
|
|
}
|
|
|
|
// See https://github.com/webpack/webpack/blob/8a7597aa6eb2eef66a8f9db3a0c49bcb96022a94/lib/NormalModuleReplacementPlugin.js
|
|
// See https://webpack.js.org/plugins/normal-module-replacement-plugin/
|
|
class GearResolverPlugin {
|
|
_resolve (result, attrName) {
|
|
if (!result) return;
|
|
|
|
if (/^!/.test(result[attrName])) return result;
|
|
|
|
let path = result[attrName].startsWith('/')
|
|
? result[attrName]
|
|
: join(result.context, result[attrName])
|
|
;
|
|
|
|
let relPath = result.context
|
|
? path.replace(new RegExp('^' + ROOT_DIR + '/?'), '')
|
|
: result[attrName]
|
|
;
|
|
|
|
if (gearResolved.has(relPath)) {
|
|
return result;
|
|
} else {
|
|
const file = rootDir(relPath, { includeGear: true });
|
|
|
|
if (file.gear !== null) { // The file has been overridden by a gear
|
|
result[attrName] = file.path;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
apply (compiler) {
|
|
compiler.hooks.normalModuleFactory.tap(
|
|
'GearResolverPlugin',
|
|
nmf => {
|
|
nmf.hooks.afterResolve.tap('NormalModuleReplacementPlugin', result => {
|
|
if (!result) return;
|
|
return this._resolve(result, 'resource');
|
|
});
|
|
})
|
|
;
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
rootDir: rootDir,
|
|
GearResolverPlugin: GearResolverPlugin
|
|
};
|