From ea3d9e0285403af447daa80df4931c86b7cb46fd Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Tue, 25 Feb 2020 21:30:21 -0500 Subject: [PATCH] fix(@angular-devkit/schematics): fully resolve schematic entries within packages Fixes: #17085 --- .../tools/node-module-engine-host.ts | 67 +++++++++++++++---- 1 file changed, 54 insertions(+), 13 deletions(-) diff --git a/packages/angular_devkit/schematics/tools/node-module-engine-host.ts b/packages/angular_devkit/schematics/tools/node-module-engine-host.ts index 1d864712ca..ee12c28056 100644 --- a/packages/angular_devkit/schematics/tools/node-module-engine-host.ts +++ b/packages/angular_devkit/schematics/tools/node-module-engine-host.ts @@ -10,7 +10,7 @@ import { InvalidJsonCharacterException, UnexpectedEndOfInputException, } from '@angular-devkit/core'; -import { dirname, extname, join, resolve } from 'path'; +import { dirname, join, resolve } from 'path'; import { RuleFactory } from '../src'; import { FileSystemCollectionDesc, @@ -39,26 +39,67 @@ export class NodePackageDoesNotSupportSchematics extends BaseException { export class NodeModulesEngineHost extends FileSystemEngineHostBase { constructor(private readonly paths?: string[]) { super(); } - protected _resolveCollectionPath(name: string): string { - let collectionPath: string | undefined = undefined; - if (name.startsWith('.') || name.startsWith('/')) { - name = resolve(name); + private resolve(name: string, requester?: string, references = new Set()): string { + if (requester) { + if (references.has(requester)) { + references.add(requester); + throw new Error( + 'Circular schematic reference detected: ' + JSON.stringify(Array.from(references)), + ); + } else { + references.add(requester); + } } - if (extname(name)) { - // When having an extension let's just resolve the provided path. - collectionPath = require.resolve(name, { paths: this.paths }); - } else { - const packageJsonPath = require.resolve(join(name, 'package.json'), { paths: this.paths }); + const relativeBase = requester ? dirname(requester) : process.cwd(); + let collectionPath: string | undefined = undefined; + + if (name.startsWith('.')) { + 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); if (!schematics || typeof schematics !== 'string') { 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 { readJsonFile(collectionPath); @@ -68,10 +109,10 @@ export class NodeModulesEngineHost extends FileSystemEngineHostBase { e instanceof InvalidJsonCharacterException || e instanceof UnexpectedEndOfInputException ) { throw new InvalidCollectionJsonException(name, collectionPath, e); + } else { + throw e; } } - - throw new CollectionCannotBeResolvedException(name); } protected _resolveReferenceString(refString: string, parentPath: string) {