Skip to content

Commit cbd91dc

Browse files
committed
feat(@angular-devkit/schematics): add test engine host
This adds a few things: - NodeModulesTestEngineHost. This uses the same logic as NodeModulesEngineHost, but allow for overwriting some scheamtics with specific paths. This prevents the EngineHost from using node_modules, which doesnt contain the collection we are trying to test. - Remove RegistryEngineHost which is not used. - Add a new FallbackEngineHost, which can resolve collections and schematics against multiple hosts as a fallback pattenr. - Use the new NodeModulesTestEngineHost for SchematicsTestRunner. - Update all the tests to use the new stuff. - When resolving Url to Sources, knowing the context when creating the source is necessary. Changed that. - The NodeModulesEngineHost now uses the resolve() method from @angular-devkit/core/node.
1 parent 9eaeb86 commit cbd91dc

21 files changed

Lines changed: 244 additions & 165 deletions

File tree

packages/angular_devkit/core/node/resolve.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export interface ResolveOptions {
8383
paths?: string[];
8484
preserveSymlinks?: boolean;
8585
checkGlobal?: boolean;
86+
checkLocal?: boolean;
8687
}
8788

8889

@@ -111,22 +112,42 @@ export function resolve(x: string, options: ResolveOptions = {}): string {
111112
}
112113
}
113114

115+
// Fallback to checking the local (callee) node modules.
116+
if (options.checkLocal) {
117+
const localDir = path.dirname(_caller());
118+
if (localDir !== options.basedir) {
119+
try {
120+
return resolve(x, {
121+
...options,
122+
checkLocal: false,
123+
checkGlobal: false,
124+
basedir: localDir,
125+
});
126+
} catch (e) {
127+
// Just swap the basePath with the original call one.
128+
if (!(e instanceof ModuleNotFoundException)) {
129+
throw e;
130+
}
131+
}
132+
}
133+
}
134+
114135
// Fallback to checking the global node modules.
115136
if (options.checkGlobal) {
116137
const globalDir = path.dirname(_getGlobalNodeModules());
117138
if (globalDir !== options.basedir) {
118139
try {
119140
return resolve(x, {
120141
...options,
142+
checkLocal: false,
121143
checkGlobal: false,
122144
basedir: globalDir,
123145
});
124146
} catch (e) {
125147
// Just swap the basePath with the original call one.
126-
if (e instanceof ModuleNotFoundException) {
127-
throw new ModuleNotFoundException(x, basePath);
148+
if (!(e instanceof ModuleNotFoundException)) {
149+
throw e;
128150
}
129-
throw e;
130151
}
131152
}
132153
}

packages/angular_devkit/schematics/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ ts_library(
3131
deps = [
3232
":schematics",
3333
"//packages/angular_devkit/core",
34+
"//packages/angular_devkit/core:node",
3435
# @deps: rxjs
3536
],
3637
tsconfig = "//:tsconfig.json",

packages/angular_devkit/schematics/src/engine/engine.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88
import { BaseException } from '@angular-devkit/core';
9-
import { CollectionDescription } from '@angular-devkit/schematics';
9+
import { CollectionDescription, TypedSchematicContext } from '@angular-devkit/schematics';
1010
import 'rxjs/add/operator/map';
1111
import { Url } from 'url';
1212
import { MergeStrategy } from '../tree/interface';
@@ -103,12 +103,12 @@ export class SchematicEngine<CollectionT extends object, SchematicT extends obje
103103
);
104104
}
105105

106-
createSourceFromUrl(url: Url): Source {
106+
createSourceFromUrl(url: Url, context: TypedSchematicContext<CollectionT, SchematicT>): Source {
107107
switch (url.protocol) {
108108
case 'null:': return () => new NullTree();
109109
case 'empty:': return () => empty();
110110
default:
111-
const hostSource = this._host.createSourceFromUrl(url);
111+
const hostSource = this._host.createSourceFromUrl(url, context);
112112
if (!hostSource) {
113113
throw new UnknownUrlSourceProtocol(url.toString());
114114
}

packages/angular_devkit/schematics/src/engine/interface.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ export interface EngineHost<CollectionMetadataT extends object, SchematicMetadat
4545
getSchematicRuleFactory<OptionT extends object>(
4646
schematic: SchematicDescription<CollectionMetadataT, SchematicMetadataT>,
4747
collection: CollectionDescription<CollectionMetadataT>): RuleFactory<OptionT>;
48-
createSourceFromUrl(url: Url): Source | null;
48+
createSourceFromUrl(
49+
url: Url,
50+
context: TypedSchematicContext<CollectionMetadataT, SchematicMetadataT>,
51+
): Source | null;
4952
transformOptions<OptionT extends object, ResultT extends object>(
5053
schematic: SchematicDescription<CollectionMetadataT, SchematicMetadataT>,
5154
options: OptionT,
@@ -72,7 +75,10 @@ export interface Engine<CollectionMetadataT extends object, SchematicMetadataT e
7275
name: string,
7376
collection: Collection<CollectionMetadataT, SchematicMetadataT>,
7477
): Schematic<CollectionMetadataT, SchematicMetadataT>;
75-
createSourceFromUrl(url: Url): Source;
78+
createSourceFromUrl(
79+
url: Url,
80+
context: TypedSchematicContext<CollectionMetadataT, SchematicMetadataT>,
81+
): Source;
7682
transformOptions<OptionT extends object, ResultT extends object>(
7783
schematic: Schematic<CollectionMetadataT, SchematicMetadataT>,
7884
options: OptionT,

packages/angular_devkit/schematics/src/rules/url.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ import { SchematicContext, Source } from '../engine/interface';
1212
export function url(urlString: string): Source {
1313
const url = parse(urlString);
1414

15-
return (context: SchematicContext) => context.engine.createSourceFromUrl(url)(context);
15+
return (context: SchematicContext) => context.engine.createSourceFromUrl(url, context)(context);
1616
}

packages/angular_devkit/schematics/test/schematic-test-runner.ts

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
} from '@angular-devkit/schematics';
1414
import {
1515
FileSystemSchematicDesc,
16-
NodeModulesEngineHost,
16+
NodeModulesTestEngineHost,
1717
} from '@angular-devkit/schematics/tools';
1818
import { SchemaClassFactory } from '@ngtools/json-schema';
1919
import { Observable } from 'rxjs/Observable';
@@ -22,19 +22,19 @@ import { Observable } from 'rxjs/Observable';
2222
export interface SchematicSchemaT {}
2323

2424
export class SchematicTestRunner {
25-
private engineHost: NodeModulesEngineHost;
26-
private engine: SchematicEngine<{}, {}>;
27-
private collection: Collection<{}, {}>;
25+
private _engineHost = new NodeModulesTestEngineHost();
26+
private _engine: SchematicEngine<{}, {}> = new SchematicEngine(this._engineHost);
27+
private _collection: Collection<{}, {}>;
2828

29-
constructor(private collectionName: string) {
30-
this.prepareCollection();
31-
}
29+
constructor(private _collectionName: string, collectionPath: string) {
30+
this._engineHost.registerCollection(_collectionName, collectionPath);
31+
32+
this._engineHost.registerOptionsTransform((
33+
schematicDescription: {},
34+
opts: SchematicSchemaT,
35+
) => {
36+
const schematic: FileSystemSchematicDesc = schematicDescription as FileSystemSchematicDesc;
3237

33-
private prepareCollection() {
34-
this.engineHost = new NodeModulesEngineHost();
35-
this.engine = new SchematicEngine(this.engineHost);
36-
this.engineHost.registerOptionsTransform((
37-
schematic: FileSystemSchematicDesc, opts: SchematicSchemaT) => {
3838
if (schematic.schema && schematic.schemaJson) {
3939
const SchemaMetaClass = SchemaClassFactory<SchematicSchemaT>(schematic.schemaJson);
4040
const schemaClass = new SchemaMetaClass(opts);
@@ -44,18 +44,19 @@ export class SchematicTestRunner {
4444

4545
return opts;
4646
});
47-
this.collection = this.engine.createCollection(this.collectionName);
47+
48+
this._collection = this._engine.createCollection(this._collectionName);
4849
}
4950

5051
runSchematicAsync(schematicName: string, opts?: SchematicSchemaT, tree?: Tree): Observable<Tree> {
51-
const schematic = this.collection.createSchematic(schematicName);
52+
const schematic = this._collection.createSchematic(schematicName);
5253
const host = Observable.of(tree || new VirtualTree);
5354

5455
return schematic.call(opts || {}, host);
5556
}
5657

5758
runSchematic(schematicName: string, opts?: SchematicSchemaT, tree?: Tree): Tree {
58-
const schematic = this.collection.createSchematic(schematicName);
59+
const schematic = this._collection.createSchematic(schematicName);
5960

6061
let result: Tree | null = null;
6162
const host = Observable.of(tree || new VirtualTree);
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import {
9+
CollectionDescription,
10+
EngineHost,
11+
RuleFactory,
12+
SchematicDescription,
13+
Source, TypedSchematicContext,
14+
UnknownCollectionException,
15+
} from '@angular-devkit/schematics';
16+
import { Url } from 'url';
17+
18+
19+
export type FallbackCollectionDescription = {
20+
host: EngineHost<{}, {}>;
21+
description: CollectionDescription<{}>;
22+
};
23+
export type FallbackSchematicDescription = {
24+
description: SchematicDescription<{}, {}>;
25+
};
26+
export declare type OptionTransform<T extends object, R extends object> = (
27+
schematic: SchematicDescription<FallbackCollectionDescription, FallbackSchematicDescription>,
28+
options: T,
29+
) => R;
30+
31+
32+
/**
33+
* An EngineHost that support multiple hosts in a fallback configuration. If a host does not
34+
* have a collection/schematics, use the following host before giving up.
35+
*/
36+
export class FallbackEngineHost implements EngineHost<{}, {}> {
37+
private _hosts: EngineHost<{}, {}>[] = [];
38+
private _transforms: OptionTransform<object, object>[] = [];
39+
40+
constructor() {}
41+
42+
addHost<CollectionT extends object, SchematicT extends object>(
43+
host: EngineHost<CollectionT, SchematicT>,
44+
) {
45+
this._hosts.push(host);
46+
}
47+
48+
registerOptionsTransform<T extends object, R extends object>(t: OptionTransform<T, R>) {
49+
this._transforms.push(t);
50+
}
51+
52+
createCollectionDescription(name: string): CollectionDescription<FallbackCollectionDescription> {
53+
for (const host of this._hosts) {
54+
try {
55+
const description = host.createCollectionDescription(name);
56+
57+
return { name, host, description };
58+
} catch (_) {
59+
}
60+
}
61+
62+
throw new UnknownCollectionException(name);
63+
}
64+
65+
createSchematicDescription(
66+
name: string,
67+
collection: CollectionDescription<FallbackCollectionDescription>,
68+
): SchematicDescription<FallbackCollectionDescription, FallbackSchematicDescription> {
69+
const description = collection.host.createSchematicDescription(name, collection.description);
70+
71+
return { name, collection, description };
72+
}
73+
74+
getSchematicRuleFactory<OptionT extends object>(
75+
schematic: SchematicDescription<FallbackCollectionDescription, FallbackSchematicDescription>,
76+
collection: CollectionDescription<FallbackCollectionDescription>): RuleFactory<OptionT> {
77+
return collection.host.getSchematicRuleFactory(schematic.description, collection.description);
78+
}
79+
80+
createSourceFromUrl(
81+
url: Url,
82+
context: TypedSchematicContext<FallbackCollectionDescription, FallbackSchematicDescription>,
83+
): Source | null {
84+
return context.schematic.collection.description.host.createSourceFromUrl(url, context);
85+
}
86+
87+
transformOptions<OptionT extends object, ResultT extends object>(
88+
schematic: SchematicDescription<FallbackCollectionDescription, FallbackSchematicDescription>,
89+
options: OptionT,
90+
): ResultT {
91+
return this._transforms.reduce((acc: ResultT, t) => t(schematic, acc), options) as ResultT;
92+
}
93+
}

packages/angular_devkit/schematics/tools/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export * from './description';
99
export * from './file-system-host';
1010
export * from './file-system-engine-host-base';
1111

12+
export { FallbackEngineHost } from './fallback-engine-host';
1213
export {FileSystemEngineHost} from './file-system-engine-host';
1314
export {NodeModulesEngineHost} from './node-module-engine-host';
14-
export {RegistryEngineHost} from './registry-engine-host';
15+
export { NodeModulesTestEngineHost } from './node-modules-test-engine-host';

packages/angular_devkit/schematics/tools/node-module-engine-host.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8+
import { resolve } from '@angular-devkit/core/node';
89
import { RuleFactory } from '@angular-devkit/schematics';
910
import {
1011
CollectionCannotBeResolvedException,
1112
CollectionMissingSchematicsMapException,
1213
SchematicMissingFieldsException,
1314
} from '@angular-devkit/schematics/tools';
14-
import { join } from 'path';
15+
import { dirname, join } from 'path';
1516
import {
1617
FileSystemCollectionDesc,
1718
FileSystemSchematicDesc,
@@ -24,13 +25,25 @@ import { FileSystemEngineHostBase } from './file-system-engine-host-base';
2425
* A simple EngineHost that uses NodeModules to resolve collections.
2526
*/
2627
export class NodeModulesEngineHost extends FileSystemEngineHostBase {
28+
constructor() { super(); }
29+
2730
protected _resolveCollectionPath(name: string): string {
28-
const pkgJsonSchematics = require(join(name, 'package.json'))['schematics'];
31+
const packageJsonPath = resolve(join(name, 'package.json'), {
32+
basedir: process.cwd(),
33+
checkLocal: true,
34+
checkGlobal: true,
35+
});
36+
37+
const pkgJsonSchematics = require(packageJsonPath)['schematics'];
2938
if (!pkgJsonSchematics) {
3039
throw new CollectionCannotBeResolvedException(name);
3140
}
3241

33-
return require.resolve(join(name, pkgJsonSchematics));
42+
return resolve(join(dirname(packageJsonPath), pkgJsonSchematics), {
43+
basedir: process.cwd(),
44+
checkLocal: true,
45+
checkGlobal: true,
46+
});
3447
}
3548

3649
protected _resolveReferenceString(refString: string, parentPath: string) {
@@ -49,12 +62,10 @@ export class NodeModulesEngineHost extends FileSystemEngineHostBase {
4962
if (!desc.schematics || typeof desc.schematics != 'object') {
5063
throw new CollectionMissingSchematicsMapException(name);
5164
}
52-
const version = require(join(name, 'package.json'))['version'];
5365

5466
return {
5567
...desc,
5668
name,
57-
version,
5869
} as FileSystemCollectionDesc;
5970
}
6071

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import { NodeModulesEngineHost } from '@angular-devkit/schematics/tools';
9+
10+
11+
/**
12+
* An EngineHost that uses a registry to super seed locations of collection.json files, but
13+
* revert back to using node modules resolution. This is done for testing.
14+
*/
15+
export class NodeModulesTestEngineHost extends NodeModulesEngineHost {
16+
private _collections = new Map<string, string>();
17+
18+
registerCollection(name: string, path: string) {
19+
this._collections.set(name, path);
20+
}
21+
22+
protected _resolveCollectionPath(name: string): string {
23+
const maybePath = this._collections.get(name);
24+
if (maybePath) {
25+
return maybePath;
26+
}
27+
28+
return super._resolveCollectionPath(name);
29+
}
30+
}

0 commit comments

Comments
 (0)