mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-18 03:23:57 +08:00
317 lines
9.0 KiB
TypeScript
317 lines
9.0 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import { BaseException } from '../src';
|
|
import { isFile } from './fs';
|
|
|
|
/**
|
|
* Exception thrown when a module could not be resolved.
|
|
* @deprecated since version 8. Use `MODULE_NOT_FOUND` Node error code instead.
|
|
*/
|
|
export class ModuleNotFoundException extends BaseException {
|
|
public readonly code: string;
|
|
|
|
constructor(public readonly moduleName: string, public readonly basePath: string) {
|
|
super(`Could not find module ${JSON.stringify(moduleName)} from ${JSON.stringify(basePath)}.`);
|
|
this.code = 'MODULE_NOT_FOUND';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a list of all the callers from the resolve() call.
|
|
* @returns {string[]}
|
|
* @private
|
|
*/
|
|
function _caller(): string[] {
|
|
// see https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
|
|
const error = Error as {} as { prepareStackTrace: (x: {}, stack: {}) => {} };
|
|
const origPrepareStackTrace = error.prepareStackTrace;
|
|
error.prepareStackTrace = (_, stack) => stack;
|
|
const stack = (new Error()).stack as {} | undefined as { getFileName(): string }[] | undefined;
|
|
error.prepareStackTrace = origPrepareStackTrace;
|
|
|
|
return stack ? stack.map(x => x.getFileName()).filter(x => !!x) : [];
|
|
}
|
|
|
|
|
|
/**
|
|
* Get the global directory for node_modules. This is based on NPM code itself, and may be subject
|
|
* to change, but is relatively stable.
|
|
* @returns {string} The path to node_modules itself.
|
|
* @private
|
|
*/
|
|
function _getGlobalNodeModules() {
|
|
let globalPrefix;
|
|
|
|
if (process.env.PREFIX) {
|
|
globalPrefix = process.env.PREFIX;
|
|
} else if (process.platform === 'win32') {
|
|
// c:\node\node.exe --> prefix=c:\node\
|
|
globalPrefix = path.dirname(process.execPath);
|
|
} else {
|
|
// /usr/local/bin/node --> prefix=/usr/local
|
|
globalPrefix = path.dirname(path.dirname(process.execPath));
|
|
|
|
// destdir only is respected on Unix
|
|
const destdir = process.env.DESTDIR;
|
|
if (destdir) {
|
|
globalPrefix = path.join(destdir, globalPrefix);
|
|
}
|
|
}
|
|
|
|
return (process.platform !== 'win32')
|
|
? path.resolve(globalPrefix || '', 'lib', 'node_modules')
|
|
: path.resolve(globalPrefix || '', 'node_modules');
|
|
}
|
|
|
|
|
|
/** @deprecated since version 8. Use `require.resolve` instead. */
|
|
export interface ResolveOptions {
|
|
/**
|
|
* The basedir to use from which to resolve.
|
|
*/
|
|
basedir: string;
|
|
|
|
/**
|
|
* The list of extensions to resolve. By default uses Object.keys(require.extensions).
|
|
*/
|
|
extensions?: string[];
|
|
|
|
/**
|
|
* An additional list of paths to look into.
|
|
*/
|
|
paths?: string[];
|
|
|
|
/**
|
|
* Whether or not to preserve symbolic links. If false, the actual paths pointed by
|
|
* the symbolic links will be used. This defaults to true.
|
|
*/
|
|
preserveSymlinks?: boolean;
|
|
|
|
/**
|
|
* Whether to fallback to a global lookup if the basedir one failed.
|
|
*/
|
|
checkGlobal?: boolean;
|
|
|
|
/**
|
|
* Whether to fallback to using the local caller's directory if the basedir failed.
|
|
*/
|
|
checkLocal?: boolean;
|
|
|
|
/**
|
|
* Whether to only resolve and return the first package.json file found. By default,
|
|
* resolves the main field or the index of the package.
|
|
*/
|
|
resolvePackageJson?: boolean;
|
|
}
|
|
|
|
|
|
let _resolveHook: ((x: string, options: ResolveOptions) => string | null) | null = null;
|
|
/** @deprecated since version 8. Use `require.resolve` instead. */
|
|
export function setResolveHook(
|
|
hook: ((x: string, options: ResolveOptions) => string | null) | null,
|
|
) {
|
|
_resolveHook = hook;
|
|
}
|
|
|
|
|
|
/**
|
|
* Resolve a package using a logic similar to npm require.resolve, but with more options.
|
|
* @param packageName The package name to resolve.
|
|
* @param options A list of options. See documentation of those options.
|
|
* @returns {string} Path to the index to include, or if `resolvePackageJson` option was
|
|
* passed, a path to that file.
|
|
* @throws {ModuleNotFoundException} If no module with that name was found anywhere.
|
|
* @deprecated since version 8. Use `require.resolve` instead.
|
|
*/
|
|
export function resolve(packageName: string, options: ResolveOptions): string {
|
|
if (_resolveHook) {
|
|
const maybe = _resolveHook(packageName, options);
|
|
if (maybe) {
|
|
return maybe;
|
|
}
|
|
}
|
|
|
|
const readFileSync = fs.readFileSync;
|
|
|
|
const extensions: string[] = options.extensions || Object.keys(require.extensions);
|
|
const basePath = options.basedir;
|
|
|
|
options.paths = options.paths || [];
|
|
|
|
if (/^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[\/\\])/.test(packageName)) {
|
|
let res = path.resolve(basePath, packageName);
|
|
if (packageName === '..' || packageName.slice(-1) === '/') {
|
|
res += '/';
|
|
}
|
|
|
|
const m = loadAsFileSync(res) || loadAsDirectorySync(res);
|
|
if (m) {
|
|
return m;
|
|
}
|
|
} else {
|
|
const n = loadNodeModulesSync(packageName, basePath);
|
|
if (n) {
|
|
return n;
|
|
}
|
|
}
|
|
|
|
// Fallback to checking the local (callee) node modules.
|
|
if (options.checkLocal) {
|
|
const callers = _caller();
|
|
for (const caller of callers) {
|
|
const localDir = path.dirname(caller);
|
|
if (localDir !== options.basedir) {
|
|
try {
|
|
return resolve(packageName, {
|
|
...options,
|
|
checkLocal: false,
|
|
checkGlobal: false,
|
|
basedir: localDir,
|
|
});
|
|
} catch (e) {
|
|
// Just swap the basePath with the original call one.
|
|
if (!(e instanceof ModuleNotFoundException)) {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fallback to checking the global node modules.
|
|
if (options.checkGlobal) {
|
|
const globalDir = path.dirname(_getGlobalNodeModules());
|
|
if (globalDir !== options.basedir) {
|
|
try {
|
|
return resolve(packageName, {
|
|
...options,
|
|
checkLocal: false,
|
|
checkGlobal: false,
|
|
basedir: globalDir,
|
|
});
|
|
} catch (e) {
|
|
// Just swap the basePath with the original call one.
|
|
if (!(e instanceof ModuleNotFoundException)) {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
throw new ModuleNotFoundException(packageName, basePath);
|
|
|
|
function loadAsFileSync(x: string): string | null {
|
|
if (isFile(x)) {
|
|
return x;
|
|
}
|
|
|
|
return extensions.map(ex => x + ex).find(f => isFile(f)) || null;
|
|
}
|
|
|
|
function loadAsDirectorySync(x: string): string | null {
|
|
const pkgfile = path.join(x, 'package.json');
|
|
if (isFile(pkgfile)) {
|
|
if (options.resolvePackageJson) {
|
|
return pkgfile;
|
|
}
|
|
|
|
try {
|
|
const body = readFileSync(pkgfile, 'UTF8');
|
|
const pkg = JSON.parse(body);
|
|
|
|
if (pkg['main']) {
|
|
if (pkg['main'] === '.' || pkg['main'] === './') {
|
|
pkg['main'] = 'index';
|
|
}
|
|
|
|
const m = loadAsFileSync(path.resolve(x, pkg['main']));
|
|
if (m) {
|
|
return m;
|
|
}
|
|
const n = loadAsDirectorySync(path.resolve(x, pkg['main']));
|
|
if (n) {
|
|
return n;
|
|
}
|
|
}
|
|
} catch {}
|
|
}
|
|
|
|
return loadAsFileSync(path.join(x, '/index'));
|
|
}
|
|
|
|
function loadNodeModulesSync(x: string, start: string): string | null {
|
|
const dirs = nodeModulesPaths(start, options);
|
|
for (const dir of dirs) {
|
|
const m = loadAsFileSync(path.join(dir, x));
|
|
if (m) {
|
|
return m;
|
|
}
|
|
const n = loadAsDirectorySync(path.join(dir, x));
|
|
if (n) {
|
|
return n;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function nodeModulesPaths(start: string, opts: ResolveOptions) {
|
|
const modules = ['node_modules'];
|
|
if (process.env.BAZEL_TARGET) {
|
|
// When running test under Bazel, node_modules have to be resolved
|
|
// differently. node_modules are installed in the `npm` workspace.
|
|
// For more info, see `yarn_install` rule in WORKSPACE file.
|
|
modules.push(path.join('npm', 'node_modules'));
|
|
}
|
|
|
|
// ensure that `start` is an absolute path at this point,
|
|
// resolving against the process' current working directory
|
|
let absoluteStart = path.resolve(start);
|
|
|
|
if (opts && opts.preserveSymlinks === false) {
|
|
try {
|
|
absoluteStart = fs.realpathSync(absoluteStart);
|
|
} catch (err) {
|
|
if (err.code !== 'ENOENT') {
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
|
|
let prefix = '/';
|
|
if (/^([A-Za-z]:)/.test(absoluteStart)) {
|
|
prefix = '';
|
|
} else if (/^\\\\/.test(absoluteStart)) {
|
|
prefix = '\\\\';
|
|
}
|
|
|
|
const paths = [absoluteStart];
|
|
let parsed = path.parse(absoluteStart);
|
|
while (parsed.dir !== paths[paths.length - 1]) {
|
|
paths.push(parsed.dir);
|
|
parsed = path.parse(parsed.dir);
|
|
}
|
|
|
|
const dirs = paths.reduce((dirs: string[], aPath: string) => {
|
|
return dirs.concat(modules.map(function (moduleDir) {
|
|
return path.join(prefix, aPath, moduleDir);
|
|
}));
|
|
}, []);
|
|
|
|
if (process.env.NG_TEMP_MODULES_DIR) {
|
|
// When running from a temporary installations, node_modules have to be resolved
|
|
// differently and they should be prefered over others.
|
|
dirs.unshift(process.env.NG_TEMP_MODULES_DIR);
|
|
}
|
|
|
|
return opts && opts.paths ? dirs.concat(opts.paths) : dirs;
|
|
}
|
|
}
|