fix(@angular-devkit/schematics): fully resolve schematic entries within packages

Fixes: #17085
This commit is contained in:
Charles Lyding 2020-02-25 21:30:21 -05:00 committed by Keen Yee Liau
parent 1e258317b1
commit ea3d9e0285

View File

@ -10,7 +10,7 @@ import {
InvalidJsonCharacterException, InvalidJsonCharacterException,
UnexpectedEndOfInputException, UnexpectedEndOfInputException,
} from '@angular-devkit/core'; } from '@angular-devkit/core';
import { dirname, extname, join, resolve } from 'path'; import { dirname, join, resolve } from 'path';
import { RuleFactory } from '../src'; import { RuleFactory } from '../src';
import { import {
FileSystemCollectionDesc, FileSystemCollectionDesc,
@ -39,26 +39,67 @@ export class NodePackageDoesNotSupportSchematics extends BaseException {
export class NodeModulesEngineHost extends FileSystemEngineHostBase { export class NodeModulesEngineHost extends FileSystemEngineHostBase {
constructor(private readonly paths?: string[]) { super(); } constructor(private readonly paths?: string[]) { super(); }
protected _resolveCollectionPath(name: string): string { private resolve(name: string, requester?: string, references = new Set<string>()): string {
let collectionPath: string | undefined = undefined; if (requester) {
if (name.startsWith('.') || name.startsWith('/')) { if (references.has(requester)) {
name = resolve(name); references.add(requester);
throw new Error(
'Circular schematic reference detected: ' + JSON.stringify(Array.from(references)),
);
} else {
references.add(requester);
}
} }
if (extname(name)) { const relativeBase = requester ? dirname(requester) : process.cwd();
// When having an extension let's just resolve the provided path. let collectionPath: string | undefined = undefined;
collectionPath = require.resolve(name, { paths: this.paths });
} else { if (name.startsWith('.')) {
const packageJsonPath = require.resolve(join(name, 'package.json'), { paths: this.paths }); name = resolve(relativeBase, name);
}
const resolveOptions = {
paths: requester ? [dirname(requester), ...(this.paths || [])] : this.paths,
};
// Try to resolve as a package
try {
const packageJsonPath = require.resolve(join(name, 'package.json'), resolveOptions);
const { schematics } = require(packageJsonPath); const { schematics } = require(packageJsonPath);
if (!schematics || typeof schematics !== 'string') { if (!schematics || typeof schematics !== 'string') {
throw new NodePackageDoesNotSupportSchematics(name); throw new NodePackageDoesNotSupportSchematics(name);
} }
collectionPath = resolve(dirname(packageJsonPath), schematics); collectionPath = this.resolve(schematics, packageJsonPath, references);
} catch (e) {
if (e.code !== 'MODULE_NOT_FOUND') {
throw e;
}
} }
// If not a package, try to resolve as a file
if (!collectionPath) {
try {
collectionPath = require.resolve(name, resolveOptions);
} catch (e) {
if (e.code !== 'MODULE_NOT_FOUND') {
throw e;
}
}
}
// If not a package or a file, error
if (!collectionPath) {
throw new CollectionCannotBeResolvedException(name);
}
return collectionPath;
}
protected _resolveCollectionPath(name: string): string {
const collectionPath = this.resolve(name);
try { try {
readJsonFile(collectionPath); readJsonFile(collectionPath);
@ -68,10 +109,10 @@ export class NodeModulesEngineHost extends FileSystemEngineHostBase {
e instanceof InvalidJsonCharacterException || e instanceof UnexpectedEndOfInputException e instanceof InvalidJsonCharacterException || e instanceof UnexpectedEndOfInputException
) { ) {
throw new InvalidCollectionJsonException(name, collectionPath, e); throw new InvalidCollectionJsonException(name, collectionPath, e);
} else {
throw e;
} }
} }
throw new CollectionCannotBeResolvedException(name);
} }
protected _resolveReferenceString(refString: string, parentPath: string) { protected _resolveReferenceString(refString: string, parentPath: string) {