Skip to content

Commit 435648e

Browse files
filipesilvahansl
authored andcommitted
feat(@angular-devkit/architect): add package
1 parent 4b05f01 commit 435648e

File tree

18 files changed

+907
-9
lines changed

18 files changed

+907
-9
lines changed

.monorepo.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,22 @@
4545
"version": "0.4.0",
4646
"hash": "b1f9f2d8e1eca2a865d39263f0ca5a38"
4747
},
48+
"@angular-devkit/architect": {
49+
"name": "Build Facade",
50+
"links": [
51+
{
52+
"label": "README",
53+
"url": "https://github.com/angular/devkit/blob/master/packages/angular_devkit/architect/README.md"
54+
}
55+
],
56+
"version": "0.0.1",
57+
"hash": ""
58+
},
59+
"@angular-devkit/architect-cli": {
60+
"name": "Architect CLI",
61+
"version": "0.0.1",
62+
"hash": ""
63+
},
4864
"@angular-devkit/build-optimizer": {
4965
"name": "Build Optimizer",
5066
"links": [

README.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
### Development tools and libraries specialized for Angular
1515

1616

17-
[![CircleCI branch](https://img.shields.io/circleci/project/github/angular/devkit/master.svg?label=circleci)](https://circleci.com/gh/angular/devkit) [![Dependency Status](https://david-dm.org/angular/devkit.svg)](https://david-dm.org/angular/devkit) [![devDependency Status](https://david-dm.org/angular/devkit/dev-status.svg)](https://david-dm.org/angular/devkit?type=dev)
17+
[![CircleCI branch](https://img.shields.io/circleci/project/github/angular/devkit/master.svg?label=circleci)](https://circleci.com/gh/angular/devkit) [![Dependency Status](https://david-dm.org/angular/devkit.svg)](https://david-dm.org/angular/devkit) [![devDependency Status](https://david-dm.org/angular/devkit/dev-status.svg)](https://david-dm.org/angular/devkit?type=dev)
1818

19-
[![License](https://img.shields.io/npm/l/@angular-devkit/core.svg)](https://github.com/angular/devkit/blob/master/LICENSE)
19+
[![License](https://img.shields.io/npm/l/@angular-devkit/core.svg)](https://github.com/angular/devkit/blob/master/LICENSE)
2020

21-
[![GitHub forks](https://img.shields.io/github/forks/angular/devkit.svg?style=social&label=Fork)](https://github.com/angular/devkit/fork) [![GitHub stars](https://img.shields.io/github/stars/angular/devkit.svg?style=social&label=Star)](https://github.com/angular/devkit)
21+
[![GitHub forks](https://img.shields.io/github/forks/angular/devkit.svg?style=social&label=Fork)](https://github.com/angular/devkit/fork) [![GitHub stars](https://img.shields.io/github/stars/angular/devkit.svg?style=social&label=Star)](https://github.com/angular/devkit)
2222

2323

2424

@@ -27,7 +27,7 @@
2727
This is the home for all the tools and libraries built to assist developers with their Angular applications.
2828

2929
### Quick Links
30-
[Gitter](https://gitter.im/angular/angular-cli) | [Contributing](https://github.com/angular/devkit/blob/master/CONTRIBUTING.md) | [Angular CLI](http://github.com/angular/angular-cli) |
30+
[Gitter](https://gitter.im/angular/angular-cli) | [Contributing](https://github.com/angular/devkit/blob/master/CONTRIBUTING.md) | [Angular CLI](http://github.com/angular/angular-cli) |
3131
|---|---|---|
3232

3333

@@ -50,17 +50,20 @@ This is a monorepo which contains many packages:
5050

5151
| Project | Package | Version | Links |
5252
|---|---|---|---|
53+
**Build Facade** | [`@angular-devkit/architect`](http://npmjs.com/packages/@angular-devkit/architect) | [![latest](https://img.shields.io/npm/v/%40angular-devkit%2Farchitect/latest.svg)](http://npmjs.com/packages/@angular-devkit/architect) | [![README](https://img.shields.io/badge/README--green.svg)](https://github.com/angular/devkit/blob/master/packages/angular_devkit/architect/README.md)
54+
**Architect CLI** | [`@angular-devkit/architect-cli`](http://npmjs.com/packages/@angular-devkit/architect-cli) | [![latest](https://img.shields.io/npm/v/%40angular-devkit%2Farchitect-cli/latest.svg)](http://npmjs.com/packages/@angular-devkit/architect-cli) |
5355
**Build Optimizer** | [`@angular-devkit/build-optimizer`](http://npmjs.com/packages/@angular-devkit/build-optimizer) | [![latest](https://img.shields.io/npm/v/%40angular-devkit%2Fbuild-optimizer/latest.svg)](http://npmjs.com/packages/@angular-devkit/build-optimizer) | [![README](https://img.shields.io/badge/README--green.svg)](https://github.com/angular/devkit/blob/master/packages/angular_devkit/build_optimizer/README.md)
56+
**Webpack Build Facade** | [`@angular-devkit/build-webpack`](http://npmjs.com/packages/@angular-devkit/build-webpack) | [![latest](https://img.shields.io/npm/v/%40angular-devkit%2Fbuild-webpack/latest.svg)](http://npmjs.com/packages/@angular-devkit/build-webpack) | [![README](https://img.shields.io/badge/README--green.svg)](https://github.com/angular/devkit/blob/master/packages/angular_devkit/build_webpack/README.md)
5457
**Core** | [`@angular-devkit/core`](http://npmjs.com/packages/@angular-devkit/core) | [![latest](https://img.shields.io/npm/v/%40angular-devkit%2Fcore/latest.svg)](http://npmjs.com/packages/@angular-devkit/core) | [![README](https://img.shields.io/badge/README--green.svg)](https://github.com/angular/devkit/blob/master/packages/angular_devkit/core/README.md)
5558
**Schematics** | [`@angular-devkit/schematics`](http://npmjs.com/packages/@angular-devkit/schematics) | [![latest](https://img.shields.io/npm/v/%40angular-devkit%2Fschematics/latest.svg)](http://npmjs.com/packages/@angular-devkit/schematics) | [![README](https://img.shields.io/badge/README--green.svg)](https://github.com/angular/devkit/blob/master/packages/angular_devkit/schematics/README.md)
56-
**Schematics CLI** | [`@angular-devkit/schematics-cli`](http://npmjs.com/packages/@angular-devkit/schematics-cli) | [![latest](https://img.shields.io/npm/v/%40angular-devkit%2Fschematics-cli/latest.svg)](http://npmjs.com/packages/@angular-devkit/schematics-cli) |
59+
**Schematics CLI** | [`@angular-devkit/schematics-cli`](http://npmjs.com/packages/@angular-devkit/schematics-cli) | [![latest](https://img.shields.io/npm/v/%40angular-devkit%2Fschematics-cli/latest.svg)](http://npmjs.com/packages/@angular-devkit/schematics-cli) |
5760

5861
#### Schematics
5962

6063
| Project | Package | Version | Links |
6164
|---|---|---|---|
62-
**Angular Schematics** | [`@schematics/angular`](http://npmjs.com/packages/@schematics/angular) | [![latest](https://img.shields.io/npm/v/%40schematics%2Fangular/latest.svg)](http://npmjs.com/packages/@schematics/angular) |
63-
**Package JSON Update Schematics** | [`@schematics/package-update`](http://npmjs.com/packages/@schematics/package-update) | [![latest](https://img.shields.io/npm/v/%40schematics%2Fpackage-update/latest.svg)](http://npmjs.com/packages/@schematics/package-update) |
64-
**Schematics Schematics** | [`@schematics/schematics`](http://npmjs.com/packages/@schematics/schematics) | [![latest](https://img.shields.io/npm/v/%40schematics%2Fschematics/latest.svg)](http://npmjs.com/packages/@schematics/schematics) |
65+
**Angular Schematics** | [`@schematics/angular`](http://npmjs.com/packages/@schematics/angular) | [![latest](https://img.shields.io/npm/v/%40schematics%2Fangular/latest.svg)](http://npmjs.com/packages/@schematics/angular) |
66+
**Package JSON Update Schematics** | [`@schematics/package-update`](http://npmjs.com/packages/@schematics/package-update) | [![latest](https://img.shields.io/npm/v/%40schematics%2Fpackage-update/latest.svg)](http://npmjs.com/packages/@schematics/package-update) |
67+
**Schematics Schematics** | [`@schematics/schematics`](http://npmjs.com/packages/@schematics/schematics) | [![latest](https://img.shields.io/npm/v/%40schematics%2Fschematics/latest.svg)](http://npmjs.com/packages/@schematics/schematics) |
6568

6669

bin/architect

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env node
2+
/**
3+
* @license
4+
* Copyright Google Inc. All Rights Reserved.
5+
*
6+
* Use of this source code is governed by an MIT-style license that can be
7+
* found in the LICENSE file at https://angular.io/license
8+
*/
9+
'use strict';
10+
11+
12+
require('../lib/bootstrap-local');
13+
const packages = require('../lib/packages').packages;
14+
require(packages['@angular-devkit/architect'].bin['architect']);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Angular Build Facade
2+
3+
WIP
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "@angular-devkit/architect",
3+
"version": "0.0.0",
4+
"description": "Angular Build Facade",
5+
"main": "src/index.js",
6+
"typings": "src/index.d.ts",
7+
"scripts": {
8+
"preinstall": "echo DO NOT INSTALL THIS PROJECT, ONLY THE ROOT PROJECT. && exit 1"
9+
},
10+
"dependencies": {
11+
"@angular-devkit/core": "0.0.0",
12+
"rxjs": "^5.5.6"
13+
}
14+
}
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
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+
9+
import {
10+
BaseException,
11+
JsonObject,
12+
JsonParseMode,
13+
Path,
14+
dirname,
15+
getSystemPath,
16+
join,
17+
logging,
18+
normalize,
19+
parseJson,
20+
resolve,
21+
schema,
22+
virtualFs,
23+
} from '@angular-devkit/core';
24+
import { resolve as nodeResolve } from '@angular-devkit/core/node';
25+
import { Observable } from 'rxjs/Observable';
26+
import { of } from 'rxjs/observable/of';
27+
import { _throw } from 'rxjs/observable/throw';
28+
import { concatMap } from 'rxjs/operators';
29+
import {
30+
BuildEvent,
31+
Builder,
32+
BuilderConstructor,
33+
BuilderContext,
34+
BuilderDescription,
35+
BuilderMap,
36+
} from './builder';
37+
import { Workspace } from './workspace';
38+
39+
40+
export class ProjectNotFoundException extends BaseException {
41+
constructor(name?: string) {
42+
const nameOrDefault = name ? `Project '${name}'` : `Default project`;
43+
super(`${nameOrDefault} could not be found in workspace.`);
44+
}
45+
}
46+
47+
export class TargetNotFoundException extends BaseException {
48+
constructor(name?: string) {
49+
const nameOrDefault = name ? `Target '${name}'` : `Default target`;
50+
super(`${nameOrDefault} could not be found in workspace.`);
51+
}
52+
}
53+
54+
export class ConfigurationNotFoundException extends BaseException {
55+
constructor(name: string) {
56+
super(`Configuration '${name}' could not be found in project.`);
57+
}
58+
}
59+
60+
export class SchemaValidationException extends BaseException {
61+
constructor(errors: string[]) {
62+
super(`Schema validation failed with the following errors:\n ${errors.join('\n ')}`);
63+
}
64+
}
65+
66+
// TODO: break this exception apart into more granular ones.
67+
export class BuilderCannotBeResolvedException extends BaseException {
68+
constructor(builder: string) {
69+
super(`Builder '${builder}' cannot be resolved.`);
70+
}
71+
}
72+
73+
export class WorkspaceNotYetLoadedException extends BaseException {
74+
constructor() { super(`Workspace needs to be loaded before Architect is used.`); }
75+
}
76+
77+
export interface Target<OptionsT = {}> {
78+
root: Path;
79+
projectType: string;
80+
builder: string;
81+
options: OptionsT;
82+
}
83+
84+
export interface TargetOptions<OptionsT = {}> {
85+
project?: string;
86+
target?: string;
87+
configuration?: string;
88+
overrides?: Partial<OptionsT>;
89+
}
90+
91+
export class Architect {
92+
private readonly _workspaceSchema = join(normalize(__dirname), 'workspace-schema.json');
93+
private readonly _buildersSchema = join(normalize(__dirname), 'builders-schema.json');
94+
private _workspace: Workspace;
95+
96+
constructor(private _root: Path, private _host: virtualFs.Host<{}>) { }
97+
98+
loadWorkspaceFromHost(workspacePath: Path) {
99+
return this._host.read(join(this._root, workspacePath)).pipe(
100+
concatMap((buffer) => {
101+
const json = JSON.parse(virtualFs.fileBufferToString(buffer));
102+
103+
return this.loadWorkspaceFromJson(json);
104+
}),
105+
);
106+
}
107+
108+
loadWorkspaceFromJson(json: Workspace) {
109+
return this._validateAgainstSchema(json, this._workspaceSchema).pipe(
110+
concatMap((validatedWorkspace: Workspace) => {
111+
this._workspace = validatedWorkspace;
112+
113+
return of(this);
114+
}),
115+
);
116+
}
117+
118+
getTarget<OptionsT>(options: TargetOptions = {}): Target<OptionsT> {
119+
let { project, target: targetName } = options;
120+
const { configuration, overrides } = options;
121+
122+
if (!this._workspace) {
123+
throw new WorkspaceNotYetLoadedException();
124+
}
125+
126+
project = project || this._workspace.defaultProject as string;
127+
const workspaceProject = this._workspace.projects[project];
128+
129+
if (!workspaceProject) {
130+
throw new ProjectNotFoundException(project);
131+
}
132+
133+
targetName = targetName || workspaceProject.defaultTarget as string;
134+
const workspaceTarget = workspaceProject.targets[targetName];
135+
136+
if (!workspaceTarget) {
137+
throw new TargetNotFoundException(targetName);
138+
}
139+
140+
const workspaceTargetOptions = workspaceTarget.options;
141+
let workspaceConfiguration;
142+
143+
if (configuration) {
144+
workspaceConfiguration = workspaceTarget.configurations
145+
&& workspaceTarget.configurations[configuration];
146+
147+
if (!workspaceConfiguration) {
148+
throw new ConfigurationNotFoundException(configuration);
149+
}
150+
}
151+
152+
// Resolve root for the target.
153+
// TODO: add Path format to JSON schemas
154+
const target: Target<OptionsT> = {
155+
root: resolve(this._root, normalize(workspaceProject.root)),
156+
projectType: workspaceProject.projectType,
157+
builder: workspaceTarget.builder,
158+
options: {
159+
...workspaceTargetOptions,
160+
...workspaceConfiguration,
161+
...overrides as {},
162+
} as OptionsT,
163+
};
164+
165+
return target;
166+
}
167+
168+
// Will run the target using the target.
169+
run<OptionsT>(
170+
target: Target<OptionsT>,
171+
partialContext: Partial<BuilderContext> = {},
172+
): Observable<BuildEvent> {
173+
const context: BuilderContext = {
174+
logger: new logging.NullLogger(),
175+
architect: this,
176+
host: this._host,
177+
...partialContext,
178+
};
179+
180+
let builderDescription: BuilderDescription;
181+
182+
return this.getBuilderDescription(target).pipe(
183+
concatMap(description => {
184+
builderDescription = description;
185+
186+
return this.validateBuilderOptions(target, builderDescription);
187+
}),
188+
concatMap(() => of(this.getBuilder(builderDescription, context))),
189+
concatMap(builder => builder.run(target)),
190+
);
191+
}
192+
193+
getBuilderDescription<OptionsT>(target: Target<OptionsT>): Observable<BuilderDescription> {
194+
return new Observable((obs) => {
195+
// TODO: this probably needs to be more like NodeModulesEngineHost.
196+
const basedir = getSystemPath(this._root);
197+
const [pkg, builderName] = target.builder.split(':');
198+
const pkgJsonPath = nodeResolve(pkg, { basedir, resolvePackageJson: true });
199+
let buildersJsonPath: Path;
200+
201+
// Read the `builders` entry of package.json.
202+
return this._host.read(normalize(pkgJsonPath)).pipe(
203+
concatMap(buffer =>
204+
of(parseJson(virtualFs.fileBufferToString(buffer), JsonParseMode.Loose))),
205+
concatMap((pkgJson: JsonObject) => {
206+
const pkgJsonBuildersentry = pkgJson['builders'] as string;
207+
if (!pkgJsonBuildersentry) {
208+
throw new BuilderCannotBeResolvedException(target.builder);
209+
}
210+
211+
buildersJsonPath = join(dirname(normalize(pkgJsonPath)), pkgJsonBuildersentry);
212+
213+
return this._host.read(buildersJsonPath);
214+
}),
215+
concatMap((buffer) => of(JSON.parse(virtualFs.fileBufferToString(buffer)))),
216+
// Validate builders json.
217+
concatMap((builderMap) =>
218+
this._validateAgainstSchema<BuilderMap>(builderMap, this._buildersSchema)),
219+
220+
221+
concatMap((builderMap) => {
222+
const builderDescription = builderMap.builders[builderName];
223+
224+
if (!builderDescription) {
225+
throw new BuilderCannotBeResolvedException(target.builder);
226+
}
227+
228+
// Resolve paths in the builder description.
229+
const builderJsonDir = dirname(buildersJsonPath);
230+
builderDescription.schema = join(builderJsonDir, builderDescription.schema);
231+
builderDescription.class = join(builderJsonDir, builderDescription.class);
232+
233+
// Validate options again builder schema.
234+
return of(builderDescription);
235+
}),
236+
).subscribe(obs);
237+
});
238+
}
239+
240+
validateBuilderOptions<OptionsT>(
241+
target: Target<OptionsT>, builderDescription: BuilderDescription,
242+
): Observable<OptionsT> {
243+
return this._validateAgainstSchema<OptionsT>(target.options,
244+
normalize(builderDescription.schema));
245+
}
246+
247+
getBuilder<OptionsT>(
248+
builderDescription: BuilderDescription, context: BuilderContext,
249+
): Builder<OptionsT> {
250+
// TODO: support more than the default export, maybe via builder#import-name.
251+
const builderModule = require(getSystemPath(builderDescription.class));
252+
const builderClass = builderModule['default'] as BuilderConstructor<OptionsT>;
253+
254+
return new builderClass(context);
255+
}
256+
257+
// Warning: this method changes contentJson in place.
258+
// TODO: add transforms to resolve paths.
259+
private _validateAgainstSchema<T = {}>(contentJson: {}, schemaPath: Path): Observable<T> {
260+
const registry = new schema.CoreSchemaRegistry();
261+
262+
return this._host.read(schemaPath).pipe(
263+
concatMap((buffer) => of(JSON.parse(virtualFs.fileBufferToString(buffer)))),
264+
concatMap((schemaContent) => registry.compile(schemaContent)),
265+
concatMap(validator => validator(contentJson)),
266+
concatMap(validatorResult => {
267+
if (validatorResult.success) {
268+
return of(contentJson as T);
269+
} else {
270+
return _throw(new SchemaValidationException(validatorResult.errors as string[]));
271+
}
272+
}),
273+
);
274+
}
275+
}

0 commit comments

Comments
 (0)