fix(@angular-devkit/schematics): resolve external schematics from requesting collection

This change first attempts to resolve a schematic referenced via the external schematic rule from the requesting schematic collection.  This allows schematic packages that are direct dependencies of another schematic package to be used with the external schematic rule without manual package resolution code within the schematic.

Closes #18098
Closes #11026
This commit is contained in:
Charles Lyding 2020-09-17 11:18:03 -04:00 committed by Filipe Silva
parent 99210b203d
commit 5ce621e371
9 changed files with 50 additions and 38 deletions

View File

@ -197,7 +197,7 @@ export declare class EmptyTree extends HostTree {
export interface Engine<CollectionMetadataT extends object, SchematicMetadataT extends object> {
readonly defaultMergeStrategy: MergeStrategy;
readonly workflow: Workflow | null;
createCollection(name: string): Collection<CollectionMetadataT, SchematicMetadataT>;
createCollection(name: string, requester?: Collection<CollectionMetadataT, SchematicMetadataT>): Collection<CollectionMetadataT, SchematicMetadataT>;
createContext(schematic: Schematic<CollectionMetadataT, SchematicMetadataT>, parent?: Partial<TypedSchematicContext<CollectionMetadataT, SchematicMetadataT>>, executionOptions?: Partial<ExecutionOptions>): TypedSchematicContext<CollectionMetadataT, SchematicMetadataT>;
createSchematic(name: string, collection: Collection<CollectionMetadataT, SchematicMetadataT>): Schematic<CollectionMetadataT, SchematicMetadataT>;
createSourceFromUrl(url: Url, context: TypedSchematicContext<CollectionMetadataT, SchematicMetadataT>): Source;
@ -207,7 +207,7 @@ export interface Engine<CollectionMetadataT extends object, SchematicMetadataT e
export interface EngineHost<CollectionMetadataT extends object, SchematicMetadataT extends object> {
readonly defaultMergeStrategy?: MergeStrategy;
createCollectionDescription(name: string): CollectionDescription<CollectionMetadataT>;
createCollectionDescription(name: string, requester?: CollectionDescription<CollectionMetadataT>): CollectionDescription<CollectionMetadataT>;
createSchematicDescription(name: string, collection: CollectionDescription<CollectionMetadataT>): SchematicDescription<CollectionMetadataT, SchematicMetadataT> | null;
createSourceFromUrl(url: Url, context: TypedSchematicContext<CollectionMetadataT, SchematicMetadataT>): Source | null;
createTaskExecutor(name: string): Observable<TaskExecutor>;
@ -445,7 +445,7 @@ export declare class SchematicEngine<CollectionT extends object, SchematicT exte
get defaultMergeStrategy(): MergeStrategy;
get workflow(): Workflow | null;
constructor(_host: EngineHost<CollectionT, SchematicT>, _workflow?: Workflow | undefined);
createCollection(name: string): Collection<CollectionT, SchematicT>;
createCollection(name: string, requester?: Collection<CollectionT, SchematicT>): Collection<CollectionT, SchematicT>;
createContext(schematic: Schematic<CollectionT, SchematicT>, parent?: Partial<TypedSchematicContext<CollectionT, SchematicT>>, executionOptions?: Partial<ExecutionOptions>): TypedSchematicContext<CollectionT, SchematicT>;
createSchematic(name: string, collection: Collection<CollectionT, SchematicT>, allowPrivate?: boolean): Schematic<CollectionT, SchematicT>;
createSourceFromUrl(url: Url, context: TypedSchematicContext<CollectionT, SchematicT>): Source;

View File

@ -53,14 +53,14 @@ export declare class FileSystemEngineHost extends FileSystemEngineHostBase {
}
export declare abstract class FileSystemEngineHostBase implements FileSystemEngineHost {
protected abstract _resolveCollectionPath(name: string): string;
protected abstract _resolveCollectionPath(name: string, requester?: string): string;
protected abstract _resolveReferenceString(name: string, parentPath: string): {
ref: RuleFactory<{}>;
path: string;
} | null;
protected abstract _transformCollectionDescription(name: string, desc: Partial<FileSystemCollectionDesc>): FileSystemCollectionDesc;
protected abstract _transformSchematicDescription(name: string, collection: FileSystemCollectionDesc, desc: Partial<FileSystemSchematicDesc>): FileSystemSchematicDesc;
createCollectionDescription(name: string): FileSystemCollectionDesc;
createCollectionDescription(name: string, requester?: FileSystemCollectionDesc): FileSystemCollectionDesc;
createSchematicDescription(name: string, collection: FileSystemCollectionDesc): FileSystemSchematicDesc | null;
createSourceFromUrl(url: Url): Source | null;
createTaskExecutor(name: string): Observable<TaskExecutor>;
@ -102,7 +102,7 @@ export declare class InvalidCollectionJsonException extends BaseException {
export declare class NodeModulesEngineHost extends FileSystemEngineHostBase {
constructor(paths?: string[] | undefined);
protected _resolveCollectionPath(name: string): string;
protected _resolveCollectionPath(name: string, requester?: string): string;
protected _resolveReferenceString(refString: string, parentPath: string): {
ref: RuleFactory<{}>;
path: string;
@ -113,7 +113,7 @@ export declare class NodeModulesEngineHost extends FileSystemEngineHostBase {
export declare class NodeModulesTestEngineHost extends NodeModulesEngineHost {
get tasks(): TaskConfiguration<{}>[];
protected _resolveCollectionPath(name: string): string;
protected _resolveCollectionPath(name: string, requester?: string): string;
clearTasks(): void;
registerCollection(name: string, path: string): void;
transformContext(context: FileSystemSchematicContext): FileSystemSchematicContext;

View File

@ -164,7 +164,7 @@ export class SchematicEngine<CollectionT extends object, SchematicT extends obje
private _collectionCache = new Map<string, CollectionImpl<CollectionT, SchematicT>>();
private _schematicCache
= new Map<string, Map<string, SchematicImpl<CollectionT, SchematicT>>>();
= new WeakMap<Collection<CollectionT, SchematicT>, Map<string, SchematicImpl<CollectionT, SchematicT>>>();
private _taskSchedulers = new Array<TaskScheduler>();
constructor(private _host: EngineHost<CollectionT, SchematicT>, protected _workflow?: Workflow) {
@ -173,26 +173,30 @@ export class SchematicEngine<CollectionT extends object, SchematicT extends obje
get workflow() { return this._workflow || null; }
get defaultMergeStrategy() { return this._host.defaultMergeStrategy || MergeStrategy.Default; }
createCollection(name: string): Collection<CollectionT, SchematicT> {
createCollection(
name: string,
requester?: Collection<CollectionT, SchematicT>,
): Collection<CollectionT, SchematicT> {
let collection = this._collectionCache.get(name);
if (collection) {
return collection;
}
const [description, bases] = this._createCollectionDescription(name);
const [description, bases] = this._createCollectionDescription(name, requester?.description);
collection = new CollectionImpl<CollectionT, SchematicT>(description, this, bases);
this._collectionCache.set(name, collection);
this._schematicCache.set(name, new Map());
this._schematicCache.set(collection, new Map());
return collection;
}
private _createCollectionDescription(
name: string,
requester?: CollectionDescription<CollectionT>,
parentNames?: Set<string>,
): [CollectionDescription<CollectionT>, Array<CollectionDescription<CollectionT>>] {
const description = this._host.createCollectionDescription(name);
const description = this._host.createCollectionDescription(name, requester);
if (!description) {
throw new UnknownCollectionException(name);
}
@ -204,7 +208,11 @@ export class SchematicEngine<CollectionT extends object, SchematicT extends obje
if (description.extends) {
parentNames = (parentNames || new Set<string>()).add(description.name);
for (const baseName of description.extends) {
const [base, baseBases] = this._createCollectionDescription(baseName, new Set(parentNames));
const [base, baseBases] = this._createCollectionDescription(
baseName,
description,
new Set(parentNames),
);
bases.unshift(base, ...baseBases);
}
@ -277,14 +285,9 @@ export class SchematicEngine<CollectionT extends object, SchematicT extends obje
collection: Collection<CollectionT, SchematicT>,
allowPrivate = false,
): Schematic<CollectionT, SchematicT> {
const collectionImpl = this._collectionCache.get(collection.description.name);
const schematicMap = this._schematicCache.get(collection.description.name);
if (!collectionImpl || !schematicMap || collectionImpl !== collection) {
// This is weird, maybe the collection was created by another engine?
throw new UnknownCollectionException(collection.description.name);
}
const schematicMap = this._schematicCache.get(collection);
let schematic = schematicMap.get(name);
let schematic = schematicMap?.get(name);
if (schematic) {
return schematic;
}
@ -314,7 +317,7 @@ export class SchematicEngine<CollectionT extends object, SchematicT extends obje
const factory = this._host.getSchematicRuleFactory(description, collectionDescription);
schematic = new SchematicImpl<CollectionT, SchematicT>(description, factory, collection, this);
schematicMap.set(name, schematic);
schematicMap?.set(name, schematic);
return schematic;
}

View File

@ -76,7 +76,10 @@ export type SchematicDescription<CollectionMetadataT extends object,
* parameters contain additional metadata that you want to store while remaining type-safe.
*/
export interface EngineHost<CollectionMetadataT extends object, SchematicMetadataT extends object> {
createCollectionDescription(name: string): CollectionDescription<CollectionMetadataT>;
createCollectionDescription(
name: string,
requester?: CollectionDescription<CollectionMetadataT>,
): CollectionDescription<CollectionMetadataT>;
listSchematicNames(collection: CollectionDescription<CollectionMetadataT>): string[];
createSchematicDescription(
@ -116,24 +119,27 @@ export interface EngineHost<CollectionMetadataT extends object, SchematicMetadat
* SchematicMetadataT is a type that contains additional typing for the Schematic Description.
*/
export interface Engine<CollectionMetadataT extends object, SchematicMetadataT extends object> {
createCollection(name: string): Collection<CollectionMetadataT, SchematicMetadataT>;
createCollection(
name: string,
requester?: Collection<CollectionMetadataT, SchematicMetadataT>,
): Collection<CollectionMetadataT, SchematicMetadataT>;
createContext(
schematic: Schematic<CollectionMetadataT, SchematicMetadataT>,
parent?: Partial<TypedSchematicContext<CollectionMetadataT, SchematicMetadataT>>,
executionOptions?: Partial<ExecutionOptions>,
): TypedSchematicContext<CollectionMetadataT, SchematicMetadataT>;
createSchematic(
name: string,
collection: Collection<CollectionMetadataT, SchematicMetadataT>,
name: string,
collection: Collection<CollectionMetadataT, SchematicMetadataT>,
): Schematic<CollectionMetadataT, SchematicMetadataT>;
createSourceFromUrl(
url: Url,
context: TypedSchematicContext<CollectionMetadataT, SchematicMetadataT>,
): Source;
transformOptions<OptionT extends object, ResultT extends object>(
schematic: Schematic<CollectionMetadataT, SchematicMetadataT>,
options: OptionT,
context?: TypedSchematicContext<CollectionMetadataT, SchematicMetadataT>,
schematic: Schematic<CollectionMetadataT, SchematicMetadataT>,
options: OptionT,
context?: TypedSchematicContext<CollectionMetadataT, SchematicMetadataT>,
): Observable<ResultT>;
executePostTasks(): Observable<void>;

View File

@ -26,7 +26,7 @@ export function externalSchematic<OptionT extends object>(
executionOptions?: Partial<ExecutionOptions>,
): Rule {
return (input: Tree, context: SchematicContext) => {
const collection = context.engine.createCollection(collectionName);
const collection = context.engine.createCollection(collectionName, context.schematic.collection);
const schematic = collection.createSchematic(schematicName);
return schematic.call(options, observableOf(branch(input)), context, executionOptions);

View File

@ -47,10 +47,10 @@ export class FallbackEngineHost implements EngineHost<{}, {}> {
this._hosts.push(host);
}
createCollectionDescription(name: string): CollectionDescription<FallbackCollectionDescription> {
createCollectionDescription(name: string, requester?: CollectionDescription<{}>): CollectionDescription<FallbackCollectionDescription> {
for (const host of this._hosts) {
try {
const description = host.createCollectionDescription(name);
const description = host.createCollectionDescription(name, requester);
return { name, host, description };
} catch (_) {

View File

@ -109,7 +109,7 @@ export class SchematicNameCollisionException extends BaseException {
* all other EngineHost provided by the tooling part of the Schematics library.
*/
export abstract class FileSystemEngineHostBase implements FileSystemEngineHost {
protected abstract _resolveCollectionPath(name: string): string;
protected abstract _resolveCollectionPath(name: string, requester?: string): string;
protected abstract _resolveReferenceString(
name: string, parentPath: string): { ref: RuleFactory<{}>, path: string } | null;
protected abstract _transformCollectionDescription(
@ -158,8 +158,11 @@ export abstract class FileSystemEngineHostBase implements FileSystemEngineHost {
* @param name
* @return {{path: string}}
*/
createCollectionDescription(name: string): FileSystemCollectionDesc {
const path = this._resolveCollectionPath(name);
createCollectionDescription(
name: string,
requester?: FileSystemCollectionDesc,
): FileSystemCollectionDesc {
const path = this._resolveCollectionPath(name, requester?.path);
const jsonValue = readJsonFile(path);
if (!jsonValue || typeof jsonValue != 'object' || Array.isArray(jsonValue)) {
throw new InvalidCollectionJsonException(name, path);

View File

@ -97,8 +97,8 @@ export class NodeModulesEngineHost extends FileSystemEngineHostBase {
return collectionPath;
}
protected _resolveCollectionPath(name: string): string {
const collectionPath = this.resolve(name);
protected _resolveCollectionPath(name: string, requester?: string): string {
const collectionPath = this.resolve(name, requester);
try {
readJsonFile(collectionPath);

View File

@ -37,12 +37,12 @@ export class NodeModulesTestEngineHost extends NodeModulesEngineHost {
return context;
}
protected _resolveCollectionPath(name: string): string {
protected _resolveCollectionPath(name: string, requester?: string): string {
const maybePath = this._collections.get(name);
if (maybePath) {
return maybePath;
}
return super._resolveCollectionPath(name);
return super._resolveCollectionPath(name, requester);
}
}