mirror of
https://github.com/atom/atom.git
synced 2026-01-15 01:48:15 -05:00
396 lines
11 KiB
JavaScript
396 lines
11 KiB
JavaScript
const Module = require('module');
|
|
const path = require('path');
|
|
const semver = require('semver');
|
|
|
|
// Extend semver.Range to memoize matched versions for speed
|
|
class Range extends semver.Range {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.matchedVersions = new Set();
|
|
this.unmatchedVersions = new Set();
|
|
}
|
|
|
|
test(version) {
|
|
if (this.matchedVersions.has(version)) return true;
|
|
if (this.unmatchedVersions.has(version)) return false;
|
|
|
|
const matches = super.test(...arguments);
|
|
if (matches) {
|
|
this.matchedVersions.add(version);
|
|
} else {
|
|
this.unmatchedVersions.add(version);
|
|
}
|
|
return matches;
|
|
}
|
|
}
|
|
|
|
let nativeModules = null;
|
|
|
|
const cache = {
|
|
builtins: {},
|
|
debug: false,
|
|
dependencies: {},
|
|
extensions: {},
|
|
folders: {},
|
|
ranges: {},
|
|
registered: false,
|
|
resourcePath: null,
|
|
resourcePathWithTrailingSlash: null
|
|
};
|
|
|
|
// isAbsolute is inlined from fs-plus so that fs-plus itself can be required
|
|
// from this cache.
|
|
let isAbsolute;
|
|
if (process.platform === 'win32') {
|
|
isAbsolute = pathToCheck =>
|
|
pathToCheck &&
|
|
(pathToCheck[1] === ':' ||
|
|
(pathToCheck[0] === '\\' && pathToCheck[1] === '\\'));
|
|
} else {
|
|
isAbsolute = pathToCheck => pathToCheck && pathToCheck[0] === '/';
|
|
}
|
|
|
|
const isCorePath = pathToCheck =>
|
|
pathToCheck.startsWith(cache.resourcePathWithTrailingSlash);
|
|
|
|
function loadDependencies(modulePath, rootPath, rootMetadata, moduleCache) {
|
|
const fs = require('fs-plus');
|
|
|
|
for (let childPath of fs.listSync(path.join(modulePath, 'node_modules'))) {
|
|
if (path.basename(childPath) === '.bin') continue;
|
|
if (
|
|
rootPath === modulePath &&
|
|
(rootMetadata.packageDependencies &&
|
|
rootMetadata.packageDependencies.hasOwnProperty(
|
|
path.basename(childPath)
|
|
))
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
const childMetadataPath = path.join(childPath, 'package.json');
|
|
if (!fs.isFileSync(childMetadataPath)) continue;
|
|
|
|
const childMetadata = JSON.parse(fs.readFileSync(childMetadataPath));
|
|
if (childMetadata && childMetadata.version) {
|
|
var mainPath;
|
|
try {
|
|
mainPath = require.resolve(childPath);
|
|
} catch (error) {
|
|
mainPath = null;
|
|
}
|
|
|
|
if (mainPath) {
|
|
moduleCache.dependencies.push({
|
|
name: childMetadata.name,
|
|
version: childMetadata.version,
|
|
path: path.relative(rootPath, mainPath)
|
|
});
|
|
}
|
|
|
|
loadDependencies(childPath, rootPath, rootMetadata, moduleCache);
|
|
}
|
|
}
|
|
}
|
|
|
|
function loadFolderCompatibility(
|
|
modulePath,
|
|
rootPath,
|
|
rootMetadata,
|
|
moduleCache
|
|
) {
|
|
const fs = require('fs-plus');
|
|
|
|
const metadataPath = path.join(modulePath, 'package.json');
|
|
if (!fs.isFileSync(metadataPath)) return;
|
|
|
|
const metadata = JSON.parse(fs.readFileSync(metadataPath));
|
|
const dependencies = metadata.dependencies || {};
|
|
|
|
for (let name in dependencies) {
|
|
if (!semver.validRange(dependencies[name])) {
|
|
delete dependencies[name];
|
|
}
|
|
}
|
|
|
|
const onDirectory = childPath => path.basename(childPath) !== 'node_modules';
|
|
|
|
const extensions = ['.js', '.coffee', '.json', '.node'];
|
|
let paths = {};
|
|
function onFile(childPath) {
|
|
const needle = path.extname(childPath);
|
|
if (extensions.includes(needle)) {
|
|
const relativePath = path.relative(rootPath, path.dirname(childPath));
|
|
paths[relativePath] = true;
|
|
}
|
|
}
|
|
fs.traverseTreeSync(modulePath, onFile, onDirectory);
|
|
|
|
paths = Object.keys(paths);
|
|
if (paths.length > 0 && Object.keys(dependencies).length > 0) {
|
|
moduleCache.folders.push({ paths, dependencies });
|
|
}
|
|
|
|
for (let childPath of fs.listSync(path.join(modulePath, 'node_modules'))) {
|
|
if (path.basename(childPath) === '.bin') continue;
|
|
if (
|
|
rootPath === modulePath &&
|
|
(rootMetadata.packageDependencies &&
|
|
rootMetadata.packageDependencies.hasOwnProperty(
|
|
path.basename(childPath)
|
|
))
|
|
) {
|
|
continue;
|
|
}
|
|
loadFolderCompatibility(childPath, rootPath, rootMetadata, moduleCache);
|
|
}
|
|
}
|
|
|
|
function loadExtensions(modulePath, rootPath, rootMetadata, moduleCache) {
|
|
const fs = require('fs-plus');
|
|
const extensions = ['.js', '.coffee', '.json', '.node'];
|
|
const nodeModulesPath = path.join(rootPath, 'node_modules');
|
|
|
|
function onFile(filePath) {
|
|
filePath = path.relative(rootPath, filePath);
|
|
const segments = filePath.split(path.sep);
|
|
if (segments.includes('test')) return;
|
|
if (segments.includes('tests')) return;
|
|
if (segments.includes('spec')) return;
|
|
if (segments.includes('specs')) return;
|
|
if (
|
|
segments.length > 1 &&
|
|
!['exports', 'lib', 'node_modules', 'src', 'static', 'vendor'].includes(
|
|
segments[0]
|
|
)
|
|
)
|
|
return;
|
|
|
|
const extension = path.extname(filePath);
|
|
if (extensions.includes(extension)) {
|
|
if (moduleCache.extensions[extension] == null) {
|
|
moduleCache.extensions[extension] = [];
|
|
}
|
|
moduleCache.extensions[extension].push(filePath);
|
|
}
|
|
}
|
|
|
|
function onDirectory(childPath) {
|
|
// Don't include extensions from bundled packages
|
|
// These are generated and stored in the package's own metadata cache
|
|
if (rootMetadata.name === 'atom') {
|
|
const parentPath = path.dirname(childPath);
|
|
if (parentPath === nodeModulesPath) {
|
|
const packageName = path.basename(childPath);
|
|
if (
|
|
rootMetadata.packageDependencies &&
|
|
rootMetadata.packageDependencies.hasOwnProperty(packageName)
|
|
)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
fs.traverseTreeSync(rootPath, onFile, onDirectory);
|
|
}
|
|
|
|
function satisfies(version, rawRange) {
|
|
let parsedRange;
|
|
if (!(parsedRange = cache.ranges[rawRange])) {
|
|
parsedRange = new Range(rawRange);
|
|
cache.ranges[rawRange] = parsedRange;
|
|
}
|
|
return parsedRange.test(version);
|
|
}
|
|
|
|
function resolveFilePath(relativePath, parentModule) {
|
|
if (!relativePath) return;
|
|
if (!(parentModule && parentModule.filename)) return;
|
|
if (relativePath[0] !== '.' && !isAbsolute(relativePath)) return;
|
|
|
|
const resolvedPath = path.resolve(
|
|
path.dirname(parentModule.filename),
|
|
relativePath
|
|
);
|
|
if (!isCorePath(resolvedPath)) return;
|
|
|
|
let extension = path.extname(resolvedPath);
|
|
if (extension) {
|
|
if (
|
|
cache.extensions[extension] &&
|
|
cache.extensions[extension].has(resolvedPath)
|
|
)
|
|
return resolvedPath;
|
|
} else {
|
|
for (extension in cache.extensions) {
|
|
const paths = cache.extensions[extension];
|
|
const resolvedPathWithExtension = `${resolvedPath}${extension}`;
|
|
if (paths.has(resolvedPathWithExtension)) {
|
|
return resolvedPathWithExtension;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function resolveModulePath(relativePath, parentModule) {
|
|
if (!relativePath) return;
|
|
if (!(parentModule && parentModule.filename)) return;
|
|
|
|
if (!nativeModules) nativeModules = process.binding('natives');
|
|
if (nativeModules.hasOwnProperty(relativePath)) return;
|
|
if (relativePath[0] === '.') return;
|
|
if (isAbsolute(relativePath)) return;
|
|
|
|
const folderPath = path.dirname(parentModule.filename);
|
|
|
|
const range =
|
|
cache.folders[folderPath] && cache.folders[folderPath][relativePath];
|
|
if (!range) {
|
|
const builtinPath = cache.builtins[relativePath];
|
|
if (builtinPath) {
|
|
return builtinPath;
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
const candidates = cache.dependencies[relativePath];
|
|
if (candidates == null) return;
|
|
|
|
for (let version in candidates) {
|
|
const resolvedPath = candidates[version];
|
|
if (Module._cache[resolvedPath] || isCorePath(resolvedPath)) {
|
|
if (satisfies(version, range)) return resolvedPath;
|
|
}
|
|
}
|
|
}
|
|
|
|
function registerBuiltins(devMode) {
|
|
if (
|
|
devMode ||
|
|
!cache.resourcePath.startsWith(`${process.resourcesPath}${path.sep}`)
|
|
) {
|
|
const fs = require('fs-plus');
|
|
const atomJsPath = path.join(cache.resourcePath, 'exports', 'atom.js');
|
|
if (fs.isFileSync(atomJsPath)) {
|
|
cache.builtins.atom = atomJsPath;
|
|
}
|
|
}
|
|
if (cache.builtins.atom == null) {
|
|
cache.builtins.atom = path.join(cache.resourcePath, 'exports', 'atom.js');
|
|
}
|
|
|
|
const electronAsarRoot = path.join(process.resourcesPath, 'electron.asar');
|
|
|
|
const commonRoot = path.join(electronAsarRoot, 'common', 'api');
|
|
const commonBuiltins = [
|
|
'callbacks-registry',
|
|
'clipboard',
|
|
'crash-reporter',
|
|
'shell'
|
|
];
|
|
for (const builtin of commonBuiltins) {
|
|
cache.builtins[builtin] = path.join(commonRoot, `${builtin}.js`);
|
|
}
|
|
|
|
const rendererRoot = path.join(electronAsarRoot, 'renderer', 'api');
|
|
const rendererBuiltins = ['ipc-renderer', 'remote', 'screen'];
|
|
for (const builtin of rendererBuiltins) {
|
|
cache.builtins[builtin] = path.join(rendererRoot, `${builtin}.js`);
|
|
}
|
|
}
|
|
|
|
exports.create = function(modulePath) {
|
|
const fs = require('fs-plus');
|
|
|
|
modulePath = fs.realpathSync(modulePath);
|
|
const metadataPath = path.join(modulePath, 'package.json');
|
|
const metadata = JSON.parse(fs.readFileSync(metadataPath));
|
|
|
|
const moduleCache = {
|
|
version: 1,
|
|
dependencies: [],
|
|
extensions: {},
|
|
folders: []
|
|
};
|
|
|
|
loadDependencies(modulePath, modulePath, metadata, moduleCache);
|
|
loadFolderCompatibility(modulePath, modulePath, metadata, moduleCache);
|
|
loadExtensions(modulePath, modulePath, metadata, moduleCache);
|
|
|
|
metadata._atomModuleCache = moduleCache;
|
|
fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
|
|
};
|
|
|
|
exports.register = function({ resourcePath, devMode } = {}) {
|
|
if (cache.registered) return;
|
|
|
|
const originalResolveFilename = Module._resolveFilename;
|
|
Module._resolveFilename = function(relativePath, parentModule) {
|
|
let resolvedPath = resolveModulePath(relativePath, parentModule);
|
|
if (!resolvedPath) {
|
|
resolvedPath = resolveFilePath(relativePath, parentModule);
|
|
}
|
|
return resolvedPath || originalResolveFilename(relativePath, parentModule);
|
|
};
|
|
|
|
cache.registered = true;
|
|
cache.resourcePath = resourcePath;
|
|
cache.resourcePathWithTrailingSlash = `${resourcePath}${path.sep}`;
|
|
registerBuiltins(devMode);
|
|
};
|
|
|
|
exports.add = function(directoryPath, metadata) {
|
|
// path.join isn't used in this function for speed since path.join calls
|
|
// path.normalize and all the paths are already normalized here.
|
|
|
|
if (metadata == null) {
|
|
try {
|
|
metadata = require(`${directoryPath}${path.sep}package.json`);
|
|
} catch (error) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
const cacheToAdd = metadata && metadata._atomModuleCache;
|
|
if (!cacheToAdd) return;
|
|
|
|
for (const dependency of cacheToAdd.dependencies || []) {
|
|
if (!cache.dependencies[dependency.name]) {
|
|
cache.dependencies[dependency.name] = {};
|
|
}
|
|
if (!cache.dependencies[dependency.name][dependency.version]) {
|
|
cache.dependencies[dependency.name][
|
|
dependency.version
|
|
] = `${directoryPath}${path.sep}${dependency.path}`;
|
|
}
|
|
}
|
|
|
|
for (const entry of cacheToAdd.folders || []) {
|
|
for (const folderPath of entry.paths) {
|
|
if (folderPath) {
|
|
cache.folders[`${directoryPath}${path.sep}${folderPath}`] =
|
|
entry.dependencies;
|
|
} else {
|
|
cache.folders[directoryPath] = entry.dependencies;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const extension in cacheToAdd.extensions) {
|
|
const paths = cacheToAdd.extensions[extension];
|
|
if (!cache.extensions[extension]) {
|
|
cache.extensions[extension] = new Set();
|
|
}
|
|
for (let filePath of paths) {
|
|
cache.extensions[extension].add(`${directoryPath}${path.sep}${filePath}`);
|
|
}
|
|
}
|
|
};
|
|
|
|
exports.cache = cache;
|
|
|
|
exports.Range = Range;
|