mirror of
https://github.com/atom/atom.git
synced 2026-01-25 14:59:03 -05:00
340 lines
11 KiB
JavaScript
340 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
|