From 03c4296c1b2984338e382a0b002657be1bc53773 Mon Sep 17 00:00:00 2001
From: Eaton
Date: Thu, 23 Aug 2018 16:54:32 -0400
Subject: [PATCH 0001/1703] docs: fix broken links to angular.io (#11811)
docs: fix broken links to angular.io
---
packages/schematics/angular/application/files/src/polyfills.ts | 2 +-
.../build_angular/hello-world-app/src/polyfills.ts | 2 +-
tests/angular_devkit/build_webpack/angular-app/src/polyfills.ts | 2 +-
tests/legacy-cli/e2e/assets/1.0-project/src/polyfills.ts | 2 +-
tests/legacy-cli/e2e/assets/1.7-project/src/polyfills.ts | 2 +-
5 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/packages/schematics/angular/application/files/src/polyfills.ts b/packages/schematics/angular/application/files/src/polyfills.ts
index d310405a6817..8fceb45d3b0a 100644
--- a/packages/schematics/angular/application/files/src/polyfills.ts
+++ b/packages/schematics/angular/application/files/src/polyfills.ts
@@ -11,7 +11,7 @@
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
- * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
+ * Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
diff --git a/tests/angular_devkit/build_angular/hello-world-app/src/polyfills.ts b/tests/angular_devkit/build_angular/hello-world-app/src/polyfills.ts
index 1e5ba9f031b6..d7fd9a7ba789 100644
--- a/tests/angular_devkit/build_angular/hello-world-app/src/polyfills.ts
+++ b/tests/angular_devkit/build_angular/hello-world-app/src/polyfills.ts
@@ -18,7 +18,7 @@
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
- * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
+ * Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
diff --git a/tests/angular_devkit/build_webpack/angular-app/src/polyfills.ts b/tests/angular_devkit/build_webpack/angular-app/src/polyfills.ts
index 1e5ba9f031b6..d7fd9a7ba789 100644
--- a/tests/angular_devkit/build_webpack/angular-app/src/polyfills.ts
+++ b/tests/angular_devkit/build_webpack/angular-app/src/polyfills.ts
@@ -18,7 +18,7 @@
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
- * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
+ * Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
diff --git a/tests/legacy-cli/e2e/assets/1.0-project/src/polyfills.ts b/tests/legacy-cli/e2e/assets/1.0-project/src/polyfills.ts
index 53bdaf1b8642..a8f13561140d 100644
--- a/tests/legacy-cli/e2e/assets/1.0-project/src/polyfills.ts
+++ b/tests/legacy-cli/e2e/assets/1.0-project/src/polyfills.ts
@@ -11,7 +11,7 @@
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
- * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
+ * Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
diff --git a/tests/legacy-cli/e2e/assets/1.7-project/src/polyfills.ts b/tests/legacy-cli/e2e/assets/1.7-project/src/polyfills.ts
index af84770782bb..e17a0b1ea437 100644
--- a/tests/legacy-cli/e2e/assets/1.7-project/src/polyfills.ts
+++ b/tests/legacy-cli/e2e/assets/1.7-project/src/polyfills.ts
@@ -11,7 +11,7 @@
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
- * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
+ * Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
From 0535e2b7389f594a68905c7acfd23e477207c51e Mon Sep 17 00:00:00 2001
From: Yi Qi
Date: Thu, 23 Aug 2018 15:02:15 -0700
Subject: [PATCH 0002/1703] feat(@angular/cli): allow schematic command to
specify the default colletion.
---
.../angular/cli/commands/generate-impl.ts | 3 +-
packages/angular/cli/commands/new-impl.ts | 4 +--
.../angular/cli/models/schematic-command.ts | 35 +++++++++++++++++--
packages/angular/cli/utilities/config.ts | 32 +----------------
4 files changed, 36 insertions(+), 38 deletions(-)
diff --git a/packages/angular/cli/commands/generate-impl.ts b/packages/angular/cli/commands/generate-impl.ts
index 812e5fe2ed24..204bee6d48db 100644
--- a/packages/angular/cli/commands/generate-impl.ts
+++ b/packages/angular/cli/commands/generate-impl.ts
@@ -9,7 +9,6 @@
// tslint:disable:no-global-tslint-disable no-any
import { tags, terminal } from '@angular-devkit/core';
import { SchematicCommand } from '../models/schematic-command';
-import { getDefaultSchematicCollection } from '../utilities/config';
export class GenerateCommand extends SchematicCommand {
@@ -61,7 +60,7 @@ export class GenerateCommand extends SchematicCommand {
}
private parseSchematicInfo(options: any) {
- let collectionName = getDefaultSchematicCollection();
+ let collectionName = this.getDefaultSchematicCollection();
let schematicName: string = options.schematic;
diff --git a/packages/angular/cli/commands/new-impl.ts b/packages/angular/cli/commands/new-impl.ts
index 0057fdcfbef5..b8663199c342 100644
--- a/packages/angular/cli/commands/new-impl.ts
+++ b/packages/angular/cli/commands/new-impl.ts
@@ -8,8 +8,6 @@
// tslint:disable:no-global-tslint-disable no-any
import { SchematicCommand } from '../models/schematic-command';
-import { getDefaultSchematicCollection } from '../utilities/config';
-
export class NewCommand extends SchematicCommand {
public readonly allowMissingWorkspace = true;
@@ -65,7 +63,7 @@ export class NewCommand extends SchematicCommand {
}
private parseCollectionName(options: any): string {
- const collectionName = options.collection || options.c || getDefaultSchematicCollection();
+ const collectionName = options.collection || options.c || this.getDefaultSchematicCollection();
return collectionName;
}
diff --git a/packages/angular/cli/models/schematic-command.ts b/packages/angular/cli/models/schematic-command.ts
index 81594ac651d2..9f78d5b450ed 100644
--- a/packages/angular/cli/models/schematic-command.ts
+++ b/packages/angular/cli/models/schematic-command.ts
@@ -40,9 +40,10 @@ import {
import { take } from 'rxjs/operators';
import { WorkspaceLoader } from '../models/workspace-loader';
import {
- getDefaultSchematicCollection,
getPackageManager,
+ getProjectByCwd,
getSchematicDefaults,
+ getWorkspace,
} from '../utilities/config';
import { ArgumentStrategy, Command, CommandContext, Option } from './command';
@@ -188,6 +189,36 @@ export abstract class SchematicCommand extends Command {
return this._workflow;
}
+ protected getDefaultSchematicCollection(): string {
+ let workspace = getWorkspace('local');
+
+ if (workspace) {
+ const project = getProjectByCwd(workspace);
+ if (project && workspace.getProjectCli(project)) {
+ const value = workspace.getProjectCli(project)['defaultCollection'];
+ if (typeof value == 'string') {
+ return value;
+ }
+ }
+ if (workspace.getCli()) {
+ const value = workspace.getCli()['defaultCollection'];
+ if (typeof value == 'string') {
+ return value;
+ }
+ }
+ }
+
+ workspace = getWorkspace('global');
+ if (workspace && workspace.getCli()) {
+ const value = workspace.getCli()['defaultCollection'];
+ if (typeof value == 'string') {
+ return value;
+ }
+ }
+
+ return '@schematics/angular';
+ }
+
protected runSchematic(options: RunSchematicOptions) {
const {collectionName, schematicName, debug, dryRun} = options;
let schematicOptions = this.removeCoreOptions(options.schematicOptions);
@@ -347,7 +378,7 @@ export abstract class SchematicCommand extends Command {
// Make a copy.
this._originalOptions = [...this.options];
- const collectionName = options.collectionName || getDefaultSchematicCollection();
+ const collectionName = options.collectionName || this.getDefaultSchematicCollection();
const collection = this.getCollection(collectionName);
diff --git a/packages/angular/cli/utilities/config.ts b/packages/angular/cli/utilities/config.ts
index d9fdb26e9aaf..abad429d7b1f 100644
--- a/packages/angular/cli/utilities/config.ts
+++ b/packages/angular/cli/utilities/config.ts
@@ -140,7 +140,7 @@ export function validateWorkspace(json: JsonObject) {
return true;
}
-function getProjectByCwd(workspace: experimental.workspace.Workspace): string | null {
+export function getProjectByCwd(workspace: experimental.workspace.Workspace): string | null {
try {
return workspace.getProjectByPath(normalize(process.cwd()));
} catch (e) {
@@ -265,36 +265,6 @@ function getLegacyPackageManager(): string | null {
return null;
}
-export function getDefaultSchematicCollection(): string {
- let workspace = getWorkspace('local');
-
- if (workspace) {
- const project = getProjectByCwd(workspace);
- if (project && workspace.getProjectCli(project)) {
- const value = workspace.getProjectCli(project)['defaultCollection'];
- if (typeof value == 'string') {
- return value;
- }
- }
- if (workspace.getCli()) {
- const value = workspace.getCli()['defaultCollection'];
- if (typeof value == 'string') {
- return value;
- }
- }
- }
-
- workspace = getWorkspace('global');
- if (workspace && workspace.getCli()) {
- const value = workspace.getCli()['defaultCollection'];
- if (typeof value == 'string') {
- return value;
- }
- }
-
- return '@schematics/angular';
-}
-
export function getSchematicDefaults(
collection: string,
schematic: string,
From c66f831a078736ce01f47d50bb7b2f74dc6ebfed Mon Sep 17 00:00:00 2001
From: ajspera
Date: Mon, 27 Aug 2018 11:24:26 -0600
Subject: [PATCH 0003/1703] fix(@angular-devkit/build-angular): load style
source maps inline so they work (#11729)
without this, the way styles are into the DOM breaks the default sourcemap option
postcss-loader option docs
https://github.com/postcss/postcss-loader#inline
fixes #9099
---
.../src/angular-cli-files/models/webpack-configs/styles.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/styles.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/styles.ts
index 09a267b2b173..b8d067df5163 100644
--- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/styles.ts
+++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/styles.ts
@@ -258,7 +258,7 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
options: {
ident: 'embedded',
plugins: postcssPluginCreator,
- sourceMap: cssSourceMap
+ sourceMap: cssSourceMap ? 'inline' : false
}
},
...(use as webpack.Loader[])
@@ -281,7 +281,7 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
options: {
ident: buildOptions.extractCss ? 'extracted' : 'embedded',
plugins: postcssPluginCreator,
- sourceMap: cssSourceMap
+ sourceMap: cssSourceMap && !buildOptions.extractCss ? 'inline' : cssSourceMap
}
},
...(use as webpack.Loader[])
From 907fedc0b8bebc227e7f70b3740babcaa4678d4a Mon Sep 17 00:00:00 2001
From: Filipe Silva
Date: Mon, 27 Aug 2018 18:26:30 +0100
Subject: [PATCH 0004/1703] feat(@angular-devkit/build-angular): use terser
instead of uglify-es (#11996)
Should help bring down prod build times, @SanderElias reported a ~60% reduction on his project.
Should fix #9340.
---
.../angular_devkit/build_angular/package.json | 2 +-
.../models/webpack-configs/common.ts | 8 +-
yarn.lock | 107 +++++++++++++++++-
3 files changed, 110 insertions(+), 7 deletions(-)
diff --git a/packages/angular_devkit/build_angular/package.json b/packages/angular_devkit/build_angular/package.json
index 558574ab3afd..35bffdbf66fa 100644
--- a/packages/angular_devkit/build_angular/package.json
+++ b/packages/angular_devkit/build_angular/package.json
@@ -46,7 +46,7 @@
"stylus": "^0.54.5",
"stylus-loader": "^3.0.2",
"tree-kill": "^1.2.0",
- "uglifyjs-webpack-plugin": "^1.2.5",
+ "terser-webpack-plugin": "^1.0.2",
"url-loader": "^1.0.1",
"webpack": "^4.15.1",
"webpack-dev-middleware": "^3.1.3",
diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts
index cab614154b63..c65d78fa07e5 100644
--- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts
+++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts
@@ -24,7 +24,7 @@ import { normalizeExtraEntryPoints } from './utils';
const ProgressPlugin = require('webpack/lib/ProgressPlugin');
const CircularDependencyPlugin = require('circular-dependency-plugin');
-const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
+const TerserPlugin = require('terser-webpack-plugin');
const StatsPlugin = require('stats-webpack-plugin');
/**
@@ -208,7 +208,7 @@ export function getCommonConfig(wco: WebpackConfigOptions) {
const isIvyEnabled = wco.tsConfig.raw.angularCompilerOptions
&& wco.tsConfig.raw.angularCompilerOptions.enableIvy;
- const uglifyOptions = {
+ const terserOptions = {
ecma: wco.supportES2015 ? 6 : 5,
warnings: !!buildOptions.verbose,
safari10: true,
@@ -318,11 +318,11 @@ export function getCommonConfig(wco: WebpackConfigOptions) {
// component styles retain their original file name
test: (file) => /\.(?:css|scss|sass|less|styl)$/.test(file),
}),
- new UglifyJSPlugin({
+ new TerserPlugin({
sourceMap: buildOptions.sourceMap,
parallel: true,
cache: true,
- uglifyOptions,
+ terserOptions,
}),
],
},
diff --git a/yarn.lock b/yarn.lock
index cf0ab04b672a..d04f305387cd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -500,6 +500,10 @@ agent-base@^4.1.0:
dependencies:
es6-promisify "^5.0.0"
+ajv-errors@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.0.tgz#ecf021fa108fd17dfb5e6b383f2dd233e31ffc59"
+
ajv-keywords@^3.1.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a"
@@ -1187,6 +1191,25 @@ cacache@^10.0.4:
unique-filename "^1.1.0"
y18n "^4.0.0"
+cacache@^11.0.2:
+ version "11.2.0"
+ resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.2.0.tgz#617bdc0b02844af56310e411c0878941d5739965"
+ dependencies:
+ bluebird "^3.5.1"
+ chownr "^1.0.1"
+ figgy-pudding "^3.1.0"
+ glob "^7.1.2"
+ graceful-fs "^4.1.11"
+ lru-cache "^4.1.3"
+ mississippi "^3.0.0"
+ mkdirp "^0.5.1"
+ move-concurrently "^1.0.1"
+ promise-inflight "^1.0.1"
+ rimraf "^2.6.2"
+ ssri "^6.0.0"
+ unique-filename "^1.1.0"
+ y18n "^4.0.0"
+
cache-base@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2"
@@ -2697,6 +2720,10 @@ fecha@^2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/fecha/-/fecha-2.3.3.tgz#948e74157df1a32fd1b12c3a3c3cdcb6ec9d96cd"
+figgy-pudding@^3.1.0:
+ version "3.4.1"
+ resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.4.1.tgz#af66da1991fa2f94ff7f33b545a38ea4b3869696"
+
file-loader@^1.1.11:
version "1.1.11"
resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-1.1.11.tgz#6fe886449b0f2a936e43cabaac0cdbfb369506f8"
@@ -2766,6 +2793,14 @@ find-cache-dir@^1.0.0:
make-dir "^1.0.0"
pkg-dir "^2.0.0"
+find-cache-dir@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.0.0.tgz#4c1faed59f45184530fb9d7fa123a4d04a98472d"
+ dependencies:
+ commondir "^1.0.1"
+ make-dir "^1.0.0"
+ pkg-dir "^3.0.0"
+
find-parent-dir@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/find-parent-dir/-/find-parent-dir-0.3.0.tgz#33c44b429ab2b2f0646299c5f9f718f376ff8d54"
@@ -4454,7 +4489,7 @@ lru-cache@2.2.x:
version "2.2.4"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.2.4.tgz#6c658619becf14031d0d0b594b16042ce4dc063d"
-lru-cache@^4.0.1, lru-cache@^4.1.1:
+lru-cache@^4.0.1, lru-cache@^4.1.1, lru-cache@^4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c"
dependencies:
@@ -4710,6 +4745,21 @@ mississippi@^2.0.0:
stream-each "^1.1.0"
through2 "^2.0.0"
+mississippi@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022"
+ dependencies:
+ concat-stream "^1.5.0"
+ duplexify "^3.4.2"
+ end-of-stream "^1.1.0"
+ flush-write-stream "^1.0.0"
+ from2 "^2.1.0"
+ parallel-transform "^1.1.0"
+ pump "^3.0.0"
+ pumpify "^1.3.3"
+ stream-each "^1.1.0"
+ through2 "^2.0.0"
+
mixin-deep@^1.2.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe"
@@ -5441,6 +5491,12 @@ pkg-dir@^2.0.0:
dependencies:
find-up "^2.1.0"
+pkg-dir@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3"
+ dependencies:
+ find-up "^3.0.0"
+
popper.js@^1.14.1:
version "1.14.4"
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.4.tgz#8eec1d8ff02a5a3a152dd43414a15c7b79fd69b6"
@@ -5635,6 +5691,13 @@ pump@^2.0.0, pump@^2.0.1:
end-of-stream "^1.1.0"
once "^1.3.1"
+pump@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
+ dependencies:
+ end-of-stream "^1.1.0"
+ once "^1.3.1"
+
pumpify@^1.3.3:
version "1.5.1"
resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce"
@@ -6195,6 +6258,14 @@ schema-utils@^0.4.0, schema-utils@^0.4.3, schema-utils@^0.4.4, schema-utils@^0.4
ajv "^6.1.0"
ajv-keywords "^3.1.0"
+schema-utils@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
+ dependencies:
+ ajv "^6.1.0"
+ ajv-errors "^1.0.0"
+ ajv-keywords "^3.1.0"
+
scss-tokenizer@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1"
@@ -6503,6 +6574,13 @@ source-map-support@~0.4.0:
dependencies:
source-map "^0.5.6"
+source-map-support@~0.5.6:
+ version "0.5.9"
+ resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f"
+ dependencies:
+ buffer-from "^1.0.0"
+ source-map "^0.6.0"
+
source-map-url@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
@@ -6660,6 +6738,10 @@ ssri@^5.2.4:
dependencies:
safe-buffer "^5.1.1"
+ssri@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.0.tgz#fc21bfc90e03275ac3e23d5a42e38b8a1cbc130d"
+
stack-trace@0.0.x:
version "0.0.10"
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
@@ -6884,6 +6966,27 @@ term-size@^1.2.0:
dependencies:
execa "^0.7.0"
+terser-webpack-plugin@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.0.2.tgz#b62dfdc4e59b0b5093665a765b234645b598d1a5"
+ dependencies:
+ cacache "^11.0.2"
+ find-cache-dir "^2.0.0"
+ schema-utils "^1.0.0"
+ serialize-javascript "^1.4.0"
+ source-map "^0.6.1"
+ terser "^3.8.1"
+ webpack-sources "^1.1.0"
+ worker-farm "^1.5.2"
+
+terser@^3.8.1:
+ version "3.8.1"
+ resolved "https://registry.yarnpkg.com/terser/-/terser-3.8.1.tgz#cb70070ac9e0a71add169dfb63c0a64fca2738ac"
+ dependencies:
+ commander "~2.16.0"
+ source-map "~0.6.1"
+ source-map-support "~0.5.6"
+
text-extensions@^1.0.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.7.0.tgz#faaaba2625ed746d568a23e4d0aacd9bf08a8b39"
@@ -7143,7 +7246,7 @@ uglify-to-browserify@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
-uglifyjs-webpack-plugin@^1.2.4, uglifyjs-webpack-plugin@^1.2.5:
+uglifyjs-webpack-plugin@^1.2.4:
version "1.2.7"
resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.7.tgz#57638dd99c853a1ebfe9d97b42160a8a507f9d00"
dependencies:
From d55deb928218089ea2eb291ae572dd59c63d07ae Mon Sep 17 00:00:00 2001
From: clydin <19598772+clydin@users.noreply.github.com>
Date: Mon, 27 Aug 2018 13:27:44 -0400
Subject: [PATCH 0005/1703] build: add bazel TS library for node tasks (#12008)
---
packages/angular_devkit/schematics/BUILD | 29 ++++++++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/packages/angular_devkit/schematics/BUILD b/packages/angular_devkit/schematics/BUILD
index db9835277204..613a672ff023 100644
--- a/packages/angular_devkit/schematics/BUILD
+++ b/packages/angular_devkit/schematics/BUILD
@@ -65,6 +65,7 @@ ts_library(
srcs = glob(
include = ["tasks/**/*.ts"],
exclude = [
+ "tasks/node/**/*.ts",
"tasks/**/*_spec.ts",
"tasks/**/*_benchmark.ts",
"tasks/tslint-fix/test/**/*",
@@ -84,6 +85,32 @@ ts_library(
],
)
+# @angular-devkit/schematics/tasks/node
+
+ts_library(
+ name = "tasks_node",
+ srcs = glob(
+ include = ["tasks/node/**/*.ts"],
+ exclude = [
+ "tasks/node/**/*_spec.ts",
+ "tasks/node/**/*_benchmark.ts",
+ ],
+ ),
+ module_name = "@angular-devkit/schematics/tasks/node",
+ module_root = "tasks/node/index.d.ts",
+ deps = [
+ ":schematics",
+ ":tasks",
+ "//packages/angular_devkit/core",
+ "//packages/angular_devkit/core:node",
+ "@rxjs",
+ "@rxjs//operators",
+ # @typings: node
+ # @typings: tslint
+ # @typings: typescript
+ ],
+)
+
ts_library(
name = "tasks_test_lib",
srcs = glob(
@@ -135,6 +162,7 @@ ts_library(
deps = [
":schematics",
":tasks",
+ ":tasks_node",
"//packages/angular_devkit/core",
"//packages/angular_devkit/core:node",
"@rxjs",
@@ -185,6 +213,7 @@ ts_library(
deps = [
":schematics",
":tasks",
+ ":tasks_node",
":tools",
"//packages/angular_devkit/core",
"@rxjs",
From 02bfde50d65c76900ab6885d5f0dc855b3e299ec Mon Sep 17 00:00:00 2001
From: Alex Eagle
Date: Mon, 27 Aug 2018 10:28:21 -0700
Subject: [PATCH 0006/1703] docs: add install instructions to pull a snapshot
build (#11988)
Would like to point users here when they ask why their bug was closed but the problem is still visible in `@latest`
---
docs/documentation/home.md | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/docs/documentation/home.md b/docs/documentation/home.md
index 83762c301ab0..c5674d45ce4f 100644
--- a/docs/documentation/home.md
+++ b/docs/documentation/home.md
@@ -2,7 +2,7 @@
# Angular CLI
-NOTE: this documentation is for Angular CLI 6. For Angular CLI 1.x go [here](1-x/home) instead.
+NOTE: this documentation is for Angular CLI 6. For Angular CLI 1.x go [here](1-x-home) instead.
### Overview
The Angular CLI is a tool to initialize, develop, scaffold and maintain [Angular](https://angular.io) applications
@@ -13,6 +13,10 @@ To install the Angular CLI:
npm install -g @angular/cli
```
+> If you get an error installing the CLI, this is an issue with your local npm setup on your machine, not a problem in Angular CLI.
+> Please have a look at the [fixing npm permissions page](https://docs.npmjs.com/getting-started/fixing-npm-permissions), [common errors page](https://docs.npmjs.com/troubleshooting/common-errors), [npm issue tracker](https://github.com/npm/npm/issues), or open a new issue if the problem you are experiencing isn't known.
+> To install a different version, see below.
+
Generating and serving an Angular project via a development server
[Create](new) and [run](serve) a new project:
```
@@ -63,3 +67,12 @@ End-to-end tests are run via [Protractor](http://www.protractortest.org/).
### Additional Information
There are several [stories](stories) which will walk you through setting up
additional aspects of Angular applications.
+
+### Installing a specific version
+The CLI is installed both globally (the command above with the `-g` argument to `npm install`) and also within the project. To install a different version of the CLI, you can just update the version locally within your project. The globally installed package will always delegate to the local one.
+
+There are several different versions available at any time:
+- Install a previous version, maybe because of a bug in the latest version. For example to get 6.0.2: `npm install @angular/cli@6.0.2`
+- Install the pre-release of a newer minor/major version, to try new features. For example to get 7.0.0-beta.3: `npm install @angular/cli@next`. Note that the `@next` version is only useful during beta periods.
+- Install a snapshot build from the latest passing run of our CI (angular-cli/master). This is useful if an issue was just fixed or a new feature just landed on master, but is not yet released: `npm install @angular/cli@github:angular/cli-builds` (or maybe better, find the particular SHA that you want to try:
+`npm install @angular/cli@github:angular/cli-builds#0123456789abcdef`)
From 62e72fea38108de31e412c6068cf55062298b4d4 Mon Sep 17 00:00:00 2001
From: Hans
Date: Mon, 27 Aug 2018 18:11:07 -0700
Subject: [PATCH 0007/1703] fix(@angular-devkit/architect): explicitly type
functions
Closes: #11992
---
.../angular_devkit/architect/testing/test-project-host.ts | 3 ++-
packages/angular_devkit/build_angular/test/utils.ts | 4 ++--
packages/angular_devkit/core/src/virtual-fs/host/test.ts | 6 +++---
3 files changed, 7 insertions(+), 6 deletions(-)
diff --git a/packages/angular_devkit/architect/testing/test-project-host.ts b/packages/angular_devkit/architect/testing/test-project-host.ts
index 4980f0a35bd9..82b75036cf56 100644
--- a/packages/angular_devkit/architect/testing/test-project-host.ts
+++ b/packages/angular_devkit/architect/testing/test-project-host.ts
@@ -8,6 +8,7 @@
import {
Path,
+ PathFragment,
basename,
dirname,
join,
@@ -128,7 +129,7 @@ export class TestProjectHost extends NodeJsSyncHost {
virtualFs.stringToFileBuffer(content.concat(str)));
}
- fileMatchExists(dir: string, regex: RegExp) {
+ fileMatchExists(dir: string, regex: RegExp): PathFragment | undefined {
const [fileName] = this.scopedSync().list(normalize(dir)).filter(name => name.match(regex));
return fileName || undefined;
diff --git a/packages/angular_devkit/build_angular/test/utils.ts b/packages/angular_devkit/build_angular/test/utils.ts
index 826c48524d3a..72a11bd0a605 100644
--- a/packages/angular_devkit/build_angular/test/utils.ts
+++ b/packages/angular_devkit/build_angular/test/utils.ts
@@ -7,13 +7,13 @@
*/
import { TestProjectHost } from '@angular-devkit/architect/testing';
-import { join, normalize } from '@angular-devkit/core';
+import { Path, join, normalize } from '@angular-devkit/core';
const devkitRoot = normalize((global as any)._DevKitRoot); // tslint:disable-line:no-any
const workspaceRoot = join(devkitRoot, 'tests/angular_devkit/build_angular/hello-world-app/');
export const host = new TestProjectHost(workspaceRoot);
-export const outputPath = normalize('dist');
+export const outputPath: Path = normalize('dist');
export const browserTargetSpec = { project: 'app', target: 'build' };
export const devServerTargetSpec = { project: 'app', target: 'serve' };
diff --git a/packages/angular_devkit/core/src/virtual-fs/host/test.ts b/packages/angular_devkit/core/src/virtual-fs/host/test.ts
index eb44fa557133..4efdae796f4b 100644
--- a/packages/angular_devkit/core/src/virtual-fs/host/test.ts
+++ b/packages/angular_devkit/core/src/virtual-fs/host/test.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import { Observable } from 'rxjs';
-import { Path, join, normalize } from '../path';
+import { Path, PathFragment, join, normalize } from '../path';
import { fileBufferToString, stringToFileBuffer } from './buffer';
import { FileBuffer, HostWatchEvent, HostWatchOptions, Stats } from './interface';
import { SimpleMemoryHost, SimpleMemoryHostStats } from './memory';
@@ -96,7 +96,7 @@ export class TestHost extends SimpleMemoryHost {
return super._rename(from, to);
}
- protected _list(path: Path) {
+ protected _list(path: Path): PathFragment[] {
this._records.push({ kind: 'list', path });
return super._list(path);
@@ -135,7 +135,7 @@ export class TestHost extends SimpleMemoryHost {
return fileBufferToString(super._read(normalize(path)));
}
- $list(path: string) {
+ $list(path: string): PathFragment[] {
return super._list(normalize(path));
}
From 1730c79b99e1744350f05585f20ab3a4fc220c55 Mon Sep 17 00:00:00 2001
From: Alan Agius
Date: Tue, 28 Aug 2018 03:12:04 +0200
Subject: [PATCH 0008/1703] =?UTF-8?q?fix(@angular-devkit/build-angular):?=
=?UTF-8?q?=20exclude=20`.map`=20files=20from=20budget=20=E2=80=A6=20(#120?=
=?UTF-8?q?12)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* fix(@angular-devkit/build-angular): exclude `.map` files from budget calculations
Closes #11999
---
.../utilities/bundle-calculator.ts | 7 ++--
.../test/browser/bundle-budgets_spec_large.ts | 35 +++++++++++++++++++
2 files changed, 40 insertions(+), 2 deletions(-)
diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator.ts
index 810899e0d868..a396b2f3801d 100644
--- a/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator.ts
+++ b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator.ts
@@ -64,6 +64,7 @@ class InitialCalculator extends Calculator {
const initialChunks = this.compilation.chunks.filter(chunk => chunk.isOnlyInitial());
const size: number = initialChunks
.reduce((files, chunk) => [...files, ...chunk.files], [])
+ .filter((file: string) => !file.endsWith('.map'))
.map((file: string) => this.compilation.assets[file].size())
.reduce((total: number, size: number) => total + size, 0);
return [{size, label: 'initial'}];
@@ -76,7 +77,7 @@ class InitialCalculator extends Calculator {
class AllScriptCalculator extends Calculator {
calculate() {
const size: number = Object.keys(this.compilation.assets)
- .filter(key => /\.js$/.test(key))
+ .filter(key => key.endsWith('.js'))
.map(key => this.compilation.assets[key])
.map(asset => asset.size())
.reduce((total: number, size: number) => total + size, 0);
@@ -90,6 +91,7 @@ class AllScriptCalculator extends Calculator {
class AllCalculator extends Calculator {
calculate() {
const size: number = Object.keys(this.compilation.assets)
+ .filter(key => !key.endsWith('.map'))
.map(key => this.compilation.assets[key].size())
.reduce((total: number, size: number) => total + size, 0);
return [{size, label: 'total'}];
@@ -102,7 +104,7 @@ class AllCalculator extends Calculator {
class AnyScriptCalculator extends Calculator {
calculate() {
return Object.keys(this.compilation.assets)
- .filter(key => /\.js$/.test(key))
+ .filter(key => key.endsWith('.js'))
.map(key => {
const asset = this.compilation.assets[key];
return {
@@ -119,6 +121,7 @@ class AnyScriptCalculator extends Calculator {
class AnyCalculator extends Calculator {
calculate() {
return Object.keys(this.compilation.assets)
+ .filter(key => !key.endsWith('.map'))
.map(key => {
const asset = this.compilation.assets[key];
return {
diff --git a/packages/angular_devkit/build_angular/test/browser/bundle-budgets_spec_large.ts b/packages/angular_devkit/build_angular/test/browser/bundle-budgets_spec_large.ts
index 918e691ba72b..56eb019a5e79 100644
--- a/packages/angular_devkit/build_angular/test/browser/bundle-budgets_spec_large.ts
+++ b/packages/angular_devkit/build_angular/test/browser/bundle-budgets_spec_large.ts
@@ -54,4 +54,39 @@ describe('Browser Builder bundle budgets', () => {
tap(() => expect(logger.includes('WARNING')).toBe(true)),
).toPromise().then(done, done.fail);
});
+
+ describe(`should ignore '.map' files`, () => {
+ it(`when 'intial' budget`, (done) => {
+ const overrides = {
+ optimization: true,
+ budgets: [{ type: 'initial', maximumError: '1mb' }],
+ };
+
+ runTargetSpec(host, browserTargetSpec, overrides, DefaultTimeout * 2).pipe(
+ tap((buildEvent) => expect(buildEvent.success).toBe(true)),
+ ).toPromise().then(done, done.fail);
+ });
+
+ it(`when 'all' budget`, (done) => {
+ const overrides = {
+ optimization: true,
+ budgets: [{ type: 'all', maximumError: '1mb' }],
+ };
+
+ runTargetSpec(host, browserTargetSpec, overrides, DefaultTimeout * 2).pipe(
+ tap((buildEvent) => expect(buildEvent.success).toBe(true)),
+ ).toPromise().then(done, done.fail);
+ });
+
+ it(`when 'any' budget`, (done) => {
+ const overrides = {
+ optimization: true,
+ budgets: [{ type: 'any', maximumError: '1mb' }],
+ };
+
+ runTargetSpec(host, browserTargetSpec, overrides, DefaultTimeout * 2).pipe(
+ tap((buildEvent) => expect(buildEvent.success).toBe(true)),
+ ).toPromise().then(done, done.fail);
+ });
+ });
});
From 8b7fa047d75c95d760903fa7dab8b7b71d5c90a5 Mon Sep 17 00:00:00 2001
From: Dmitry Teplov
Date: Tue, 28 Aug 2018 04:12:30 +0300
Subject: [PATCH 0009/1703] docs: fix generate library skip-install desc
(#11998)
---
docs/documentation/generate/library.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/documentation/generate/library.md b/docs/documentation/generate/library.md
index 2d62e7985834..2215d2c69db3 100644
--- a/docs/documentation/generate/library.md
+++ b/docs/documentation/generate/library.md
@@ -57,7 +57,7 @@ Generate a library project for Angular.
--skip-install
- Do not add dependencies to package.json.
+ Skip installing dependency packages.
From 3f94b2d8ba5fe58d71a19e8808628302794a6b81 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?C=C3=A9dric=20Exbrayat?=
Date: Tue, 28 Aug 2018 22:10:18 +0200
Subject: [PATCH 0010/1703] feat(@schematics/angular): remove useless import
for Ivy (#11874)
Using cli `6.2.0-beta.2` with the new `experimentalIvy` flag leads to:
ERROR in src/app/app.module.ts(1,1): error TS6133: 'BrowserModule' is declared but its value is never read.
---
.../schematics/angular/application/other-files/app.module.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/schematics/angular/application/other-files/app.module.ts b/packages/schematics/angular/application/other-files/app.module.ts
index 281482250cfa..4ef3c6e953a1 100644
--- a/packages/schematics/angular/application/other-files/app.module.ts
+++ b/packages/schematics/angular/application/other-files/app.module.ts
@@ -1,6 +1,6 @@
-import { BrowserModule } from '@angular/platform-browser';
+<% if (!experimentalIvy) { %>import { BrowserModule } from '@angular/platform-browser';<% } %>
import { NgModule } from '@angular/core';
-<% if (routing) { %>
+<% if (routing && !experimentalIvy) { %>
import { AppRoutingModule } from './app-routing.module';<% } %>
import { AppComponent } from './app.component';
From 7f67c64be1b84d4927bce882cb37cf323688dccc Mon Sep 17 00:00:00 2001
From: Alan Agius
Date: Tue, 28 Aug 2018 22:10:43 +0200
Subject: [PATCH 0011/1703] fix(@angular-devkit/build-angular): fixes cors
issues with karma (#11970)
Checking the stack trace in the issue one can noticed that the paths for the components are being served via the `webpack://` protocol which is causing a `cors` issue between `http` and `webpack` protocol
This PR removed the `webpack` protocol from the `devtoolModuleFilenameTemplate`
Closes #11966
---
.../build_angular/src/angular-cli-files/plugins/karma.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/karma.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/karma.ts
index 71060d02a597..92f4776ae7b6 100644
--- a/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/karma.ts
+++ b/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/karma.ts
@@ -144,6 +144,7 @@ const init: any = (config: any, emitter: any, customFileHandlers: any) => {
// Files need to be served from a custom path for Karma.
webpackConfig.output.path = '/_karma_webpack_/';
webpackConfig.output.publicPath = '/_karma_webpack_/';
+ webpackConfig.output.devtoolModuleFilenameTemplate = '[namespace]/[resource-path]?[loaders]';
let compiler: any;
try {
From 7eb080362ffdb137c00cb0572f50a7664db807de Mon Sep 17 00:00:00 2001
From: Alan Agius
Date: Tue, 28 Aug 2018 22:50:25 +0200
Subject: [PATCH 0012/1703] fix(@angular-devkit/build-angular): only collect
coverage from files under `sourceRoot` (#11974)
In some cases when having libraries within the workspace this is causing a `Maximum call stack size exceeded`, Also for libraries coverage should be collected with the respective `ng test`
Closes #11934
---
.../angular-cli-files/models/build-options.ts | 1 +
.../models/webpack-configs/test.ts | 6 ++-
.../build_angular/src/karma/index.ts | 6 ++-
.../test/karma/code-coverage_spec_large.ts | 48 +++++++++++++++++++
4 files changed, 58 insertions(+), 3 deletions(-)
diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/build-options.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/build-options.ts
index a81e728e45de..4dac5449818a 100644
--- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/build-options.ts
+++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/build-options.ts
@@ -74,6 +74,7 @@ export interface WebpackTestOptions extends BuildOptions {
export interface WebpackConfigOptions {
root: string;
projectRoot: string;
+ sourceRoot?: string;
buildOptions: T;
tsConfig: ts.ParsedCommandLine;
tsConfigPath: string;
diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/test.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/test.ts
index 9a2c6bf10945..4c8b05aa6008 100644
--- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/test.ts
+++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/test.ts
@@ -23,7 +23,7 @@ import { WebpackConfigOptions, WebpackTestOptions } from '../build-options';
export function getTestConfig(
wco: WebpackConfigOptions,
): webpack.Configuration {
- const { root, buildOptions } = wco;
+ const { root, buildOptions, sourceRoot: include } = wco;
const extraRules: webpack.Rule[] = [];
const extraPlugins: webpack.Plugin[] = [];
@@ -46,10 +46,12 @@ export function getTestConfig(
}
extraRules.push({
- test: /\.(js|ts)$/, loader: 'istanbul-instrumenter-loader',
+ test: /\.(js|ts)$/,
+ loader: 'istanbul-instrumenter-loader',
options: { esModules: true },
enforce: 'post',
exclude,
+ include,
});
}
diff --git a/packages/angular_devkit/build_angular/src/karma/index.ts b/packages/angular_devkit/build_angular/src/karma/index.ts
index f12b29015c5a..df1138edc85a 100644
--- a/packages/angular_devkit/build_angular/src/karma/index.ts
+++ b/packages/angular_devkit/build_angular/src/karma/index.ts
@@ -71,11 +71,13 @@ export class KarmaBuilder implements Builder {
karmaOptions.browsers = options.browsers.split(',');
}
+ const sourceRoot = builderConfig.sourceRoot && resolve(root, builderConfig.sourceRoot);
+
karmaOptions.buildWebpack = {
root: getSystemPath(root),
projectRoot: getSystemPath(projectRoot),
options: options as NormalizedKarmaBuilderSchema,
- webpackConfig: this._buildWebpackConfig(root, projectRoot, host,
+ webpackConfig: this._buildWebpackConfig(root, projectRoot, sourceRoot, host,
options as NormalizedKarmaBuilderSchema),
// Pass onto Karma to emit BuildEvents.
successCb: () => obs.next({ success: true }),
@@ -108,6 +110,7 @@ export class KarmaBuilder implements Builder {
private _buildWebpackConfig(
root: Path,
projectRoot: Path,
+ sourceRoot: Path | undefined,
host: virtualFs.Host,
options: NormalizedKarmaBuilderSchema,
) {
@@ -130,6 +133,7 @@ export class KarmaBuilder implements Builder {
wco = {
root: getSystemPath(root),
projectRoot: getSystemPath(projectRoot),
+ sourceRoot: sourceRoot && getSystemPath(sourceRoot),
// TODO: use only this.options, it contains all flags and configs items already.
buildOptions: compatOptions,
tsConfig,
diff --git a/packages/angular_devkit/build_angular/test/karma/code-coverage_spec_large.ts b/packages/angular_devkit/build_angular/test/karma/code-coverage_spec_large.ts
index b67d3d41108e..8440e1143420 100644
--- a/packages/angular_devkit/build_angular/test/karma/code-coverage_spec_large.ts
+++ b/packages/angular_devkit/build_angular/test/karma/code-coverage_spec_large.ts
@@ -56,4 +56,52 @@ describe('Karma Builder code coverage', () => {
}),
).toPromise().then(done, done.fail);
}, 120000);
+
+ it(`should collect coverage from paths in 'sourceRoot'`, (done) => {
+ const overrides: Partial = { codeCoverage: true };
+
+ const files: { [path: string]: string } = {
+ './dist/my-lib/index.d.ts': `
+ export declare const title = 'app';
+ `,
+ './dist/my-lib/index.js': `
+ export const title = 'app';
+ `,
+ './src/app/app.component.ts': `
+ import { Component } from '@angular/core';
+ import { title } from 'my-lib';
+
+ @Component({
+ selector: 'app-root',
+ templateUrl: './app.component.html',
+ styleUrls: ['./app.component.css']
+ })
+ export class AppComponent {
+ title = title;
+ }
+ `,
+ };
+
+ host.writeMultipleFiles(files);
+
+ host.replaceInFile('tsconfig.json', /"baseUrl": ".\/",/, `
+ "baseUrl": "./",
+ "paths": {
+ "my-lib": [
+ "./dist/my-lib"
+ ]
+ },
+ `);
+
+ runTargetSpec(host, karmaTargetSpec, overrides).pipe(
+ // It seems like the coverage files take a while being written to disk, so we wait 500ms here.
+ debounceTime(500),
+ tap(buildEvent => {
+ expect(buildEvent.success).toBe(true);
+ expect(host.scopedSync().exists(coverageFilePath)).toBe(true);
+ const content = virtualFs.fileBufferToString(host.scopedSync().read(coverageFilePath));
+ expect(content).not.toContain('my-lib');
+ }),
+ ).toPromise().then(done, done.fail);
+ }, 120000);
});
From 2dcf0b9901af011c02a7d425e23e4d4c04ba1100 Mon Sep 17 00:00:00 2001
From: Charles Lyding <19598772+clydin@users.noreply.github.com>
Date: Wed, 8 Aug 2018 11:39:38 -0400
Subject: [PATCH 0013/1703] refactor(@angular-devkit/core): cleanup ajv
initialization
---
.../core/src/json/schema/registry.ts | 23 ++++++++-----------
1 file changed, 9 insertions(+), 14 deletions(-)
diff --git a/packages/angular_devkit/core/src/json/schema/registry.ts b/packages/angular_devkit/core/src/json/schema/registry.ts
index 0965910a6273..530ea4d14888 100644
--- a/packages/angular_devkit/core/src/json/schema/registry.ts
+++ b/packages/angular_devkit/core/src/json/schema/registry.ts
@@ -7,7 +7,7 @@
*/
import * as ajv from 'ajv';
import * as http from 'http';
-import { Observable, from, of as observableOf } from 'rxjs';
+import { Observable, from, of as observableOf, throwError } from 'rxjs';
import { concatMap, map, switchMap, tap } from 'rxjs/operators';
import { BaseException } from '../../exception/exception';
import { PartiallyOrderedSet, isObservable } from '../../utils';
@@ -176,7 +176,7 @@ export class CoreSchemaRegistry implements SchemaRegistry {
validate = (validate.refVal as any)[(validate.refs as any)['#' + refHash]];
}
- return { context: validate, schema: validate && validate.schema as JsonObject };
+ return { context: validate, schema: validate.schema as JsonObject };
}
compile(schema: JsonObject): Observable {
@@ -186,23 +186,18 @@ export class CoreSchemaRegistry implements SchemaRegistry {
// in synchronous (if available).
let validator: Observable;
try {
- const maybeFnValidate = this._ajv.compile(schema);
- validator = observableOf(maybeFnValidate);
+ validator = observableOf(this._ajv.compile(schema));
} catch (e) {
// Propagate the error.
if (!(e instanceof (ajv.MissingRefError as {} as Function))) {
- throw e;
+ return throwError(e);
}
- validator = new Observable(obs => {
- this._ajv.compileAsync(schema)
- .then(validate => {
- obs.next(validate);
- obs.complete();
- }, err => {
- obs.error(err);
- });
- });
+ try {
+ validator = from(this._ajv.compileAsync(schema));
+ } catch (e) {
+ return throwError(e);
+ }
}
return validator
From 4207a15c4f68dccc89feba5c93dca948f71f51d9 Mon Sep 17 00:00:00 2001
From: Charles Lyding <19598772+clydin@users.noreply.github.com>
Date: Fri, 10 Aug 2018 14:37:55 -0400
Subject: [PATCH 0014/1703] fix(@angular-devkit/core): make smart default work
with default/required
---
packages/angular_devkit/core/src/json/schema/registry.ts | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/packages/angular_devkit/core/src/json/schema/registry.ts b/packages/angular_devkit/core/src/json/schema/registry.ts
index 530ea4d14888..5ad00609e965 100644
--- a/packages/angular_devkit/core/src/json/schema/registry.ts
+++ b/packages/angular_devkit/core/src/json/schema/registry.ts
@@ -208,6 +208,7 @@ export class CoreSchemaRegistry implements SchemaRegistry {
return visitJson(data, visitor, schema, this._resolver, validate);
})),
).pipe(
+ switchMap(updateData => this._applySmartDefaults(updateData)),
switchMap(updatedData => {
const result = validate(updatedData);
@@ -227,8 +228,8 @@ export class CoreSchemaRegistry implements SchemaRegistry {
}),
switchMap(([data, valid]) => {
if (valid) {
- return this._applySmartDefaults(data).pipe(
- ...[...this._post].map(visitor => concatMap(data => {
+ return observableOf(data).pipe(
+ ...[...this._post].map(visitor => concatMap((data: JsonValue) => {
return visitJson(data as JsonValue, visitor, schema, this._resolver, validate);
})),
).pipe(
From 516f52e3ddb5828d9f792fd740164fe894e4fdc1 Mon Sep 17 00:00:00 2001
From: Charles Lyding <19598772+clydin@users.noreply.github.com>
Date: Thu, 9 Aug 2018 14:18:41 -0400
Subject: [PATCH 0015/1703] feat(@angular-devkit/core): initial prompt provider
json schema support
---
packages/angular_devkit/core/package.json | 1 +
.../core/src/json/schema/interface.ts | 25 +-
.../core/src/json/schema/prompt_spec.ts | 364 ++++++++++++++++++
.../core/src/json/schema/registry.ts | 311 ++++++++++++---
.../core/src/json/schema/registry_spec.ts | 1 +
yarn.lock | 6 +-
6 files changed, 647 insertions(+), 61 deletions(-)
create mode 100644 packages/angular_devkit/core/src/json/schema/prompt_spec.ts
diff --git a/packages/angular_devkit/core/package.json b/packages/angular_devkit/core/package.json
index 1c310e7cb05d..b63e953adeec 100644
--- a/packages/angular_devkit/core/package.json
+++ b/packages/angular_devkit/core/package.json
@@ -10,6 +10,7 @@
"dependencies": {
"ajv": "~6.4.0",
"chokidar": "^2.0.3",
+ "fast-json-stable-stringify": "^2.0.0",
"source-map": "^0.5.6",
"rxjs": "^6.0.0"
}
diff --git a/packages/angular_devkit/core/src/json/schema/interface.ts b/packages/angular_devkit/core/src/json/schema/interface.ts
index 99af07451d46..6f0f092629c3 100644
--- a/packages/angular_devkit/core/src/json/schema/interface.ts
+++ b/packages/angular_devkit/core/src/json/schema/interface.ts
@@ -5,7 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
-import { Observable } from 'rxjs';
+import { Observable, SubscribableOrPromise } from 'rxjs';
import { JsonArray, JsonObject, JsonValue } from '../interface';
export type JsonPointer = string & {
@@ -57,9 +57,13 @@ export interface SchemaValidatorResult {
errors?: SchemaValidatorError[];
}
+export interface SchemaValidatorOptions {
+ withPrompts: boolean;
+}
+
export interface SchemaValidator {
// tslint:disable-next-line:no-any
- (data: any): Observable;
+ (data: any, options?: Partial): Observable;
}
export interface SchemaFormatter {
@@ -90,8 +94,25 @@ export interface SchemaKeywordValidator {
): boolean | Observable;
}
+export interface PromptDefinition {
+ id: string;
+ type: string;
+ message: string;
+ default?: string | number | boolean | null;
+ priority: number;
+ validator?: (value: string) => boolean | string | Promise;
+
+ items?: Array;
+
+ raw?: string | JsonObject;
+}
+
+export type PromptProvider = (definitions: Array)
+ => SubscribableOrPromise<{ [id: string]: JsonValue }>;
+
export interface SchemaRegistry {
compile(schema: Object): Observable;
addFormat(format: SchemaFormat): void;
addSmartDefaultProvider(source: string, provider: SmartDefaultProvider): void;
+ usePromptProvider(provider: PromptProvider): void;
}
diff --git a/packages/angular_devkit/core/src/json/schema/prompt_spec.ts b/packages/angular_devkit/core/src/json/schema/prompt_spec.ts
new file mode 100644
index 000000000000..40d5f89543c9
--- /dev/null
+++ b/packages/angular_devkit/core/src/json/schema/prompt_spec.ts
@@ -0,0 +1,364 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+// tslint:disable:no-any no-big-function
+import { map, mergeMap } from 'rxjs/operators';
+import { CoreSchemaRegistry } from './registry';
+
+
+describe('Prompt Provider', () => {
+ it('sets properties with answer', done => {
+ const registry = new CoreSchemaRegistry();
+ const data: any = {};
+
+ registry.usePromptProvider(async definitions => {
+ return { [definitions[0].id]: true };
+ });
+
+ registry
+ .compile({
+ properties: {
+ test: {
+ type: 'boolean',
+ 'x-prompt': 'test-message',
+ },
+ },
+ })
+ .pipe(
+ mergeMap(validator => validator(data)),
+ map(() => {
+ expect(data.test).toBe(true);
+ }),
+ )
+ .toPromise().then(done, done.fail);
+ });
+
+ it('supports mixed schema references', done => {
+ const registry = new CoreSchemaRegistry();
+ const data: any = {};
+
+ registry.usePromptProvider(async definitions => {
+ return {
+ '\'bool\'': true,
+ '\'test\'': 'two',
+ '\'obj\'/\'deep\'/\'three\'': 'test3-answer',
+ };
+ });
+
+ registry
+ .compile({
+ properties: {
+ bool: {
+ $ref: '#/definitions/example',
+ },
+ test: {
+ type: 'string',
+ enum: [
+ 'one',
+ 'two',
+ 'three',
+ ],
+ 'x-prompt': {
+ type: 'list',
+ 'message': 'other-message',
+ },
+ },
+ obj: {
+ properties: {
+ deep: {
+ properties: {
+ three: {
+ $ref: '#/definitions/test3',
+ },
+ },
+ },
+ },
+ },
+ },
+ definitions: {
+ example: {
+ type: 'boolean',
+ 'x-prompt': 'example-message',
+ },
+ test3: {
+ type: 'string',
+ 'x-prompt': 'test3-message',
+ },
+ },
+ })
+ .pipe(
+ mergeMap(validator => validator(data)),
+ map(result => {
+ expect(result.success).toBe(true);
+ expect(data.bool).toBe(true);
+ expect(data.test).toBe('two');
+ expect(data.obj.deep.three).toEqual('test3-answer');
+ }),
+ )
+ .toPromise().then(done, done.fail);
+ });
+
+ describe('with shorthand', () => {
+ it('supports message value', done => {
+ const registry = new CoreSchemaRegistry();
+ const data: any = {};
+
+ registry.usePromptProvider(async definitions => {
+ expect(definitions.length).toBe(1);
+ expect(definitions[0].message).toBe('test-message');
+
+ return {};
+ });
+
+ registry
+ .compile({
+ properties: {
+ test: {
+ type: 'string',
+ 'x-prompt': 'test-message',
+ },
+ },
+ })
+ .pipe(
+ mergeMap(validator => validator(data)),
+ )
+ .toPromise().then(done, done.fail);
+ });
+
+ it('analyzes enums', done => {
+ const registry = new CoreSchemaRegistry();
+ const data: any = {};
+
+ registry.usePromptProvider(async definitions => {
+ expect(definitions.length).toBe(1);
+ expect(definitions[0].type).toBe('list');
+ expect(definitions[0].items).toEqual([
+ 'one',
+ 'two',
+ 'three',
+ ]);
+
+ return {};
+ });
+
+ registry
+ .compile({
+ properties: {
+ test: {
+ type: 'string',
+ enum: [
+ 'one',
+ 'two',
+ 'three',
+ ],
+ 'x-prompt': 'test-message',
+ },
+ },
+ })
+ .pipe(
+ mergeMap(validator => validator(data)),
+ )
+ .toPromise().then(done, done.fail);
+ });
+
+ it('analyzes boolean properties', done => {
+ const registry = new CoreSchemaRegistry();
+ const data: any = {};
+
+ registry.usePromptProvider(async definitions => {
+ expect(definitions.length).toBe(1);
+ expect(definitions[0].type).toBe('confirmation');
+ expect(definitions[0].items).toBeUndefined();
+
+ return {};
+ });
+
+ registry
+ .compile({
+ properties: {
+ test: {
+ type: 'boolean',
+ 'x-prompt': 'test-message',
+ },
+ },
+ })
+ .pipe(
+ mergeMap(validator => validator(data)),
+ )
+ .toPromise().then(done, done.fail);
+ });
+
+ });
+
+ describe('with longhand', () => {
+ it('supports message option', done => {
+ const registry = new CoreSchemaRegistry();
+ const data: any = {};
+
+ registry.usePromptProvider(async definitions => {
+ expect(definitions.length).toBe(1);
+ expect(definitions[0].message).toBe('test-message');
+
+ return {};
+ });
+
+ registry
+ .compile({
+ properties: {
+ test: {
+ type: 'string',
+ 'x-prompt': {
+ 'message': 'test-message',
+ },
+ },
+ },
+ })
+ .pipe(
+ mergeMap(validator => validator(data)),
+ )
+ .toPromise().then(done, done.fail);
+ });
+
+ it('analyzes enums WITH explicit list type', done => {
+ const registry = new CoreSchemaRegistry();
+ const data: any = {};
+
+ registry.usePromptProvider(async definitions => {
+ expect(definitions.length).toBe(1);
+ expect(definitions[0].type).toBe('list');
+ expect(definitions[0].items).toEqual([
+ 'one',
+ 'two',
+ 'three',
+ ]);
+
+ return { [definitions[0].id]: 'one' };
+ });
+
+ registry
+ .compile({
+ properties: {
+ test: {
+ type: 'string',
+ enum: [
+ 'one',
+ 'two',
+ 'three',
+ ],
+ 'x-prompt': {
+ 'type': 'list',
+ 'message': 'test-message',
+ },
+ },
+ },
+ })
+ .pipe(
+ mergeMap(validator => validator(data)),
+ )
+ .toPromise().then(done, done.fail);
+ });
+
+ it('analyzes enums WITHOUT explicit list type', done => {
+ const registry = new CoreSchemaRegistry();
+ const data: any = {};
+
+ registry.usePromptProvider(async definitions => {
+ expect(definitions.length).toBe(1);
+ expect(definitions[0].type).toBe('list');
+ expect(definitions[0].items).toEqual([
+ 'one',
+ 'two',
+ 'three',
+ ]);
+
+ return {};
+ });
+
+ registry
+ .compile({
+ properties: {
+ test: {
+ type: 'string',
+ enum: [
+ 'one',
+ 'two',
+ 'three',
+ ],
+ 'x-prompt': {
+ 'message': 'test-message',
+ },
+ },
+ },
+ })
+ .pipe(
+ mergeMap(validator => validator(data)),
+ )
+ .toPromise().then(done, done.fail);
+ });
+
+ it('analyzes boolean properties', done => {
+ const registry = new CoreSchemaRegistry();
+ const data: any = {};
+
+ registry.usePromptProvider(async definitions => {
+ expect(definitions.length).toBe(1);
+ expect(definitions[0].type).toBe('confirmation');
+ expect(definitions[0].items).toBeUndefined();
+
+ return {};
+ });
+
+ registry
+ .compile({
+ properties: {
+ test: {
+ type: 'boolean',
+ 'x-prompt': {
+ 'message': 'test-message',
+ },
+ },
+ },
+ })
+ .pipe(
+ mergeMap(validator => validator(data)),
+ )
+ .toPromise().then(done, done.fail);
+ });
+
+ it('allows prompt type override', done => {
+ const registry = new CoreSchemaRegistry();
+ const data: any = {};
+
+ registry.usePromptProvider(async definitions => {
+ expect(definitions.length).toBe(1);
+ expect(definitions[0].type).toBe('input');
+ expect(definitions[0].items).toBeUndefined();
+
+ return {};
+ });
+
+ registry
+ .compile({
+ properties: {
+ test: {
+ type: 'boolean',
+ 'x-prompt': {
+ 'type': 'input',
+ 'message': 'test-message',
+ },
+ },
+ },
+ })
+ .pipe(
+ mergeMap(validator => validator(data)),
+ )
+ .toPromise().then(done, done.fail);
+ });
+
+ });
+
+});
diff --git a/packages/angular_devkit/core/src/json/schema/registry.ts b/packages/angular_devkit/core/src/json/schema/registry.ts
index 5ad00609e965..27e82270ad69 100644
--- a/packages/angular_devkit/core/src/json/schema/registry.ts
+++ b/packages/angular_devkit/core/src/json/schema/registry.ts
@@ -13,17 +13,21 @@ import { BaseException } from '../../exception/exception';
import { PartiallyOrderedSet, isObservable } from '../../utils';
import { JsonObject, JsonValue } from '../interface';
import {
+ PromptDefinition,
+ PromptProvider,
SchemaFormat,
SchemaFormatter,
SchemaRegistry,
SchemaValidator,
SchemaValidatorError,
+ SchemaValidatorOptions,
SchemaValidatorResult,
SmartDefaultProvider,
} from './interface';
import { addUndefinedDefaults } from './transforms';
import { JsonVisitor, visitJson } from './visitor';
+const serialize = require('fast-json-stable-stringify');
// This interface should be exported from ajv, but they only export the class and not the type.
interface AjvValidationError {
@@ -69,15 +73,22 @@ export class SchemaValidationException extends BaseException {
}
}
+interface SchemaInfo {
+ smartDefaultRecord: Map;
+ promptDefinitions: Array;
+}
+
export class CoreSchemaRegistry implements SchemaRegistry {
private _ajv: ajv.Ajv;
private _uriCache = new Map();
private _pre = new PartiallyOrderedSet();
private _post = new PartiallyOrderedSet();
+ private _currentCompilationSchemaInfo?: SchemaInfo;
+ private _validatorCache = new Map();
private _smartDefaultKeyword = false;
+ private _promptProvider?: PromptProvider;
private _sourceMap = new Map>();
- private _smartDefaultRecord = new Map();
constructor(formats: SchemaFormat[] = []) {
/**
@@ -91,10 +102,10 @@ export class CoreSchemaRegistry implements SchemaRegistry {
}
this._ajv = ajv({
- useDefaults: true,
formats: formatsObj,
loadSchema: (uri: string) => this._fetch(uri),
schemaId: 'auto',
+ passContext: true,
});
this._ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json'));
@@ -176,16 +187,28 @@ export class CoreSchemaRegistry implements SchemaRegistry {
validate = (validate.refVal as any)[(validate.refs as any)['#' + refHash]];
}
- return { context: validate, schema: validate.schema as JsonObject };
+ return { context: validate, schema: validate && validate.schema as JsonObject };
}
compile(schema: JsonObject): Observable {
+ const schemaKey = serialize(schema);
+ const existingValidator = this._validatorCache.get(schemaKey);
+ if (existingValidator) {
+ return observableOf(existingValidator);
+ }
+
+ const schemaInfo: SchemaInfo = {
+ smartDefaultRecord: new Map(),
+ promptDefinitions: [],
+ };
+
// Supports both synchronous and asynchronous compilation, by trying the synchronous
// version first, then if refs are missing this will fails.
// We also add any refs from external fetched schemas so that those will also be used
// in synchronous (if available).
let validator: Observable;
try {
+ this._currentCompilationSchemaInfo = schemaInfo;
validator = observableOf(this._ajv.compile(schema));
} catch (e) {
// Propagate the error.
@@ -202,15 +225,26 @@ export class CoreSchemaRegistry implements SchemaRegistry {
return validator
.pipe(
- map(validate => (data: JsonValue): Observable => {
+ map(validate => (data, options) => {
+ const validationOptions: SchemaValidatorOptions = {
+ withPrompts: true,
+ ...options,
+ };
+ const validationContext = {
+ promptFieldsWithValue: new Set(),
+ };
+
return observableOf(data).pipe(
...[...this._pre].map(visitor => concatMap((data: JsonValue) => {
return visitJson(data, visitor, schema, this._resolver, validate);
})),
).pipe(
- switchMap(updateData => this._applySmartDefaults(updateData)),
- switchMap(updatedData => {
- const result = validate(updatedData);
+ switchMap(updateData => this._applySmartDefaults(
+ updateData,
+ schemaInfo.smartDefaultRecord,
+ )),
+ switchMap((updatedData: JsonValue) => {
+ const result = validate.call(validationContext, updatedData);
return typeof result == 'boolean'
? observableOf([updatedData, result])
@@ -226,6 +260,22 @@ export class CoreSchemaRegistry implements SchemaRegistry {
return Promise.reject(err);
}));
}),
+ switchMap(([data, valid]) => {
+ if (!validationOptions.withPrompts) {
+ return observableOf([data, valid]);
+ }
+
+ const definitions = schemaInfo.promptDefinitions
+ .filter(def => !validationContext.promptFieldsWithValue.has(def.id));
+
+ if (valid && this._promptProvider && definitions.length > 0) {
+ return from(this._applyPrompts(data, definitions)).pipe(
+ map(data => [data, valid]),
+ );
+ } else {
+ return observableOf([data, valid]);
+ }
+ }),
switchMap(([data, valid]) => {
if (valid) {
return observableOf(data).pipe(
@@ -252,6 +302,7 @@ export class CoreSchemaRegistry implements SchemaRegistry {
}),
);
}),
+ tap(v => this._validatorCache.set(schemaKey, v)),
);
}
@@ -289,10 +340,15 @@ export class CoreSchemaRegistry implements SchemaRegistry {
errors: false,
valid: true,
compile: (schema, _parentSchema, it) => {
+ const compilationSchemInfo = this._currentCompilationSchemaInfo;
+ if (!compilationSchemInfo) {
+ throw new Error('Invalid JSON schema compilation state');
+ }
+
// We cheat, heavily.
- this._smartDefaultRecord.set(
+ compilationSchemInfo.smartDefaultRecord.set(
// tslint:disable-next-line:no-any
- JSON.stringify((it as any).dataPathArr.slice(1, (it as any).dataLevel + 1) as string[]),
+ JSON.stringify((it as any).dataPathArr.slice(1, it.dataLevel + 1) as string[]),
schema,
);
@@ -310,70 +366,209 @@ export class CoreSchemaRegistry implements SchemaRegistry {
}
}
- // tslint:disable-next-line:no-any
- private _applySmartDefaults(data: any): Observable {
- function _set(
- // tslint:disable-next-line:no-any
- data: any,
- fragments: string[],
- value: {},
- // tslint:disable-next-line:no-any
- parent: any | null = null,
- parentProperty?: string,
- ): void {
- for (let i = 0; i < fragments.length; i++) {
- const f = fragments[i];
-
- if (f[0] == 'i') {
- if (!Array.isArray(data)) {
- return;
+ usePromptProvider(provider: PromptProvider) {
+ const isSetup = !!this._promptProvider;
+
+ this._promptProvider = provider;
+
+ if (isSetup) {
+ return;
+ }
+
+ this._ajv.addKeyword('x-prompt', {
+ errors: false,
+ valid: true,
+ compile: (schema, parentSchema: JsonObject, it) => {
+ const compilationSchemInfo = this._currentCompilationSchemaInfo;
+ if (!compilationSchemInfo) {
+ throw new Error('Invalid JSON schema compilation state');
+ }
+
+ // tslint:disable-next-line:no-any
+ const pathArray = ((it as any).dataPathArr as string[]).slice(1, it.dataLevel + 1);
+ const path = pathArray.join('/');
+
+ let type: string | undefined;
+ let items: Array | undefined;
+ let message: string;
+ if (typeof schema == 'string') {
+ message = schema;
+ } else {
+ message = schema.message;
+ type = schema.type;
+ items = schema.items;
+ }
+
+ if (!type) {
+ if (parentSchema.type === 'boolean') {
+ type = 'confirmation';
+ } else if (Array.isArray(parentSchema.enum)) {
+ type = 'list';
+ } else {
+ type = 'input';
}
+ }
- for (let j = 0; j < data.length; j++) {
- _set(data[j], fragments.slice(i + 1), value, data, '' + j);
+ if (type === 'list' && !items) {
+ if (Array.isArray(parentSchema.enum)) {
+ type = 'list';
+ items = [];
+ for (const value of parentSchema.enum) {
+ if (typeof value == 'string') {
+ items.push(value);
+ } else if (typeof value == 'object') {
+ // Invalid
+ } else {
+ items.push({ label: value.toString(), value });
+ }
+ }
}
+ }
- return;
- } else if (f.startsWith('key')) {
- if (typeof data !== 'object') {
- return;
+ const definition: PromptDefinition = {
+ id: path,
+ type,
+ message,
+ priority: 0,
+ raw: schema,
+ items,
+ default: typeof parentSchema.default == 'object' ? undefined : parentSchema.default,
+ async validator(data: string) {
+ const result = it.self.validate(parentSchema, data);
+ if (typeof result === 'boolean') {
+ return result;
+ } else {
+ try {
+ await result;
+
+ return true;
+ } catch {
+ return false;
+ }
+ }
+ },
+ };
+
+ compilationSchemInfo.promptDefinitions.push(definition);
+
+ return function(this: { promptFieldsWithValue: Set }) {
+ if (this) {
+ this.promptFieldsWithValue.add(path);
}
- Object.getOwnPropertyNames(data).forEach(property => {
- _set(data[property], fragments.slice(i + 1), value, data, property);
- });
+ return true;
+ };
+ },
+ metaSchema: {
+ oneOf: [
+ { type: 'string' },
+ {
+ type: 'object',
+ properties: {
+ 'type': { type: 'string' },
+ 'message': { type: 'string' },
+ },
+ additionalProperties: true,
+ required: [ 'message' ],
+ },
+ ],
+ },
+ });
+ }
+
+ private _applyPrompts(data: T, prompts: Array): Observable {
+ const provider = this._promptProvider;
+ if (!provider) {
+ return observableOf(data);
+ }
+
+ prompts.sort((a, b) => b.priority - a.priority);
+
+ return from(provider(prompts)).pipe(
+ map(answers => {
+ for (const path in answers) {
+ CoreSchemaRegistry._set(
+ data,
+ path.split('/'),
+ answers[path] as {},
+ null,
+ undefined,
+ true,
+ );
+ }
+
+ return data;
+ }),
+ );
+ }
+ private static _set(
+ // tslint:disable-next-line:no-any
+ data: any,
+ fragments: string[],
+ value: {},
+ // tslint:disable-next-line:no-any
+ parent: any | null = null,
+ parentProperty?: string,
+ force?: boolean,
+ ): void {
+ for (let i = 0; i < fragments.length; i++) {
+ const f = fragments[i];
+
+ if (f[0] == 'i') {
+ if (!Array.isArray(data)) {
return;
- } else if (f.startsWith('\'') && f[f.length - 1] == '\'') {
- const property = f
- .slice(1, -1)
- .replace(/\\'/g, '\'')
- .replace(/\\n/g, '\n')
- .replace(/\\r/g, '\r')
- .replace(/\\f/g, '\f')
- .replace(/\\t/g, '\t');
-
- // We know we need an object because the fragment is a property key.
- if (!data && parent !== null && parentProperty) {
- data = parent[parentProperty] = {};
- }
- parent = data;
- parentProperty = property;
+ }
- data = data[property];
- } else {
+ for (let j = 0; j < data.length; j++) {
+ CoreSchemaRegistry._set(data[j], fragments.slice(i + 1), value, data, '' + j);
+ }
+
+ return;
+ } else if (f.startsWith('key')) {
+ if (typeof data !== 'object') {
return;
}
- }
- if (parent && parentProperty && parent[parentProperty] === undefined) {
- parent[parentProperty] = value;
+ Object.getOwnPropertyNames(data).forEach(property => {
+ CoreSchemaRegistry._set(data[property], fragments.slice(i + 1), value, data, property);
+ });
+
+ return;
+ } else if (f.startsWith('\'') && f[f.length - 1] == '\'') {
+ const property = f
+ .slice(1, -1)
+ .replace(/\\'/g, '\'')
+ .replace(/\\n/g, '\n')
+ .replace(/\\r/g, '\r')
+ .replace(/\\f/g, '\f')
+ .replace(/\\t/g, '\t');
+
+ // We know we need an object because the fragment is a property key.
+ if (!data && parent !== null && parentProperty) {
+ data = parent[parentProperty] = {};
+ }
+ parent = data;
+ parentProperty = property;
+
+ data = data[property];
+ } else {
+ return;
}
}
+ if (parent && parentProperty && (force || parent[parentProperty] === undefined)) {
+ parent[parentProperty] = value;
+ }
+ }
+
+ private _applySmartDefaults(
+ data: T,
+ smartDefaults: Map,
+ ): Observable {
return observableOf(data).pipe(
- ...[...this._smartDefaultRecord.entries()].map(([pointer, schema]) => {
- return concatMap(data => {
+ ...[...smartDefaults.entries()].map(([pointer, schema]) => {
+ return concatMap(data => {
const fragments = JSON.parse(pointer);
const source = this._sourceMap.get((schema as JsonObject).$source as string);
@@ -385,7 +580,7 @@ export class CoreSchemaRegistry implements SchemaRegistry {
return (value as Observable<{}>).pipe(
// Synchronously set the new data at the proper JsonSchema path.
- tap(x => _set(data, fragments, x)),
+ tap(x => CoreSchemaRegistry._set(data, fragments, x)),
// But return the data object.
map(() => data),
);
diff --git a/packages/angular_devkit/core/src/json/schema/registry_spec.ts b/packages/angular_devkit/core/src/json/schema/registry_spec.ts
index 4b47be28b2d8..773a87a479bf 100644
--- a/packages/angular_devkit/core/src/json/schema/registry_spec.ts
+++ b/packages/angular_devkit/core/src/json/schema/registry_spec.ts
@@ -424,4 +424,5 @@ describe('CoreSchemaRegistry', () => {
)
.toPromise().then(done, done.fail);
});
+
});
diff --git a/yarn.lock b/yarn.lock
index d04f305387cd..d5e23db55b07 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3813,6 +3813,10 @@ is-primitive@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575"
+is-promise@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
+
is-redirect@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
@@ -4408,7 +4412,7 @@ lodash.templatesettings@^4.0.0:
dependencies:
lodash._reinterpolate "~3.0.0"
-lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.5.0, lodash@~4.17.10:
+lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.0, lodash@~4.17.10:
version "4.17.10"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
From 489f0e905576f3c4ad821d4385d771b0f4bcb31d Mon Sep 17 00:00:00 2001
From: Charles Lyding <19598772+clydin@users.noreply.github.com>
Date: Thu, 9 Aug 2018 14:19:25 -0400
Subject: [PATCH 0016/1703] feat(@angular/cli): initialize a console prompt
provider for schematics
---
package.json | 1 +
.../angular/cli/commands/generate-impl.ts | 10 +-
packages/angular/cli/commands/generate.json | 5 +
packages/angular/cli/commands/new-impl.ts | 6 +-
packages/angular/cli/commands/new.json | 5 +
.../angular/cli/models/schematic-command.ts | 45 ++++
packages/angular/cli/package.json | 2 +-
yarn.lock | 192 +++++++++++++++++-
8 files changed, 255 insertions(+), 11 deletions(-)
diff --git a/package.json b/package.json
index 4936df3a1966..8b881cb9159e 100644
--- a/package.json
+++ b/package.json
@@ -69,6 +69,7 @@
"@types/copy-webpack-plugin": "^4.4.1",
"@types/express": "^4.16.0",
"@types/glob": "^5.0.35",
+ "@types/inquirer": "^0.0.42",
"@types/istanbul": "^0.4.30",
"@types/jasmine": "^2.8.8",
"@types/loader-utils": "^1.1.3",
diff --git a/packages/angular/cli/commands/generate-impl.ts b/packages/angular/cli/commands/generate-impl.ts
index 204bee6d48db..7e2580751137 100644
--- a/packages/angular/cli/commands/generate-impl.ts
+++ b/packages/angular/cli/commands/generate-impl.ts
@@ -52,10 +52,11 @@ export class GenerateCommand extends SchematicCommand {
return this.runSchematic({
collectionName,
schematicName,
- schematicOptions: options,
+ schematicOptions: this.removeLocalOptions(options),
debug: options.debug,
dryRun: options.dryRun,
force: options.force,
+ interactive: options.interactive,
});
}
@@ -95,4 +96,11 @@ export class GenerateCommand extends SchematicCommand {
this.logger.info(terminal.cyan(` ng generate --help`));
}
}
+
+ private removeLocalOptions(options: any): any {
+ const opts = Object.assign({}, options);
+ delete opts.interactive;
+
+ return opts;
+ }
}
diff --git a/packages/angular/cli/commands/generate.json b/packages/angular/cli/commands/generate.json
index 95c6c8a46e90..3a784e3d47b9 100644
--- a/packages/angular/cli/commands/generate.json
+++ b/packages/angular/cli/commands/generate.json
@@ -30,6 +30,11 @@
"default": false,
"aliases": ["f"],
"description": "Forces overwriting of files."
+ },
+ "interactive": {
+ "type": "boolean",
+ "default": "true",
+ "description": "Disables interactive inputs (i.e., prompts)."
}
},
"required": [
diff --git a/packages/angular/cli/commands/new-impl.ts b/packages/angular/cli/commands/new-impl.ts
index b8663199c342..c9229a2e1b37 100644
--- a/packages/angular/cli/commands/new-impl.ts
+++ b/packages/angular/cli/commands/new-impl.ts
@@ -50,15 +50,14 @@ export class NewCommand extends SchematicCommand {
// Ensure skipGit has a boolean value.
options.skipGit = options.skipGit === undefined ? false : options.skipGit;
- options = this.removeLocalOptions(options);
-
return this.runSchematic({
collectionName: collectionName,
schematicName: this.schematicName,
- schematicOptions: options,
+ schematicOptions: this.removeLocalOptions(options),
debug: options.debug,
dryRun: options.dryRun,
force: options.force,
+ interactive: options.interactive,
});
}
@@ -72,6 +71,7 @@ export class NewCommand extends SchematicCommand {
const opts = Object.assign({}, options);
delete opts.verbose;
delete opts.collection;
+ delete opts.interactive;
return opts;
}
diff --git a/packages/angular/cli/commands/new.json b/packages/angular/cli/commands/new.json
index cf8104029909..b1f8db9f577b 100644
--- a/packages/angular/cli/commands/new.json
+++ b/packages/angular/cli/commands/new.json
@@ -33,6 +33,11 @@
"default": false,
"aliases": ["v"],
"description": "Adds more details to output logging."
+ },
+ "interactive": {
+ "type": "boolean",
+ "default": "true",
+ "description": "Disables interactive inputs (i.e., prompts)."
}
},
"required": []
diff --git a/packages/angular/cli/models/schematic-command.ts b/packages/angular/cli/models/schematic-command.ts
index 9f78d5b450ed..75714606c250 100644
--- a/packages/angular/cli/models/schematic-command.ts
+++ b/packages/angular/cli/models/schematic-command.ts
@@ -37,6 +37,7 @@ import {
NodeWorkflow,
validateOptionsWithSchema,
} from '@angular-devkit/schematics/tools';
+import * as inquirer from 'inquirer';
import { take } from 'rxjs/operators';
import { WorkspaceLoader } from '../models/workspace-loader';
import {
@@ -60,6 +61,7 @@ export interface RunSchematicOptions {
dryRun: boolean;
force: boolean;
showNothingDone?: boolean;
+ interactive?: boolean;
}
export interface GetOptionsOptions {
@@ -279,6 +281,49 @@ export abstract class SchematicCommand extends Command {
return undefined;
});
+ if (options.interactive !== false && process.stdout.isTTY) {
+ workflow.registry.usePromptProvider((definitions: Array) => {
+ const questions: inquirer.Questions = definitions.map(definition => {
+ const question: inquirer.Question = {
+ name: definition.id,
+ message: definition.message,
+ default: definition.default,
+ };
+
+ const validator = definition.validator;
+ if (validator) {
+ question.validate = input => validator(input);
+ }
+
+ switch (definition.type) {
+ case 'confirmation':
+ question.type = 'confirm';
+ break;
+ case 'list':
+ question.type = 'list';
+ question.choices = definition.items && definition.items.map(item => {
+ if (typeof item == 'string') {
+ return item;
+ } else {
+ return {
+ name: item.label,
+ value: item.value,
+ };
+ }
+ });
+ break;
+ default:
+ question.type = definition.type;
+ break;
+ }
+
+ return question;
+ });
+
+ return inquirer.prompt(questions);
+ });
+ }
+
workflow.reporter.subscribe((event: DryRunEvent) => {
nothingDone = false;
diff --git a/packages/angular/cli/package.json b/packages/angular/cli/package.json
index 38b778a03d0d..6c658e474a02 100644
--- a/packages/angular/cli/package.json
+++ b/packages/angular/cli/package.json
@@ -35,9 +35,9 @@
"@angular-devkit/schematics": "0.0.0",
"@schematics/angular": "0.0.0",
"@schematics/update": "0.0.0",
+ "inquirer": "^6.1.0",
"json-schema-traverse": "^0.4.1",
"opn": "^5.3.0",
- "json-schema-traverse": "^0.4.1",
"rxjs": "^6.0.0",
"semver": "^5.1.0",
"symbol-observable": "^1.2.0",
diff --git a/yarn.lock b/yarn.lock
index d5e23db55b07..88e1463881ab 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -181,6 +181,13 @@
"@types/events" "*"
"@types/node" "*"
+"@types/inquirer@^0.0.42":
+ version "0.0.42"
+ resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-0.0.42.tgz#2310fd8623053bead72247adae416aa32bacdf0c"
+ dependencies:
+ "@types/rx" "*"
+ "@types/through" "*"
+
"@types/istanbul@^0.4.30":
version "0.4.30"
resolved "https://registry.yarnpkg.com/@types/istanbul/-/istanbul-0.4.30.tgz#073159320ab3296b2cfeb481f756a1f8f4c9c8e4"
@@ -237,6 +244,94 @@
"@types/node" "*"
"@types/tough-cookie" "*"
+"@types/rx-core-binding@*":
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/@types/rx-core-binding/-/rx-core-binding-4.0.4.tgz#d969d32f15a62b89e2862c17b3ee78fe329818d3"
+ dependencies:
+ "@types/rx-core" "*"
+
+"@types/rx-core@*":
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/@types/rx-core/-/rx-core-4.0.3.tgz#0b3354b1238cedbe2b74f6326f139dbc7a591d60"
+
+"@types/rx-lite-aggregates@*":
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/@types/rx-lite-aggregates/-/rx-lite-aggregates-4.0.3.tgz#6efb2b7f3d5f07183a1cb2bd4b1371d7073384c2"
+ dependencies:
+ "@types/rx-lite" "*"
+
+"@types/rx-lite-async@*":
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/@types/rx-lite-async/-/rx-lite-async-4.0.2.tgz#27fbf0caeff029f41e2d2aae638b05e91ceb600c"
+ dependencies:
+ "@types/rx-lite" "*"
+
+"@types/rx-lite-backpressure@*":
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/@types/rx-lite-backpressure/-/rx-lite-backpressure-4.0.3.tgz#05abb19bdf87cc740196c355e5d0b37bb50b5d56"
+ dependencies:
+ "@types/rx-lite" "*"
+
+"@types/rx-lite-coincidence@*":
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/@types/rx-lite-coincidence/-/rx-lite-coincidence-4.0.3.tgz#80bd69acc4054a15cdc1638e2dc8843498cd85c0"
+ dependencies:
+ "@types/rx-lite" "*"
+
+"@types/rx-lite-experimental@*":
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/@types/rx-lite-experimental/-/rx-lite-experimental-4.0.1.tgz#c532f5cbdf3f2c15da16ded8930d1b2984023cbd"
+ dependencies:
+ "@types/rx-lite" "*"
+
+"@types/rx-lite-joinpatterns@*":
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/@types/rx-lite-joinpatterns/-/rx-lite-joinpatterns-4.0.1.tgz#f70fe370518a8432f29158cc92ffb56b4e4afc3e"
+ dependencies:
+ "@types/rx-lite" "*"
+
+"@types/rx-lite-testing@*":
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/@types/rx-lite-testing/-/rx-lite-testing-4.0.1.tgz#21b19d11f4dfd6ffef5a9d1648e9c8879bfe21e9"
+ dependencies:
+ "@types/rx-lite-virtualtime" "*"
+
+"@types/rx-lite-time@*":
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/@types/rx-lite-time/-/rx-lite-time-4.0.3.tgz#0eda65474570237598f3448b845d2696f2dbb1c4"
+ dependencies:
+ "@types/rx-lite" "*"
+
+"@types/rx-lite-virtualtime@*":
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/@types/rx-lite-virtualtime/-/rx-lite-virtualtime-4.0.3.tgz#4b30cacd0fe2e53af29f04f7438584c7d3959537"
+ dependencies:
+ "@types/rx-lite" "*"
+
+"@types/rx-lite@*":
+ version "4.0.5"
+ resolved "https://registry.yarnpkg.com/@types/rx-lite/-/rx-lite-4.0.5.tgz#b3581525dff69423798daa9a0d33c1e66a5e8c4c"
+ dependencies:
+ "@types/rx-core" "*"
+ "@types/rx-core-binding" "*"
+
+"@types/rx@*":
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/@types/rx/-/rx-4.1.1.tgz#598fc94a56baed975f194574e0f572fd8e627a48"
+ dependencies:
+ "@types/rx-core" "*"
+ "@types/rx-core-binding" "*"
+ "@types/rx-lite" "*"
+ "@types/rx-lite-aggregates" "*"
+ "@types/rx-lite-async" "*"
+ "@types/rx-lite-backpressure" "*"
+ "@types/rx-lite-coincidence" "*"
+ "@types/rx-lite-experimental" "*"
+ "@types/rx-lite-joinpatterns" "*"
+ "@types/rx-lite-testing" "*"
+ "@types/rx-lite-time" "*"
+ "@types/rx-lite-virtualtime" "*"
+
"@types/selenium-webdriver@^3.0.0":
version "3.0.10"
resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-3.0.10.tgz#e98cc6f05b4b436277671c784ee2f9d05a634f9b"
@@ -270,6 +365,12 @@
version "1.0.4"
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.4.tgz#b4ffc7dc97b498c969b360a41eee247f82616370"
+"@types/through@*":
+ version "0.0.29"
+ resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.29.tgz#72943aac922e179339c651fa34a4428a4d722f93"
+ dependencies:
+ "@types/node" "*"
+
"@types/tough-cookie@*":
version "2.3.3"
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.3.tgz#7f226d67d654ec9070e755f46daebf014628e9d9"
@@ -553,6 +654,10 @@ ansi-align@^2.0.0:
dependencies:
string-width "^2.0.0"
+ansi-escapes@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30"
+
ansi-html@0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e"
@@ -1307,7 +1412,7 @@ chalk@^1.1.1, chalk@^1.1.3:
strip-ansi "^3.0.0"
supports-color "^2.0.0"
-chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3.2, chalk@^2.4.1:
+chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3.2, chalk@^2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e"
dependencies:
@@ -1315,6 +1420,10 @@ chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3.2, chalk@^2.4
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
+chardet@^0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.5.0.tgz#fe3ac73c00c3d865ffcc02a0682e2c20b6a06029"
+
chokidar@^1.4.2:
version "1.7.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468"
@@ -1403,6 +1512,16 @@ cli-boxes@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143"
+cli-cursor@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
+ dependencies:
+ restore-cursor "^2.0.0"
+
+cli-width@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
+
cliui@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1"
@@ -2653,6 +2772,14 @@ extend@^3.0.0, extend@~3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
+external-editor@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.1.tgz#fc9638c4d7cde4f0bb82b12307a1a23912c492e3"
+ dependencies:
+ chardet "^0.5.0"
+ iconv-lite "^0.4.22"
+ tmp "^0.0.33"
+
extglob@^0.3.1:
version "0.3.2"
resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1"
@@ -2724,6 +2851,12 @@ figgy-pudding@^3.1.0:
version "3.4.1"
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.4.1.tgz#af66da1991fa2f94ff7f33b545a38ea4b3869696"
+figures@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
+ dependencies:
+ escape-string-regexp "^1.0.5"
+
file-loader@^1.1.11:
version "1.1.11"
resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-1.1.11.tgz#6fe886449b0f2a936e43cabaac0cdbfb369506f8"
@@ -3463,6 +3596,12 @@ iconv-lite@0.4.23, iconv-lite@^0.4.4:
dependencies:
safer-buffer ">= 2.1.2 < 3"
+iconv-lite@^0.4.22:
+ version "0.4.24"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
+ dependencies:
+ safer-buffer ">= 2.1.2 < 3"
+
ieee754@^1.1.11, ieee754@^1.1.4:
version "1.1.12"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
@@ -3561,6 +3700,24 @@ injection-js@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/injection-js/-/injection-js-2.2.1.tgz#a8d6a085b2f0b8d8650f6f4487f6abb8cc0d67ce"
+inquirer@^6.1.0:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.0.tgz#51adcd776f661369dc1e894859c2560a224abdd8"
+ dependencies:
+ ansi-escapes "^3.0.0"
+ chalk "^2.0.0"
+ cli-cursor "^2.1.0"
+ cli-width "^2.0.0"
+ external-editor "^3.0.0"
+ figures "^2.0.0"
+ lodash "^4.17.10"
+ mute-stream "0.0.7"
+ run-async "^2.2.0"
+ rxjs "^6.1.0"
+ string-width "^2.1.0"
+ strip-ansi "^4.0.0"
+ through "^2.3.6"
+
internal-ip@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-1.2.0.tgz#ae9fbf93b984878785d50a8de1b356956058cf5c"
@@ -4412,7 +4569,7 @@ lodash.templatesettings@^4.0.0:
dependencies:
lodash._reinterpolate "~3.0.0"
-lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.0, lodash@~4.17.10:
+lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.5.0, lodash@~4.17.10:
version "4.17.10"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
@@ -4818,6 +4975,10 @@ multicast-dns@^6.0.1:
dns-packet "^1.3.1"
thunky "^1.0.2"
+mute-stream@0.0.7:
+ version "0.0.7"
+ resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
+
nan@^2.10.0, nan@^2.9.2:
version "2.10.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
@@ -5193,6 +5354,12 @@ one-time@0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/one-time/-/one-time-0.0.4.tgz#f8cdf77884826fe4dff93e3a9cc37b1e4480742e"
+onetime@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
+ dependencies:
+ mimic-fn "^1.0.0"
+
opn@^5.1.0, opn@^5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/opn/-/opn-5.3.0.tgz#64871565c863875f052cfdf53d3e3cb5adb53b1c"
@@ -6116,6 +6283,13 @@ responselike@1.0.2:
dependencies:
lowercase-keys "^1.0.0"
+restore-cursor@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
+ dependencies:
+ onetime "^2.0.0"
+ signal-exit "^3.0.2"
+
ret@~0.1.10:
version "0.1.15"
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
@@ -6185,13 +6359,19 @@ rollup@^0.64.0:
"@types/estree" "0.0.39"
"@types/node" "*"
+run-async@^2.2.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
+ dependencies:
+ is-promise "^2.1.0"
+
run-queue@^1.0.0, run-queue@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47"
dependencies:
aproba "^1.1.1"
-rxjs@6.0.0, rxjs@^6.0.0:
+rxjs@6.0.0, rxjs@^6.0.0, rxjs@^6.1.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.0.0.tgz#d647e029b5854617f994c82fe57a4c6747b352da"
dependencies:
@@ -6830,7 +7010,7 @@ string-width@^1.0.1, string-width@^1.0.2:
is-fullwidth-code-point "^1.0.0"
strip-ansi "^3.0.0"
-"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.1:
+"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
dependencies:
@@ -7010,7 +7190,7 @@ through2@^2.0.0, through2@^2.0.2, through2@^2.0.3:
readable-stream "^2.1.5"
xtend "~4.0.1"
-through@2, "through@>=2.2.7 <3", through@X.X.X:
+through@2, "through@>=2.2.7 <3", through@X.X.X, through@^2.3.6:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
@@ -7034,7 +7214,7 @@ tmp@0.0.30:
dependencies:
os-tmpdir "~1.0.1"
-tmp@0.0.33, tmp@0.0.x:
+tmp@0.0.33, tmp@0.0.x, tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
dependencies:
From cdbc1988f28ce4f2c2509efb9ebd10e727facbdd Mon Sep 17 00:00:00 2001
From: Charles Lyding <19598772+clydin@users.noreply.github.com>
Date: Fri, 17 Aug 2018 11:42:18 -0400
Subject: [PATCH 0017/1703] feat(@angular-devkit/schematics): allow schematic
rules to control interactivity
---
.../schematics/src/engine/engine.ts | 13 +++++++++++-
.../schematics/src/engine/interface.ts | 9 ++++++++
.../schematics/src/engine/schematic.ts | 6 ++++--
.../schematics/src/rules/schematic.ts | 21 ++++++++++++-------
.../schematics/tools/fallback-engine-host.ts | 9 ++++----
.../tools/file-system-engine-host-base.ts | 9 ++++++--
.../tools/schema-option-transform.ts | 12 ++++++++---
7 files changed, 59 insertions(+), 20 deletions(-)
diff --git a/packages/angular_devkit/schematics/src/engine/engine.ts b/packages/angular_devkit/schematics/src/engine/engine.ts
index 7bcff545af15..4222a904ce11 100644
--- a/packages/angular_devkit/schematics/src/engine/engine.ts
+++ b/packages/angular_devkit/schematics/src/engine/engine.ts
@@ -18,6 +18,7 @@ import {
CollectionDescription,
Engine,
EngineHost,
+ ExecutionOptions,
Schematic,
SchematicContext,
SchematicDescription,
@@ -215,12 +216,20 @@ export class SchematicEngine,
parent?: Partial>,
+ executionOptions?: Partial,
): TypedSchematicContext {
// Check for inconsistencies.
if (parent && parent.engine && parent.engine !== this) {
throw new SchematicEngineConflictingException();
}
+ let interactive = true;
+ if (executionOptions && executionOptions.interactive != undefined) {
+ interactive = executionOptions.interactive;
+ } else if (parent && parent.interactive != undefined) {
+ interactive = parent.interactive;
+ }
+
let context: TypedSchematicContext = {
debug: parent && parent.debug || false,
engine: this,
@@ -229,6 +238,7 @@ export class SchematicEngine(
schematic: Schematic,
options: OptionT,
+ context?: TypedSchematicContext,
): Observable {
- return this._host.transformOptions(schematic.description, options);
+ return this._host.transformOptions(schematic.description, options, context);
}
createSourceFromUrl(url: Url, context: TypedSchematicContext): Source {
diff --git a/packages/angular_devkit/schematics/src/engine/interface.ts b/packages/angular_devkit/schematics/src/engine/interface.ts
index 2bcb6b9a6c33..0e43788c2340 100644
--- a/packages/angular_devkit/schematics/src/engine/interface.ts
+++ b/packages/angular_devkit/schematics/src/engine/interface.ts
@@ -41,6 +41,10 @@ export interface TaskInfo {
readonly context: SchematicContext;
}
+export interface ExecutionOptions {
+ interactive: boolean;
+}
+
/**
* The description (metadata) of a collection. This type contains every information the engine
* needs to run. The CollectionMetadataT type parameter contains additional metadata that you
@@ -92,6 +96,7 @@ export interface EngineHost(
schematic: SchematicDescription,
options: OptionT,
+ context?: TypedSchematicContext,
): Observable;
transformContext(
context: TypedSchematicContext,
@@ -118,6 +123,7 @@ export interface Engine,
parent?: Partial>,
+ executionOptions?: Partial,
): TypedSchematicContext;
createSchematic(
name: string,
@@ -130,6 +136,7 @@ export interface Engine(
schematic: Schematic,
options: OptionT,
+ context?: TypedSchematicContext,
): Observable;
executePostTasks(): Observable;
@@ -166,6 +173,7 @@ export interface Schematic,
parentContext?: Partial>,
+ executionOptions?: Partial,
): Observable;
}
@@ -181,6 +189,7 @@ export interface TypedSchematicContext;
readonly strategy: MergeStrategy;
+ readonly interactive: boolean;
addTask(task: TaskConfigurationGenerator, dependencies?: Array): TaskId;
}
diff --git a/packages/angular_devkit/schematics/src/engine/schematic.ts b/packages/angular_devkit/schematics/src/engine/schematic.ts
index 932fdd76285a..fd3acbf774df 100644
--- a/packages/angular_devkit/schematics/src/engine/schematic.ts
+++ b/packages/angular_devkit/schematics/src/engine/schematic.ts
@@ -13,6 +13,7 @@ import { Tree } from '../tree/interface';
import {
Collection,
Engine,
+ ExecutionOptions,
RuleFactory,
Schematic,
SchematicDescription,
@@ -46,13 +47,14 @@ export class SchematicImpl,
parentContext?: Partial>,
+ executionOptions?: Partial,
): Observable {
- const context = this._engine.createContext(this, parentContext);
+ const context = this._engine.createContext(this, parentContext, executionOptions);
return host
.pipe(
first(),
- concatMap(tree => this._engine.transformOptions(this, options).pipe(
+ concatMap(tree => this._engine.transformOptions(this, options, context).pipe(
map(o => [tree, o]),
)),
concatMap(([tree, transformedOptions]: [Tree, OptionT]) => {
diff --git a/packages/angular_devkit/schematics/src/rules/schematic.ts b/packages/angular_devkit/schematics/src/rules/schematic.ts
index 7d7a01cfd258..6885ad9bacb6 100644
--- a/packages/angular_devkit/schematics/src/rules/schematic.ts
+++ b/packages/angular_devkit/schematics/src/rules/schematic.ts
@@ -7,7 +7,7 @@
*/
import { of as observableOf } from 'rxjs';
import { last, map } from 'rxjs/operators';
-import { Rule, SchematicContext } from '../engine/interface';
+import { ExecutionOptions, Rule, SchematicContext } from '../engine/interface';
import { MergeStrategy, Tree } from '../tree/interface';
import { branch } from '../tree/static';
@@ -19,14 +19,17 @@ import { branch } from '../tree/static';
* @param schematicName The name of the schematic to run.
* @param options The options to pass as input to the RuleFactory.
*/
-export function externalSchematic(collectionName: string,
- schematicName: string,
- options: OptionT): Rule {
+export function externalSchematic(
+ collectionName: string,
+ schematicName: string,
+ options: OptionT,
+ executionOptions?: Partial,
+): Rule {
return (input: Tree, context: SchematicContext) => {
const collection = context.engine.createCollection(collectionName);
const schematic = collection.createSchematic(schematicName);
- return schematic.call(options, observableOf(branch(input)), context);
+ return schematic.call(options, observableOf(branch(input)), context, executionOptions);
};
}
@@ -37,12 +40,16 @@ export function externalSchematic(collectionName: string
* @param schematicName The name of the schematic to run.
* @param options The options to pass as input to the RuleFactory.
*/
-export function schematic(schematicName: string, options: OptionT): Rule {
+export function schematic(
+ schematicName: string,
+ options: OptionT,
+ executionOptions?: Partial,
+): Rule {
return (input: Tree, context: SchematicContext) => {
const collection = context.schematic.collection;
const schematic = collection.createSchematic(schematicName, true);
- return schematic.call(options, observableOf(branch(input)), context).pipe(
+ return schematic.call(options, observableOf(branch(input)), context, executionOptions).pipe(
last(),
map(x => {
// We allow overwrite conflict here because they're the only merge conflict we particularly
diff --git a/packages/angular_devkit/schematics/tools/fallback-engine-host.ts b/packages/angular_devkit/schematics/tools/fallback-engine-host.ts
index a9c327cbc7e0..7dd9849bef24 100644
--- a/packages/angular_devkit/schematics/tools/fallback-engine-host.ts
+++ b/packages/angular_devkit/schematics/tools/fallback-engine-host.ts
@@ -31,10 +31,6 @@ export type FallbackSchematicDescription = {
};
export type FallbackContext =
TypedSchematicContext;
-export declare type OptionTransform = (
- schematic: SchematicDescription,
- options: T,
-) => Observable;
/**
@@ -93,9 +89,12 @@ export class FallbackEngineHost implements EngineHost<{}, {}> {
transformOptions(
schematic: SchematicDescription,
options: OptionT,
+ context?: FallbackContext,
): Observable {
return (observableOf(options)
- .pipe(...this._hosts.map(host => mergeMap(opt => host.transformOptions(schematic, opt))))
+ .pipe(...this._hosts
+ .map(host => mergeMap(opt => host.transformOptions(schematic, opt, context))),
+ )
) as {} as Observable;
}
diff --git a/packages/angular_devkit/schematics/tools/file-system-engine-host-base.ts b/packages/angular_devkit/schematics/tools/file-system-engine-host-base.ts
index bc5d19fac843..7c3ed9c808d4 100644
--- a/packages/angular_devkit/schematics/tools/file-system-engine-host-base.ts
+++ b/packages/angular_devkit/schematics/tools/file-system-engine-host-base.ts
@@ -39,7 +39,11 @@ import { readJsonFile } from './file-system-utility';
export declare type OptionTransform
- = (schematic: FileSystemSchematicDescription, options: T) => Observable;
+ = (
+ schematic: FileSystemSchematicDescription,
+ options: T,
+ context?: FileSystemSchematicContext,
+ ) => Observable;
export class CollectionCannotBeResolvedException extends BaseException {
@@ -269,11 +273,12 @@ export abstract class FileSystemEngineHostBase implements
transformOptions(
schematic: FileSystemSchematicDesc,
options: OptionT,
+ context?: FileSystemSchematicContext,
): Observable {
return (observableOf(options)
.pipe(
...this._transforms.map(tFn => mergeMap(opt => {
- const newOptions = tFn(schematic, opt);
+ const newOptions = tFn(schematic, opt, context);
if (isObservable(newOptions)) {
return newOptions;
} else {
diff --git a/packages/angular_devkit/schematics/tools/schema-option-transform.ts b/packages/angular_devkit/schematics/tools/schema-option-transform.ts
index 1827b1106f62..1c7491ecb60a 100644
--- a/packages/angular_devkit/schematics/tools/schema-option-transform.ts
+++ b/packages/angular_devkit/schematics/tools/schema-option-transform.ts
@@ -8,7 +8,7 @@
import { deepCopy, schema } from '@angular-devkit/core';
import { Observable, of as observableOf } from 'rxjs';
import { first, map, mergeMap } from 'rxjs/operators';
-import { FileSystemSchematicDescription } from './description';
+import { FileSystemSchematicContext, FileSystemSchematicDescription } from './description';
export class InvalidInputOptions extends schema.SchemaValidationException {
constructor(options: T, errors: schema.SchemaValidatorError[]) {
@@ -21,16 +21,22 @@ export class InvalidInputOptions extends schema.SchemaValidationExceptio
// This can only be used in NodeJS.
export function validateOptionsWithSchema(registry: schema.SchemaRegistry) {
- return (schematic: FileSystemSchematicDescription, options: T): Observable => {
+ return (
+ schematic: FileSystemSchematicDescription,
+ options: T,
+ context?: FileSystemSchematicContext,
+ ): Observable => {
// Prevent a schematic from changing the options object by making a copy of it.
options = deepCopy(options);
+ const withPrompts = context ? context.interactive : true;
+
if (schematic.schema && schematic.schemaJson) {
// Make a deep copy of options.
return registry
.compile(schematic.schemaJson)
.pipe(
- mergeMap(validator => validator(options)),
+ mergeMap(validator => validator(options, { withPrompts })),
first(),
map(result => {
if (!result.success) {
From d299ef7ff710b5408dee0c1d0f80bf5c8660967a Mon Sep 17 00:00:00 2001
From: Charles Lyding <19598772+clydin@users.noreply.github.com>
Date: Tue, 21 Aug 2018 13:45:57 -0400
Subject: [PATCH 0018/1703] fix(@angular-devkit/core): correctly resolve schema
references
---
.../core/src/json/schema/registry.ts | 22 +++++++++----------
1 file changed, 10 insertions(+), 12 deletions(-)
diff --git a/packages/angular_devkit/core/src/json/schema/registry.ts b/packages/angular_devkit/core/src/json/schema/registry.ts
index 27e82270ad69..b16d79495839 100644
--- a/packages/angular_devkit/core/src/json/schema/registry.ts
+++ b/packages/angular_devkit/core/src/json/schema/registry.ts
@@ -171,23 +171,21 @@ export class CoreSchemaRegistry implements SchemaRegistry {
ref: string,
validate: ajv.ValidateFunction,
): { context?: ajv.ValidateFunction, schema?: JsonObject } {
- if (!validate) {
+ if (!validate || !validate.refs || !validate.refVal || !ref) {
return {};
}
- const refHash = ref.split('#', 2)[1];
- const refUrl = ref.startsWith('#') ? ref : ref.split('#', 1);
-
- if (!ref.startsWith('#')) {
- // tslint:disable-next-line:no-any
- validate = (validate.refVal as any)[(validate.refs as any)[refUrl[0]]];
- }
- if (validate && refHash) {
- // tslint:disable-next-line:no-any
- validate = (validate.refVal as any)[(validate.refs as any)['#' + refHash]];
+ // tslint:disable-next-line:no-any
+ const id = (validate.schema as any).$id || (validate.schema as any).id;
+ let fullReference = (ref[0] === '#' && id) ? id + ref : ref;
+ if (fullReference.endsWith('#')) {
+ fullReference = fullReference.slice(0, -1);
}
- return { context: validate, schema: validate && validate.schema as JsonObject };
+ // tslint:disable-next-line:no-any
+ const context = validate.refVal[(validate.refs as any)[fullReference]];
+
+ return { context, schema: context && context.schema as JsonObject };
}
compile(schema: JsonObject): Observable {
From e177849f44621bb2fbbe96a19d32213c4050fedc Mon Sep 17 00:00:00 2001
From: Charles Lyding <19598772+clydin@users.noreply.github.com>
Date: Tue, 21 Aug 2018 21:31:06 -0400
Subject: [PATCH 0019/1703] fix(@angular/cli): correct update command packages
option type
---
packages/angular/cli/commands/update.json | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/packages/angular/cli/commands/update.json b/packages/angular/cli/commands/update.json
index dd66520136e0..7a47adf3c7bb 100644
--- a/packages/angular/cli/commands/update.json
+++ b/packages/angular/cli/commands/update.json
@@ -11,7 +11,10 @@
"type": "object",
"properties": {
"packages": {
- "type": "string",
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
"description": "The names of package(s) to update",
"$default": {
"$source": "argv"
From e0882a754995af7ee1d4c875de4642f43504a79a Mon Sep 17 00:00:00 2001
From: Charles Lyding <19598772+clydin@users.noreply.github.com>
Date: Wed, 22 Aug 2018 12:28:24 -0400
Subject: [PATCH 0020/1703] fix(@angular-devkit/core): make workspace projects
field not required
It has a default which makes the required aspect void.
Global files also do not have projects.
---
.../angular_devkit/core/src/workspace/workspace-schema.json | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/packages/angular_devkit/core/src/workspace/workspace-schema.json b/packages/angular_devkit/core/src/workspace/workspace-schema.json
index 9fd692661630..a030a6753d55 100644
--- a/packages/angular_devkit/core/src/workspace/workspace-schema.json
+++ b/packages/angular_devkit/core/src/workspace/workspace-schema.json
@@ -48,8 +48,7 @@
},
"additionalProperties": false,
"required": [
- "version",
- "projects"
+ "version"
],
"definitions": {
"project": {
From fd6bba38a008c6ff89584c3edf8ff5ab326f51d1 Mon Sep 17 00:00:00 2001
From: Charles Lyding <19598772+clydin@users.noreply.github.com>
Date: Wed, 22 Aug 2018 13:03:32 -0400
Subject: [PATCH 0021/1703] fix(@angular/cli): properly type config command
value option
---
packages/angular/cli/commands/config.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/angular/cli/commands/config.json b/packages/angular/cli/commands/config.json
index 0caaa4f5415a..cdcce065041c 100644
--- a/packages/angular/cli/commands/config.json
+++ b/packages/angular/cli/commands/config.json
@@ -20,7 +20,7 @@
}
},
"value": {
- "type": "string",
+ "type": ["string", "number", "boolean"],
"description": "The new value to be set.",
"$default": {
"$source": "argv",
From 9af2368481bc3c869f138c942e0177fa9e2c2c5a Mon Sep 17 00:00:00 2001
From: clydin <19598772+clydin@users.noreply.github.com>
Date: Wed, 29 Aug 2018 12:26:30 -0400
Subject: [PATCH 0022/1703] ci: continue even if lint or validate fail (#12011)
---
.circleci/config.yml | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index c02a15a576f5..bab56e835e84 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -140,12 +140,10 @@ workflows:
- install
- build:
requires:
- - lint
- - validate
+ - install
- build-bazel:
requires:
- - lint
- - validate
+ - build
- test:
requires:
- build
@@ -167,6 +165,7 @@ workflows:
requires:
- test
- build
+ - e2e-cli
- snapshot_publish
filters:
tags:
From 67e32a8f2536132e594a00f28ebc7aebb5b8949f Mon Sep 17 00:00:00 2001
From: clydin <19598772+clydin@users.noreply.github.com>
Date: Wed, 29 Aug 2018 12:26:52 -0400
Subject: [PATCH 0023/1703] fix(@angular-devkit/build-angular): improve bundle
size value parsing (#12026)
Fixes #12013
---
.../plugins/bundle-budget.ts | 36 ++++-----
.../utilities/bundle-calculator.ts | 74 +++++++++--------
.../utilities/bundle-calculator_spec.ts | 80 +++++++++++++++++++
3 files changed, 133 insertions(+), 57 deletions(-)
create mode 100644 packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator_spec.ts
diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/bundle-budget.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/bundle-budget.ts
index 6400ce9491b9..4ee676886846 100644
--- a/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/bundle-budget.ts
+++ b/packages/angular_devkit/build_angular/src/angular-cli-files/plugins/bundle-budget.ts
@@ -1,6 +1,3 @@
-// tslint:disable
-// TODO: cleanup this file, it's copied as is from Angular CLI.
-
/**
* @license
* Copyright Google Inc. All Rights Reserved.
@@ -8,9 +5,9 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
-
-import { Size, calculateBytes, calculateSizes } from '../utilities/bundle-calculator';
+import { Compiler, compilation } from 'webpack';
import { Budget } from '../../browser/schema';
+import { Size, calculateBytes, calculateSizes } from '../utilities/bundle-calculator';
import { formatSize } from '../utilities/stats';
interface Thresholds {
@@ -31,19 +28,20 @@ export interface BundleBudgetPluginOptions {
export class BundleBudgetPlugin {
constructor(private options: BundleBudgetPluginOptions) { }
- apply(compiler: any): void {
+ apply(compiler: Compiler): void {
const { budgets } = this.options;
- compiler.hooks.afterEmit.tap('BundleBudgetPlugin', (compilation: any) => {
+ compiler.hooks.afterEmit.tap('BundleBudgetPlugin', (compilation: compilation.Compilation) => {
if (!budgets || budgets.length === 0) {
return;
}
budgets.map(budget => {
const thresholds = this.calculate(budget);
+
return {
budget,
thresholds,
- sizes: calculateSizes(budget, compilation)
+ sizes: calculateSizes(budget, compilation),
};
})
.forEach(budgetCheck => {
@@ -62,7 +60,7 @@ export class BundleBudgetPlugin {
});
}
- private checkMinimum(threshold: number | undefined, size: Size, messages: any) {
+ private checkMinimum(threshold: number | undefined, size: Size, messages: string[]) {
if (threshold) {
if (threshold > size.size) {
const sizeDifference = formatSize(threshold - size.size);
@@ -72,7 +70,7 @@ export class BundleBudgetPlugin {
}
}
- private checkMaximum(threshold: number | undefined, size: Size, messages: any) {
+ private checkMaximum(threshold: number | undefined, size: Size, messages: string[]) {
if (threshold) {
if (threshold < size.size) {
const sizeDifference = formatSize(size.size - threshold);
@@ -83,37 +81,37 @@ export class BundleBudgetPlugin {
}
private calculate(budget: Budget): Thresholds {
- let thresholds: Thresholds = {};
+ const thresholds: Thresholds = {};
if (budget.maximumWarning) {
- thresholds.maximumWarning = calculateBytes(budget.maximumWarning, budget.baseline, 'pos');
+ thresholds.maximumWarning = calculateBytes(budget.maximumWarning, budget.baseline, 1);
}
if (budget.maximumError) {
- thresholds.maximumError = calculateBytes(budget.maximumError, budget.baseline, 'pos');
+ thresholds.maximumError = calculateBytes(budget.maximumError, budget.baseline, 1);
}
if (budget.minimumWarning) {
- thresholds.minimumWarning = calculateBytes(budget.minimumWarning, budget.baseline, 'neg');
+ thresholds.minimumWarning = calculateBytes(budget.minimumWarning, budget.baseline, -1);
}
if (budget.minimumError) {
- thresholds.minimumError = calculateBytes(budget.minimumError, budget.baseline, 'neg');
+ thresholds.minimumError = calculateBytes(budget.minimumError, budget.baseline, -1);
}
if (budget.warning) {
- thresholds.warningLow = calculateBytes(budget.warning, budget.baseline, 'neg');
+ thresholds.warningLow = calculateBytes(budget.warning, budget.baseline, -1);
}
if (budget.warning) {
- thresholds.warningHigh = calculateBytes(budget.warning, budget.baseline, 'pos');
+ thresholds.warningHigh = calculateBytes(budget.warning, budget.baseline, 1);
}
if (budget.error) {
- thresholds.errorLow = calculateBytes(budget.error, budget.baseline, 'neg');
+ thresholds.errorLow = calculateBytes(budget.error, budget.baseline, -1);
}
if (budget.error) {
- thresholds.errorHigh = calculateBytes(budget.error, budget.baseline, 'pos');
+ thresholds.errorHigh = calculateBytes(budget.error, budget.baseline, 1);
}
return thresholds;
diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator.ts
index a396b2f3801d..ca20d6fb386c 100644
--- a/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator.ts
+++ b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator.ts
@@ -1,6 +1,3 @@
-// tslint:disable
-// TODO: cleanup this file, it's copied as is from Angular CLI.
-
/**
* @license
* Copyright Google Inc. All Rights Reserved.
@@ -11,8 +8,8 @@
import { Budget } from '../../browser/schema';
export interface Compilation {
- assets: any;
- chunks: any[];
+ assets: { [name: string]: { size: () => number } };
+ chunks: { name: string, files: string[], isOnlyInitial: () => boolean }[];
warnings: string[];
errors: string[];
}
@@ -33,6 +30,7 @@ export function calculateSizes(budget: Budget, compilation: Compilation): Size[]
};
const ctor = calculatorMap[budget.type];
const calculator = new ctor(budget, compilation);
+
return calculator.calculate();
}
@@ -52,6 +50,7 @@ class BundleCalculator extends Calculator {
.reduce((files, chunk) => [...files, ...chunk.files], [])
.map((file: string) => this.compilation.assets[file].size())
.reduce((total: number, size: number) => total + size, 0);
+
return [{size, label: this.budget.name}];
}
}
@@ -67,6 +66,7 @@ class InitialCalculator extends Calculator {
.filter((file: string) => !file.endsWith('.map'))
.map((file: string) => this.compilation.assets[file].size())
.reduce((total: number, size: number) => total + size, 0);
+
return [{size, label: 'initial'}];
}
}
@@ -81,6 +81,7 @@ class AllScriptCalculator extends Calculator {
.map(key => this.compilation.assets[key])
.map(asset => asset.size())
.reduce((total: number, size: number) => total + size, 0);
+
return [{size, label: 'total scripts'}];
}
}
@@ -94,6 +95,7 @@ class AllCalculator extends Calculator {
.filter(key => !key.endsWith('.map'))
.map(key => this.compilation.assets[key].size())
.reduce((total: number, size: number) => total + size, 0);
+
return [{size, label: 'total'}];
}
}
@@ -107,9 +109,10 @@ class AnyScriptCalculator extends Calculator {
.filter(key => key.endsWith('.js'))
.map(key => {
const asset = this.compilation.assets[key];
+
return {
size: asset.size(),
- label: key
+ label: key,
};
});
}
@@ -124,9 +127,10 @@ class AnyCalculator extends Calculator {
.filter(key => !key.endsWith('.map'))
.map(key => {
const asset = this.compilation.assets[key];
+
return {
size: asset.size(),
- label: key
+ label: key,
};
});
}
@@ -135,39 +139,33 @@ class AnyCalculator extends Calculator {
/**
* Calculate the bytes given a string value.
*/
-export function calculateBytes(val: string, baseline?: string, factor?: ('pos' | 'neg')): number {
- if (/^\d+$/.test(val)) {
- return parseFloat(val);
+export function calculateBytes(
+ input: string,
+ baseline?: string,
+ factor: 1 | -1 = 1,
+): number {
+ const matches = input.match(/^\s*(\d+(?:\.\d+)?)\s*(%|(?:[mM]|[kK]|[gG])?[bB])?\s*$/);
+ if (!matches) {
+ return NaN;
}
- if (/^(\d+)%$/.test(val)) {
- return calculatePercentBytes(val, baseline, factor);
+ const baselineBytes = baseline && calculateBytes(baseline) || 0;
+
+ let value = Number(matches[1]);
+ switch (matches[2] && matches[2].toLowerCase()) {
+ case '%':
+ value = baselineBytes * value / 100 * factor;
+ break;
+ case 'kb':
+ value *= 1024;
+ break;
+ case 'mb':
+ value *= 1024 * 1024;
+ break;
+ case 'gb':
+ value *= 1024 * 1024 * 1024;
+ break;
}
- const multiplier = getMultiplier(val);
-
- const numberVal = parseFloat(val.replace(/((k|m|M|)b?)$/, ''));
- const baselineVal = baseline ? parseFloat(baseline.replace(/((k|m|M|)b?)$/, '')) : 0;
- const baselineMultiplier = baseline ? getMultiplier(baseline) : 1;
- const factorMultiplier = factor ? (factor === 'pos' ? 1 : -1) : 1;
-
- return numberVal * multiplier + baselineVal * baselineMultiplier * factorMultiplier;
-}
-
-function getMultiplier(val: string): number {
- if (/^(\d+)b?$/.test(val)) {
- return 1;
- } else if (/^(\d+)kb$/.test(val)) {
- return 1000;
- } else if (/^(\d+)(m|M)b$/.test(val)) {
- return 1000 * 1000;
- } else {
- return 1;
- }
-}
-
-function calculatePercentBytes(val: string, baseline?: string, factor?: ('pos' | 'neg')): number {
- const baselineBytes = calculateBytes(baseline as string);
- const percentage = parseFloat(val.replace(/%/g, ''));
- return baselineBytes + baselineBytes * percentage / 100 * (factor === 'pos' ? 1 : -1);
+ return value + baselineBytes;
}
diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator_spec.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator_spec.ts
new file mode 100644
index 000000000000..c610706ebe0c
--- /dev/null
+++ b/packages/angular_devkit/build_angular/src/angular-cli-files/utilities/bundle-calculator_spec.ts
@@ -0,0 +1,80 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import { calculateBytes } from './bundle-calculator';
+
+describe('bundle-calculator', () => {
+ it('converts an integer with no postfix', () => {
+ expect(calculateBytes('0')).toBe(0);
+ expect(calculateBytes('5')).toBe(5);
+ expect(calculateBytes('190')).toBe(190);
+ expect(calculateBytes('92')).toBe(92);
+ });
+
+ it('converts a decimal with no postfix', () => {
+ expect(calculateBytes('3.14')).toBe(3.14);
+ expect(calculateBytes('0.25')).toBe(0.25);
+ expect(calculateBytes('90.5')).toBe(90.5);
+ expect(calculateBytes('25.0')).toBe(25);
+ });
+
+ it('converts an integer with kb postfix', () => {
+ expect(calculateBytes('0kb')).toBe(0);
+ expect(calculateBytes('5kb')).toBe(5 * 1024);
+ expect(calculateBytes('190KB')).toBe(190 * 1024);
+ expect(calculateBytes('92Kb')).toBe(92 * 1024);
+ expect(calculateBytes('25kB')).toBe(25 * 1024);
+ });
+
+ it('converts a decimal with kb postfix', () => {
+ expect(calculateBytes('3.14kb')).toBe(3.14 * 1024);
+ expect(calculateBytes('0.25KB')).toBe(0.25 * 1024);
+ expect(calculateBytes('90.5Kb')).toBe(90.5 * 1024);
+ expect(calculateBytes('25.0kB')).toBe(25 * 1024);
+ });
+
+ it('converts an integer with mb postfix', () => {
+ expect(calculateBytes('0mb')).toBe(0);
+ expect(calculateBytes('5mb')).toBe(5 * 1024 * 1024);
+ expect(calculateBytes('190MB')).toBe(190 * 1024 * 1024);
+ expect(calculateBytes('92Mb')).toBe(92 * 1024 * 1024);
+ expect(calculateBytes('25mB')).toBe(25 * 1024 * 1024);
+ });
+
+ it('converts a decimal with mb postfix', () => {
+ expect(calculateBytes('3.14mb')).toBe(3.14 * 1024 * 1024);
+ expect(calculateBytes('0.25MB')).toBe(0.25 * 1024 * 1024);
+ expect(calculateBytes('90.5Mb')).toBe(90.5 * 1024 * 1024);
+ expect(calculateBytes('25.0mB')).toBe(25 * 1024 * 1024);
+ });
+
+ it('converts an integer with gb postfix', () => {
+ expect(calculateBytes('0gb')).toBe(0);
+ expect(calculateBytes('5gb')).toBe(5 * 1024 * 1024 * 1024);
+ expect(calculateBytes('190GB')).toBe(190 * 1024 * 1024 * 1024);
+ expect(calculateBytes('92Gb')).toBe(92 * 1024 * 1024 * 1024);
+ expect(calculateBytes('25gB')).toBe(25 * 1024 * 1024 * 1024);
+ });
+
+ it('converts a decimal with gb postfix', () => {
+ expect(calculateBytes('3.14gb')).toBe(3.14 * 1024 * 1024 * 1024);
+ expect(calculateBytes('0.25GB')).toBe(0.25 * 1024 * 1024 * 1024);
+ expect(calculateBytes('90.5Gb')).toBe(90.5 * 1024 * 1024 * 1024);
+ expect(calculateBytes('25.0gB')).toBe(25 * 1024 * 1024 * 1024);
+ });
+
+ it ('converts a percentage with baseline', () => {
+ expect(calculateBytes('20%', '1mb')).toBe(1024 * 1024 * 1.2);
+ expect(calculateBytes('20%', '1mb', -1)).toBe(1024 * 1024 * 0.8);
+ });
+
+ it ('supports whitespace', () => {
+ expect(calculateBytes(' 5kb ')).toBe(5 * 1024);
+ expect(calculateBytes('0.25 MB')).toBe(0.25 * 1024 * 1024);
+ expect(calculateBytes(' 20 % ', ' 1 mb ')).toBe(1024 * 1024 * 1.2);
+ });
+});
From a2342b6a75fee3181397f7a6e78642a62812640e Mon Sep 17 00:00:00 2001
From: Alan Agius
Date: Wed, 29 Aug 2018 18:27:21 +0200
Subject: [PATCH 0024/1703] feat(@angular-devkit/build-angular): remove
inlining of assets in css (#12027)
BREAKING CHANGE: Assets under 10Kib are not longer inlined in css
---
.../models/webpack-configs/styles.ts | 41 +++++-------
.../test/browser/styles_spec_large.ts | 66 ++----------------
.../e2e/tests/build/styles/inline-urls.ts | 67 -------------------
3 files changed, 21 insertions(+), 153 deletions(-)
delete mode 100644 tests/legacy-cli/e2e/tests/build/styles/inline-urls.ts
diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/styles.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/styles.ts
index b8d067df5163..353e2b91ffc2 100644
--- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/styles.ts
+++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/styles.ts
@@ -53,8 +53,6 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
const extraPlugins: any[] = [];
const cssSourceMap = buildOptions.sourceMap;
- // Maximum resource size to inline (KiB)
- const maximumInlineSize = 10;
// Determine hashing format.
const hashFormat = getOutputHashFormat(buildOptions.outputHashing as string);
// Convert absolute resource URLs to account for base-href and deploy-url.
@@ -134,18 +132,7 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
return `/${baseHref}/${deployUrl}/${url}`.replace(/\/\/+/g, '/');
}
}
- },
- {
- // TODO: inline .cur if not supporting IE (use browserslist to check)
- filter: (asset: PostcssUrlAsset) => {
- return maximumInlineSize > 0 && !asset.hash && !asset.absolutePath.endsWith('.cur');
- },
- url: 'inline',
- // NOTE: maxSize is in KB
- maxSize: maximumInlineSize,
- fallback: 'rebase',
- },
- { url: 'rebase' },
+ }
]),
PostcssCliResources({
deployUrl: loader.loaders[loader.loaderIndex].options.ident == 'extracted' ? '' : deployUrl,
@@ -196,7 +183,7 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
if (chunkNames.length > 0) {
// Add plugin to remove hashes from lazy styles.
- extraPlugins.push(new RemoveHashPlugin({ chunkNames, hashFormat}));
+ extraPlugins.push(new RemoveHashPlugin({ chunkNames, hashFormat }));
}
}
@@ -216,7 +203,8 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
const baseRules: webpack.Rule[] = [
{ test: /\.css$/, use: [] },
{
- test: /\.scss$|\.sass$/, use: [{
+ test: /\.scss$|\.sass$/,
+ use: [{
loader: 'sass-loader',
options: {
implementation: dartSass,
@@ -229,7 +217,8 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
}]
},
{
- test: /\.less$/, use: [{
+ test: /\.less$/,
+ use: [{
loader: 'less-loader',
options: {
sourceMap: cssSourceMap,
@@ -239,7 +228,8 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
}]
},
{
- test: /\.styl$/, use: [{
+ test: /\.styl$/,
+ use: [{
loader: 'stylus-loader',
options: {
sourceMap: cssSourceMap,
@@ -251,7 +241,9 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
// load component css as raw strings
const rules: webpack.Rule[] = baseRules.map(({ test, use }) => ({
- exclude: globalStylePaths, test, use: [
+ exclude: globalStylePaths,
+ test,
+ use: [
{ loader: 'raw-loader' },
{
loader: 'postcss-loader',
@@ -306,16 +298,17 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
}
if (buildOptions.extractCss) {
- // extract global css from js files into own css file
extraPlugins.push(
- new MiniCssExtractPlugin({ filename: `[name]${hashFormat.extract}.css` }));
- // suppress empty .js files in css only entry points
- extraPlugins.push(new SuppressExtractedTextChunksWebpackPlugin());
+ // extract global css from js files into own css file
+ new MiniCssExtractPlugin({ filename: `[name]${hashFormat.extract}.css` }),
+ // suppress empty .js files in css only entry points
+ new SuppressExtractedTextChunksWebpackPlugin(),
+ );
}
return {
entry: entryPoints,
module: { rules },
- plugins: [].concat(extraPlugins as any)
+ plugins: extraPlugins
};
}
diff --git a/packages/angular_devkit/build_angular/test/browser/styles_spec_large.ts b/packages/angular_devkit/build_angular/test/browser/styles_spec_large.ts
index e6bb8cf866ae..774e1ae96752 100644
--- a/packages/angular_devkit/build_angular/test/browser/styles_spec_large.ts
+++ b/packages/angular_devkit/build_angular/test/browser/styles_spec_large.ts
@@ -278,62 +278,6 @@ describe('Browser Builder styles', () => {
});
});
- it('inlines resources', (done) => {
- host.copyFile('src/spectrum.png', 'src/large.png');
- host.writeMultipleFiles({
- 'src/styles.scss': `
- h1 { background: url('./large.png'),
- linear-gradient(to bottom, #0e40fa 25%, #0654f4 75%); }
- h2 { background: url('./small.svg'); }
- p { background: url(./small-id.svg#testID); }
- `,
- 'src/app/app.component.css': `
- h3 { background: url('../small.svg'); }
- h4 { background: url("../large.png"); }
- `,
- 'src/small.svg': imgSvg,
- 'src/small-id.svg': imgSvg,
- });
-
- const overrides = {
- aot: true,
- extractCss: true,
- styles: [`src/styles.scss`],
- };
-
- runTargetSpec(host, browserTargetSpec, overrides).pipe(
- tap((buildEvent) => expect(buildEvent.success).toBe(true)),
- tap(() => {
- const fileName = 'dist/styles.css';
- const content = virtualFs.fileBufferToString(host.scopedSync().read(normalize(fileName)));
- // Large image should not be inlined, and gradient should be there.
- expect(content).toMatch(
- /url\(['"]?large\.png['"]?\),\s+linear-gradient\(to bottom, #0e40fa 25%, #0654f4 75%\);/);
- // Small image should be inlined.
- expect(content).toMatch(/url\(\\?['"]data:image\/svg\+xml/);
- // Small image with param should not be inlined.
- expect(content).toMatch(/url\(['"]?small-id\.svg#testID['"]?\)/);
- }),
- tap(() => {
- const fileName = 'dist/main.js';
- const content = virtualFs.fileBufferToString(host.scopedSync().read(normalize(fileName)));
- // Large image should not be inlined.
- expect(content).toMatch(/url\((?:['"]|\\')?large\.png(?:['"]|\\')?\)/);
- // Small image should be inlined.
- expect(content).toMatch(/url\(\\?['"]data:image\/svg\+xml/);
- }),
- tap(() => {
- expect(host.scopedSync().exists(normalize('dist/small.svg'))).toBe(false);
- expect(host.scopedSync().exists(normalize('dist/large.png'))).toBe(true);
- expect(host.scopedSync().exists(normalize('dist/small-id.svg'))).toBe(true);
- }),
- // TODO: find a way to check logger/output for warnings.
- // if (stdout.match(/postcss-url: \.+: Can't read file '\.+', ignoring/)) {
- // throw new Error('Expected no postcss-url file read warnings.');
- // }
- ).toPromise().then(done, done.fail);
- });
-
it(`supports font-awesome imports`, (done) => {
host.writeMultipleFiles({
'src/styles.scss': `
@@ -407,8 +351,6 @@ describe('Browser Builder styles', () => {
h3 { background: url('/assets/component-img-absolute.svg'); }
h4 { background: url('../assets/component-img-relative.png'); }
`,
- // Use a small SVG for the absolute image to help validate that it is being referenced,
- // because it is so small it would be inlined usually.
'src/assets/global-img-absolute.svg': imgSvg,
'src/assets/component-img-absolute.svg': imgSvg,
});
@@ -427,12 +369,12 @@ describe('Browser Builder styles', () => {
expect(styles).toContain(`url('global-img-relative.png')`);
expect(main).toContain(`url('/assets/component-img-absolute.svg')`);
expect(main).toContain(`url('component-img-relative.png')`);
- expect(host.scopedSync().exists(normalize('dist/global-img-absolute.svg')))
- .toBe(false);
+ expect(host.scopedSync().exists(normalize('dist/assets/global-img-absolute.svg')))
+ .toBe(true);
expect(host.scopedSync().exists(normalize('dist/global-img-relative.png')))
.toBe(true);
- expect(host.scopedSync().exists(normalize('dist/component-img-absolute.svg')))
- .toBe(false);
+ expect(host.scopedSync().exists(normalize('dist/assets/component-img-absolute.svg')))
+ .toBe(true);
expect(host.scopedSync().exists(normalize('dist/component-img-relative.png')))
.toBe(true);
}),
diff --git a/tests/legacy-cli/e2e/tests/build/styles/inline-urls.ts b/tests/legacy-cli/e2e/tests/build/styles/inline-urls.ts
deleted file mode 100644
index a1ff323a1067..000000000000
--- a/tests/legacy-cli/e2e/tests/build/styles/inline-urls.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import { ng, silentNpm } from '../../../utils/process';
-import {
- expectFileToMatch,
- expectFileToExist,
- expectFileMatchToExist,
- writeMultipleFiles
-} from '../../../utils/fs';
-import { copyProjectAsset } from '../../../utils/assets';
-import { expectToFail } from '../../../utils/utils';
-import { updateJsonFile } from '../../../utils/project';
-
-const imgSvg = `
-
-`;
-
-export default function () {
- // TODO(architect): Delete this test. It is now in devkit/build-angular.
-
- return Promise.resolve()
- .then(() => silentNpm('install', 'font-awesome@4.7.0'))
- .then(() => writeMultipleFiles({
- 'src/styles.scss': `
- $fa-font-path: "~font-awesome/fonts";
- @import "~font-awesome/scss/font-awesome";
- h1 { background: url('./assets/large.png'),
- linear-gradient(to bottom, #0e40fa 25%, #0654f4 75%); }
- h2 { background: url('./assets/small.svg'); }
- p { background: url(./assets/small-id.svg#testID); }
- `,
- 'src/app/app.component.css': `
- h3 { background: url('../assets/small.svg'); }
- h4 { background: url("../assets/large.png"); }
- `,
- 'src/assets/small.svg': imgSvg,
- 'src/assets/small-id.svg': imgSvg
- }))
- .then(() => copyProjectAsset('images/spectrum.png', './src/assets/large.png'))
- .then(() => updateJsonFile('angular.json', workspaceJson => {
- const appArchitect = workspaceJson.projects['test-project'].targets;
- appArchitect.build.options.styles = [
- { input: 'src/styles.scss' }
- ];
- }))
- .then(() => ng('build', '--extract-css', '--aot'))
- .then(({ stdout }) => {
- if (stdout.match(/postcss-url: \.+: Can't read file '\.+', ignoring/)) {
- throw new Error('Expected no postcss-url file read warnings.');
- }
- })
- // Check paths are correctly generated.
- .then(() => expectFileToMatch('dist/test-project/styles.css',
- /url\(['"]?large\.png['"]?\),\s+linear-gradient\(to bottom, #0e40fa 25%, #0654f4 75%\);/))
- .then(() => expectFileToMatch('dist/test-project/styles.css',
- /url\(\\?['"]data:image\/svg\+xml/))
- .then(() => expectFileToMatch('dist/test-project/styles.css',
- /url\(['"]?small-id\.svg#testID['"]?\)/))
- .then(() => expectFileToMatch('dist/test-project/main.js',
- /url\(\\?['"]data:image\/svg\+xml/))
- .then(() => expectFileToMatch('dist/test-project/main.js',
- /url\((?:['"]|\\')?large\.png(?:['"]|\\')?\)/))
- // Check files are correctly created.
- .then(() => expectToFail(() => expectFileToExist('dist/test-project/small.svg')))
- .then(() => expectFileMatchToExist('./dist/test-project', /large\.png/))
- .then(() => expectFileMatchToExist('./dist/test-project', /small-id\.svg/));
-}
From 41d20873b16ccb0e2e02db228bb4b3940ce8f3bd Mon Sep 17 00:00:00 2001
From: Filipe Silva
Date: Wed, 29 Aug 2018 17:27:39 +0100
Subject: [PATCH 0025/1703] fix(@angular-devkit/build-angular): remove
workaround for uglify-es (#12033)
Fix https://github.com/angular/angular-cli/pull/11996#issuecomment-416514015
---
.../src/angular-cli-files/models/webpack-configs/common.ts | 4 ----
1 file changed, 4 deletions(-)
diff --git a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts
index c65d78fa07e5..29eea7c418f7 100644
--- a/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts
+++ b/packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts
@@ -229,10 +229,6 @@ export function getCommonConfig(wco: WebpackConfigOptions) {
// PURE comments work best with 3 passes.
// See https://github.com/webpack/webpack/issues/2899#issuecomment-317425926.
passes: buildOptions.buildOptimizer ? 3 : 1,
- // Workaround known uglify-es issue
- // See https://github.com/mishoo/UglifyJS2/issues/2949#issuecomment-368070307
- inline: wco.supportES2015 ? 1 : 3,
-
global_defs: {
ngDevMode: false,
},
From b0897c0306c16f3332666b1e7b00ce784c309934 Mon Sep 17 00:00:00 2001
From: Alan Agius
Date: Thu, 30 Aug 2018 22:46:52 +0200
Subject: [PATCH 0026/1703] fix(@schematics/angular): better error message when
finding only routing modules (#11994)
Closes #11961
---
.../schematics/angular/utility/find-module.ts | 23 +++++++++++++------
.../angular/utility/find-module_spec.ts | 12 ++++++++++
2 files changed, 28 insertions(+), 7 deletions(-)
diff --git a/packages/schematics/angular/utility/find-module.ts b/packages/schematics/angular/utility/find-module.ts
index cf55bbd2a88f..6086523b6a12 100644
--- a/packages/schematics/angular/utility/find-module.ts
+++ b/packages/schematics/angular/utility/find-module.ts
@@ -28,7 +28,7 @@ export function findModuleFromOptions(host: Tree, options: ModuleOptions): Path
if (!options.module) {
const pathToCheck = (options.path || '')
- + (options.flat ? '' : '/' + strings.dasherize(options.name));
+ + (options.flat ? '' : '/' + strings.dasherize(options.name));
return normalize(findModule(host, pathToCheck));
} else {
@@ -59,12 +59,17 @@ export function findModule(host: Tree, generateDir: string): Path {
const moduleRe = /\.module\.ts$/;
const routingModuleRe = /-routing\.module\.ts/;
+ let foundRoutingModule = false;
+
while (dir) {
- const matches = dir.subfiles.filter(p => moduleRe.test(p) && !routingModuleRe.test(p));
+ const allMatches = dir.subfiles.filter(p => moduleRe.test(p));
+ const filteredMatches = allMatches.filter(p => !routingModuleRe.test(p));
+
+ foundRoutingModule = foundRoutingModule || allMatches.length !== filteredMatches.length;
- if (matches.length == 1) {
- return join(dir.path, matches[0]);
- } else if (matches.length > 1) {
+ if (filteredMatches.length == 1) {
+ return join(dir.path, filteredMatches[0]);
+ } else if (filteredMatches.length > 1) {
throw new Error('More than one module matches. Use skip-import option to skip importing '
+ 'the component into the closest module.');
}
@@ -72,8 +77,12 @@ export function findModule(host: Tree, generateDir: string): Path {
dir = dir.parent;
}
- throw new Error('Could not find an NgModule. Use the skip-import '
- + 'option to skip importing in NgModule.');
+ const errorMsg = foundRoutingModule ? 'Could not find a non Routing NgModule.'
+ + '\nModules with suffix \'-routing.module\' are strictly reserved for routing.'
+ + '\nUse the skip-import option to skip importing in NgModule.'
+ : 'Could not find an NgModule. Use the skip-import option to skip importing in NgModule.';
+
+ throw new Error(errorMsg);
}
/**
diff --git a/packages/schematics/angular/utility/find-module_spec.ts b/packages/schematics/angular/utility/find-module_spec.ts
index 8b92f4ebc54a..4caa1d1b1618 100644
--- a/packages/schematics/angular/utility/find-module_spec.ts
+++ b/packages/schematics/angular/utility/find-module_spec.ts
@@ -53,6 +53,18 @@ describe('find-module', () => {
}
});
+ it('should throw if only routing modules were found', () => {
+ host = new EmptyTree();
+ host.create('/foo/src/app/anything-routing.module.ts', 'anything routing module');
+
+ try {
+ findModule(host, 'foo/src/app/anything-routing');
+ throw new Error('Succeeded, should have failed');
+ } catch (err) {
+ expect(err.message).toMatch(/Could not find a non Routing NgModule/);
+ }
+ });
+
it('should throw if two modules found', () => {
try {
host = new EmptyTree();
From 247b20d37a917b0540d16ca3c6ea09e50f75a644 Mon Sep 17 00:00:00 2001
From: Adrian Moos
Date: Thu, 30 Aug 2018 22:47:15 +0200
Subject: [PATCH 0027/1703] style(@schematics/angular): fix typo and English in
environment.ts (#12023)
---
.../application/files/src/environments/environment.ts | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/packages/schematics/angular/application/files/src/environments/environment.ts b/packages/schematics/angular/application/files/src/environments/environment.ts
index 600fdaf8a00a..7b4f817adb75 100644
--- a/packages/schematics/angular/application/files/src/environments/environment.ts
+++ b/packages/schematics/angular/application/files/src/environments/environment.ts
@@ -1,5 +1,5 @@
// This file can be replaced during build by using the `fileReplacements` array.
-// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.
+// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
@@ -7,9 +7,10 @@ export const environment = {
};
/*
- * In development mode, for easier debugging, you can ignore zone related error
- * stack frames such as `zone.run`/`zoneDelegate.invokeTask` by importing the
- * below file. Don't forget to comment it out in production mode
- * because it will have a performance impact when errors are thrown
+ * For easier debugging in development mode, you can import the following file
+ * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
+ *
+ * This import should be commented out in production mode because it will have a negative impact
+ * on performance if an error is thrown.
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
From 7e63dd791b67ad49de4b0b2f55ecc386e4cad20e Mon Sep 17 00:00:00 2001
From: clydin <19598772+clydin@users.noreply.github.com>
Date: Thu, 30 Aug 2018 18:33:31 -0400
Subject: [PATCH 0028/1703] build: limit rxjs to 6.2.x due to a defect in 6.3.0
(#12048)
---
package.json | 4 ++--
packages/angular/cli/package.json | 2 +-
packages/angular_devkit/architect/package.json | 2 +-
packages/angular_devkit/architect/test/package.json | 2 +-
packages/angular_devkit/architect_cli/package.json | 2 +-
packages/angular_devkit/build_angular/package.json | 2 +-
packages/angular_devkit/build_ng_packagr/package.json | 2 +-
packages/angular_devkit/build_webpack/package.json | 2 +-
packages/angular_devkit/core/package.json | 2 +-
packages/angular_devkit/schematics/package.json | 2 +-
packages/angular_devkit/schematics_cli/package.json | 2 +-
packages/ngtools/webpack/package.json | 2 +-
packages/schematics/angular/utility/latest-versions.ts | 2 +-
packages/schematics/update/package.json | 2 +-
.../build_angular/hello-world-app/package.json | 2 +-
yarn.lock | 6 +++---
16 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/package.json b/package.json
index 8b881cb9159e..04bba652e809 100644
--- a/package.json
+++ b/package.json
@@ -93,7 +93,7 @@
"license-checker": "^20.1.0",
"minimatch": "^3.0.4",
"minimist": "^1.2.0",
- "rxjs": "^6.0.0",
+ "rxjs": "~6.2.0",
"semver": "^5.3.0",
"source-map": "^0.5.6",
"source-map-support": "^0.5.0",
@@ -107,6 +107,6 @@
"resolutions": {
"@types/webpack": "4.4.0",
"@types/webpack-dev-server": "2.9.4",
- "rxjs": "6.0.0"
+ "rxjs": "~6.2.0"
}
}
diff --git a/packages/angular/cli/package.json b/packages/angular/cli/package.json
index 6c658e474a02..ef122769ae77 100644
--- a/packages/angular/cli/package.json
+++ b/packages/angular/cli/package.json
@@ -38,7 +38,7 @@
"inquirer": "^6.1.0",
"json-schema-traverse": "^0.4.1",
"opn": "^5.3.0",
- "rxjs": "^6.0.0",
+ "rxjs": "~6.2.0",
"semver": "^5.1.0",
"symbol-observable": "^1.2.0",
"yargs-parser": "^10.0.0"
diff --git a/packages/angular_devkit/architect/package.json b/packages/angular_devkit/architect/package.json
index 496454e7d68e..442fdee61353 100644
--- a/packages/angular_devkit/architect/package.json
+++ b/packages/angular_devkit/architect/package.json
@@ -6,6 +6,6 @@
"typings": "src/index.d.ts",
"dependencies": {
"@angular-devkit/core": "0.0.0",
- "rxjs": "^6.0.0"
+ "rxjs": "~6.2.0"
}
}
\ No newline at end of file
diff --git a/packages/angular_devkit/architect/test/package.json b/packages/angular_devkit/architect/test/package.json
index b4617c03ea0d..f433fb9a3eca 100644
--- a/packages/angular_devkit/architect/test/package.json
+++ b/packages/angular_devkit/architect/test/package.json
@@ -1,6 +1,6 @@
{
"builders": "builders.json",
"dependencies": {
- "rxjs": "^6.0.0"
+ "rxjs": "~6.2.0"
}
}
diff --git a/packages/angular_devkit/architect_cli/package.json b/packages/angular_devkit/architect_cli/package.json
index ec7373fd2699..9b54a4cbd89c 100644
--- a/packages/angular_devkit/architect_cli/package.json
+++ b/packages/angular_devkit/architect_cli/package.json
@@ -16,6 +16,6 @@
"@angular-devkit/architect": "0.0.0",
"minimist": "^1.2.0",
"symbol-observable": "^1.2.0",
- "rxjs": "^6.0.0"
+ "rxjs": "~6.2.0"
}
}
diff --git a/packages/angular_devkit/build_angular/package.json b/packages/angular_devkit/build_angular/package.json
index 35bffdbf66fa..68284ad44e6d 100644
--- a/packages/angular_devkit/build_angular/package.json
+++ b/packages/angular_devkit/build_angular/package.json
@@ -36,7 +36,7 @@
"postcss-loader": "^2.1.5",
"postcss-url": "^7.3.2",
"raw-loader": "^0.5.1",
- "rxjs": "^6.0.0",
+ "rxjs": "~6.2.0",
"sass-loader": "^7.1.0",
"semver": "^5.5.0",
"source-map-support": "^0.5.0",
diff --git a/packages/angular_devkit/build_ng_packagr/package.json b/packages/angular_devkit/build_ng_packagr/package.json
index ef34febed104..a9eebffa6c25 100644
--- a/packages/angular_devkit/build_ng_packagr/package.json
+++ b/packages/angular_devkit/build_ng_packagr/package.json
@@ -8,7 +8,7 @@
"dependencies": {
"@angular-devkit/architect": "0.0.0",
"@angular-devkit/core": "0.0.0",
- "rxjs": "^6.0.0",
+ "rxjs": "~6.2.0",
"semver": "^5.3.0"
},
"peerDependencies": {
diff --git a/packages/angular_devkit/build_webpack/package.json b/packages/angular_devkit/build_webpack/package.json
index 63c07c6c97a0..4f4b343eae32 100644
--- a/packages/angular_devkit/build_webpack/package.json
+++ b/packages/angular_devkit/build_webpack/package.json
@@ -8,7 +8,7 @@
"dependencies": {
"@angular-devkit/architect": "0.0.0",
"@angular-devkit/core": "0.0.0",
- "rxjs": "^6.0.0"
+ "rxjs": "~6.2.0"
},
"peerDependencies": {
"webpack": "^4.6.0",
diff --git a/packages/angular_devkit/core/package.json b/packages/angular_devkit/core/package.json
index b63e953adeec..b48d40207cb5 100644
--- a/packages/angular_devkit/core/package.json
+++ b/packages/angular_devkit/core/package.json
@@ -12,6 +12,6 @@
"chokidar": "^2.0.3",
"fast-json-stable-stringify": "^2.0.0",
"source-map": "^0.5.6",
- "rxjs": "^6.0.0"
+ "rxjs": "~6.2.0"
}
}
diff --git a/packages/angular_devkit/schematics/package.json b/packages/angular_devkit/schematics/package.json
index a964deec6899..44f474c8db8f 100644
--- a/packages/angular_devkit/schematics/package.json
+++ b/packages/angular_devkit/schematics/package.json
@@ -14,6 +14,6 @@
],
"dependencies": {
"@angular-devkit/core": "0.0.0",
- "rxjs": "^6.0.0"
+ "rxjs": "~6.2.0"
}
}
diff --git a/packages/angular_devkit/schematics_cli/package.json b/packages/angular_devkit/schematics_cli/package.json
index afe13e9cb173..609a07421c9d 100644
--- a/packages/angular_devkit/schematics_cli/package.json
+++ b/packages/angular_devkit/schematics_cli/package.json
@@ -23,6 +23,6 @@
"@schematics/schematics": "0.0.0",
"minimist": "^1.2.0",
"symbol-observable": "^1.2.0",
- "rxjs": "^6.0.0"
+ "rxjs": "~6.2.0"
}
}
diff --git a/packages/ngtools/webpack/package.json b/packages/ngtools/webpack/package.json
index e7505a21e3d9..e49d0c2868f5 100644
--- a/packages/ngtools/webpack/package.json
+++ b/packages/ngtools/webpack/package.json
@@ -26,7 +26,7 @@
},
"dependencies": {
"@angular-devkit/core": "0.0.0",
- "rxjs": "^6.0.0",
+ "rxjs": "~6.2.0",
"tree-kill": "^1.0.0",
"webpack-sources": "^1.1.0"
},
diff --git a/packages/schematics/angular/utility/latest-versions.ts b/packages/schematics/angular/utility/latest-versions.ts
index 095b7cf1e6a1..ed46b138584e 100644
--- a/packages/schematics/angular/utility/latest-versions.ts
+++ b/packages/schematics/angular/utility/latest-versions.ts
@@ -9,7 +9,7 @@
export const latestVersions = {
// These versions should be kept up to date with latest Angular peer dependencies.
Angular: '^6.1.0',
- RxJs: '^6.0.0',
+ RxJs: '~6.2.0',
ZoneJs: '~0.8.26',
TypeScript: '~2.9.2',
// The versions below must be manually updated when making a new devkit release.
diff --git a/packages/schematics/update/package.json b/packages/schematics/update/package.json
index 7f5346566eea..9d8b40047fc5 100644
--- a/packages/schematics/update/package.json
+++ b/packages/schematics/update/package.json
@@ -15,6 +15,6 @@
"npm-registry-client": "^8.5.1",
"semver": "^5.3.0",
"semver-intersect": "^1.1.2",
- "rxjs": "^6.0.0"
+ "rxjs": "~6.2.0"
}
}
\ No newline at end of file
diff --git a/tests/angular_devkit/build_angular/hello-world-app/package.json b/tests/angular_devkit/build_angular/hello-world-app/package.json
index df5e3fd1255f..e45a8c5b0c7c 100644
--- a/tests/angular_devkit/build_angular/hello-world-app/package.json
+++ b/tests/angular_devkit/build_angular/hello-world-app/package.json
@@ -22,7 +22,7 @@
"@angular/platform-browser-dynamic": "^6.0.0-rc.0",
"@angular/router": "^6.0.0-rc.0",
"core-js": "^2.4.1",
- "rxjs": "^6.0.0",
+ "rxjs": "~6.2.0",
"zone.js": "^0.8.19"
},
"devDependencies": {
diff --git a/yarn.lock b/yarn.lock
index 88e1463881ab..41f1c83ef417 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6371,9 +6371,9 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies:
aproba "^1.1.1"
-rxjs@6.0.0, rxjs@^6.0.0, rxjs@^6.1.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.0.0.tgz#d647e029b5854617f994c82fe57a4c6747b352da"
+rxjs@^6.0.0, rxjs@^6.1.0, rxjs@~6.2.0:
+ version "6.2.2"
+ resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.2.2.tgz#eb75fa3c186ff5289907d06483a77884586e1cf9"
dependencies:
tslib "^1.9.0"
From 39b4cc030b0deba88627d659df01aaf3663f0265 Mon Sep 17 00:00:00 2001
From: Hans Larsen
Date: Thu, 30 Aug 2018 16:34:02 -0700
Subject: [PATCH 0029/1703] build: fix RxJS invalid typings
---
packages/angular_devkit/core/src/json/schema/registry.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/angular_devkit/core/src/json/schema/registry.ts b/packages/angular_devkit/core/src/json/schema/registry.ts
index b16d79495839..89130f10d563 100644
--- a/packages/angular_devkit/core/src/json/schema/registry.ts
+++ b/packages/angular_devkit/core/src/json/schema/registry.ts
@@ -258,7 +258,7 @@ export class CoreSchemaRegistry implements SchemaRegistry {
return Promise.reject(err);
}));
}),
- switchMap(([data, valid]) => {
+ switchMap(([data, valid]: [JsonValue, boolean]) => {
if (!validationOptions.withPrompts) {
return observableOf([data, valid]);
}
@@ -274,7 +274,7 @@ export class CoreSchemaRegistry implements SchemaRegistry {
return observableOf([data, valid]);
}
}),
- switchMap(([data, valid]) => {
+ switchMap(([data, valid]: [JsonValue, boolean]) => {
if (valid) {
return observableOf(data).pipe(
...[...this._post].map(visitor => concatMap((data: JsonValue) => {
@@ -287,7 +287,7 @@ export class CoreSchemaRegistry implements SchemaRegistry {
return observableOf([data, valid]);
}
}),
- map(([data, valid]) => {
+ map(([data, valid]: [JsonValue, boolean]) => {
if (valid) {
return { data, success: true } as SchemaValidatorResult;
}
From 189563d2778d4691ee2fe247465ec6c16a348c3b Mon Sep 17 00:00:00 2001
From: Hans
Date: Tue, 4 Sep 2018 14:04:21 -0700
Subject: [PATCH 0030/1703] refactor(@angular/cli): rename getset command to
deprecated
---
packages/angular/cli/commands.json | 4 ++--
.../angular/cli/commands/deprecated-impl.ts | 21 +++++++++++++++++++
.../commands/{getset.json => deprecated.json} | 6 +++---
packages/angular/cli/commands/getset-impl.ts | 20 ------------------
4 files changed, 26 insertions(+), 25 deletions(-)
create mode 100644 packages/angular/cli/commands/deprecated-impl.ts
rename packages/angular/cli/commands/{getset.json => deprecated.json} (66%)
delete mode 100644 packages/angular/cli/commands/getset-impl.ts
diff --git a/packages/angular/cli/commands.json b/packages/angular/cli/commands.json
index a4a0b9360211..be69ff2dbe62 100644
--- a/packages/angular/cli/commands.json
+++ b/packages/angular/cli/commands.json
@@ -7,8 +7,8 @@
"make-this-awesome": "./commands/easter-egg.json",
"eject": "./commands/eject.json",
"generate": "./commands/generate.json",
- "get": "./commands/getset.json",
- "set": "./commands/getset.json",
+ "get": "./commands/deprecated.json",
+ "set": "./commands/deprecated.json",
"help": "./commands/help.json",
"lint": "./commands/lint.json",
"new": "./commands/new.json",
diff --git a/packages/angular/cli/commands/deprecated-impl.ts b/packages/angular/cli/commands/deprecated-impl.ts
new file mode 100644
index 000000000000..85c8af440384
--- /dev/null
+++ b/packages/angular/cli/commands/deprecated-impl.ts
@@ -0,0 +1,21 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import { Command } from '../models/command';
+
+export class DeprecatedCommand extends Command {
+ public async run() {
+ let message = 'The "${this.description.name}" command has been deprecated.';
+ if (this.description.name == 'get' || this.description.name == 'set') {
+ message = 'get/set have been deprecated in favor of the config command.';
+ }
+
+ this.logger.error(message);
+
+ return 0;
+ }
+}
diff --git a/packages/angular/cli/commands/getset.json b/packages/angular/cli/commands/deprecated.json
similarity index 66%
rename from packages/angular/cli/commands/getset.json
rename to packages/angular/cli/commands/deprecated.json
index 5d05f348a0ac..ee77bbd03d20 100644
--- a/packages/angular/cli/commands/getset.json
+++ b/packages/angular/cli/commands/deprecated.json
@@ -1,12 +1,12 @@
{
"$schema": "http://json-schema.org/schema",
- "id": "GetSetCommandOptions",
+ "$id": "ng-cli://commands/deprecated.json",
"description": "Deprecated in favor of config command.",
"$longDescription": "",
- "$impl": "./getset-impl#GetSetCommand",
+ "$impl": "./deprecated-impl#DeprecatedCommand",
"$hidden": true,
"$type": "deprecated",
"type": "object"
-}
\ No newline at end of file
+}
diff --git a/packages/angular/cli/commands/getset-impl.ts b/packages/angular/cli/commands/getset-impl.ts
deleted file mode 100644
index 0e6c26795820..000000000000
--- a/packages/angular/cli/commands/getset-impl.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- * @license
- * Copyright Google Inc. All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.io/license
- */
-
-import { Command } from '../models/command';
-
-export interface Options {
- keyword: string;
- search?: boolean;
-}
-
-export class GetSetCommand extends Command {
- public async run(_options: Options) {
- this.logger.warn('get/set have been deprecated in favor of the config command.');
- }
-}
From 4a25d7c9b4ebfabca2a4498c977003edbc792467 Mon Sep 17 00:00:00 2001
From: Hans
Date: Tue, 4 Sep 2018 14:04:43 -0700
Subject: [PATCH 0031/1703] test: get-set deprecation should check stderr now
---
.../e2e/tests/commands/config/get-set-deprecation.ts | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/tests/legacy-cli/e2e/tests/commands/config/get-set-deprecation.ts b/tests/legacy-cli/e2e/tests/commands/config/get-set-deprecation.ts
index d5b898b82825..ad9d3f106d17 100644
--- a/tests/legacy-cli/e2e/tests/commands/config/get-set-deprecation.ts
+++ b/tests/legacy-cli/e2e/tests/commands/config/get-set-deprecation.ts
@@ -5,14 +5,14 @@ export default function() {
const depRegEx = /get\/set have been deprecated in favor of the config command\./;
return Promise.resolve()
.then(() => ng('get'))
- .then(({ stdout }) => {
- if (!stdout.match(depRegEx)) {
+ .then(({ stderr }) => {
+ if (!stderr.match(depRegEx)) {
throw new Error(`Expected deprecation warning.`);
}
})
.then(() => ng('set'))
- .then(({ stdout }) => {
- if (!stdout.match(depRegEx)) {
+ .then(({ stderr }) => {
+ if (!stderr.match(depRegEx)) {
throw new Error(`Expected deprecation warning.`);
}
});
From 0b84c1d4b06c5ec43f59a10c54996521a149d920 Mon Sep 17 00:00:00 2001
From: Hans
Date: Tue, 4 Sep 2018 14:15:37 -0700
Subject: [PATCH 0032/1703] feat(@angular-devkit/core): move findTypes in
utility and export
---
.../core/src/json/schema/transforms.ts | 54 ++-------------
.../core/src/json/schema/utility.ts | 67 +++++++++++++++++++
2 files changed, 71 insertions(+), 50 deletions(-)
create mode 100644 packages/angular_devkit/core/src/json/schema/utility.ts
diff --git a/packages/angular_devkit/core/src/json/schema/transforms.ts b/packages/angular_devkit/core/src/json/schema/transforms.ts
index 513d7943b0d5..9c6694e4e32e 100644
--- a/packages/angular_devkit/core/src/json/schema/transforms.ts
+++ b/packages/angular_devkit/core/src/json/schema/transforms.ts
@@ -7,55 +7,7 @@
*/
import { JsonObject, JsonValue, isJsonObject } from '../interface';
import { JsonPointer } from './interface';
-
-const allTypes = ['string', 'integer', 'number', 'object', 'array', 'boolean', 'null'];
-
-function findTypes(schema: JsonObject): Set {
- if (!schema) {
- return new Set();
- }
-
- let potentials: Set;
- if (typeof schema.type === 'string') {
- potentials = new Set([schema.type]);
- } else if (Array.isArray(schema.type)) {
- potentials = new Set(schema.type as string[]);
- } else {
- potentials = new Set(allTypes);
- }
-
- if (isJsonObject(schema.not)) {
- const notTypes = findTypes(schema.not);
- potentials = new Set([...potentials].filter(p => !notTypes.has(p)));
- }
-
- if (Array.isArray(schema.allOf)) {
- for (const sub of schema.allOf) {
- const types = findTypes(sub as JsonObject);
- potentials = new Set([...potentials].filter(p => types.has(p)));
- }
- }
-
- if (Array.isArray(schema.oneOf)) {
- let options = new Set();
- for (const sub of schema.oneOf) {
- const types = findTypes(sub as JsonObject);
- options = new Set([...options, ...types]);
- }
- potentials = new Set([...potentials].filter(p => options.has(p)));
- }
-
- if (Array.isArray(schema.anyOf)) {
- let options = new Set();
- for (const sub of schema.anyOf) {
- const types = findTypes(sub as JsonObject);
- options = new Set([...options, ...types]);
- }
- potentials = new Set([...potentials].filter(p => options.has(p)));
- }
-
- return potentials;
-}
+import { getTypesOfSchema } from './utility';
export function addUndefinedDefaults(
value: JsonValue,
@@ -66,7 +18,7 @@ export function addUndefinedDefaults(
return value;
}
- const types = findTypes(schema);
+ const types = getTypesOfSchema(schema);
if (types.size === 0) {
return value;
}
@@ -110,6 +62,8 @@ export function addUndefinedDefaults(
for (const propName of Object.getOwnPropertyNames(schema.properties)) {
if (propName in newValue) {
continue;
+ } else if (propName == '$schema') {
+ continue;
}
// TODO: Does not currently handle more complex schemas (oneOf/anyOf/etc.)
diff --git a/packages/angular_devkit/core/src/json/schema/utility.ts b/packages/angular_devkit/core/src/json/schema/utility.ts
new file mode 100644
index 000000000000..5e1608312ea8
--- /dev/null
+++ b/packages/angular_devkit/core/src/json/schema/utility.ts
@@ -0,0 +1,67 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import { JsonObject, isJsonObject } from '../interface';
+
+
+const allTypes = ['string', 'integer', 'number', 'object', 'array', 'boolean', 'null'];
+
+export function getTypesOfSchema(schema: JsonObject | true): Set {
+ if (!schema) {
+ return new Set();
+ }
+ if (schema === true) {
+ return new Set(allTypes);
+ }
+
+ let potentials: Set;
+ if (typeof schema.type === 'string') {
+ potentials = new Set([schema.type]);
+ } else if (Array.isArray(schema.type)) {
+ potentials = new Set(schema.type as string[]);
+ } else {
+ potentials = new Set(allTypes);
+ }
+
+ if (isJsonObject(schema.not)) {
+ const notTypes = getTypesOfSchema(schema.not);
+ potentials = new Set([...potentials].filter(p => !notTypes.has(p)));
+ }
+
+ if (Array.isArray(schema.allOf)) {
+ for (const sub of schema.allOf) {
+ const types = getTypesOfSchema(sub as JsonObject);
+ potentials = new Set([...potentials].filter(p => types.has(p)));
+ }
+ }
+
+ if (Array.isArray(schema.oneOf)) {
+ let options = new Set();
+ for (const sub of schema.oneOf) {
+ const types = getTypesOfSchema(sub as JsonObject);
+ options = new Set([...options, ...types]);
+ }
+ potentials = new Set([...potentials].filter(p => options.has(p)));
+ }
+
+ if (Array.isArray(schema.anyOf)) {
+ let options = new Set();
+ for (const sub of schema.anyOf) {
+ const types = getTypesOfSchema(sub as JsonObject);
+ options = new Set([...options, ...types]);
+ }
+ potentials = new Set([...potentials].filter(p => options.has(p)));
+ }
+
+ if (schema.properties) {
+ potentials.add('object');
+ } else if (schema.items) {
+ potentials.add('array');
+ }
+
+ return potentials;
+}
From 1b5c0082efb2224be80f3b8128a1c17a1f1bac99 Mon Sep 17 00:00:00 2001
From: Hans
Date: Tue, 4 Sep 2018 14:18:14 -0700
Subject: [PATCH 0033/1703] feat(@angular-devkit/core): add levenshtein
distance utility
---
.../angular_devkit/core/src/utils/strings.ts | 49 +++++++++++++++++++
1 file changed, 49 insertions(+)
diff --git a/packages/angular_devkit/core/src/utils/strings.ts b/packages/angular_devkit/core/src/utils/strings.ts
index 4743e9cb6555..ff3c0ce7cec3 100644
--- a/packages/angular_devkit/core/src/utils/strings.ts
+++ b/packages/angular_devkit/core/src/utils/strings.ts
@@ -127,3 +127,52 @@ export function underscore(str: string): string {
export function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.substr(1);
}
+
+/**
+ * Calculate the levenshtein distance of two strings.
+ * See https://en.wikipedia.org/wiki/Levenshtein_distance.
+ * Based off https://gist.github.com/andrei-m/982927 (for using the faster dynamic programming
+ * version).
+ *
+ * @param a String a.
+ * @param b String b.
+ * @returns A number that represents the distance between the two strings. The greater the number
+ * the more distant the strings are from each others.
+ */
+export function levenshtein(a: string, b: string): number {
+ if (a.length == 0) {
+ return b.length;
+ }
+ if (b.length == 0) {
+ return a.length;
+ }
+
+ const matrix = [];
+
+ // increment along the first column of each row
+ for (let i = 0; i <= b.length; i++) {
+ matrix[i] = [i];
+ }
+
+ // increment each column in the first row
+ for (let j = 0; j <= a.length; j++) {
+ matrix[0][j] = j;
+ }
+
+ // Fill in the rest of the matrix
+ for (let i = 1; i <= b.length; i++) {
+ for (let j = 1; j <= a.length; j++) {
+ if (b.charAt(i - 1) == a.charAt(j - 1)) {
+ matrix[i][j] = matrix[i - 1][j - 1];
+ } else {
+ matrix[i][j] = Math.min(
+ matrix[i - 1][j - 1] + 1, // substitution
+ matrix[i][j - 1] + 1, // insertion
+ matrix[i - 1][j] + 1, // deletion
+ );
+ }
+ }
+ }
+
+ return matrix[b.length][a.length];
+}
From 75d682b2716a3d1203da1812cf3456bb6e924048 Mon Sep 17 00:00:00 2001
From: Hans
Date: Tue, 4 Sep 2018 14:29:13 -0700
Subject: [PATCH 0034/1703] refactor(@angular/cli): use smart default instead
of overwriting args
---
packages/angular/cli/commands/new-impl.ts | 4 +++-
packages/schematics/angular/ng-new/schema.json | 6 ++++--
packages/schematics/angular/workspace/schema.json | 5 ++++-
3 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/packages/angular/cli/commands/new-impl.ts b/packages/angular/cli/commands/new-impl.ts
index c9229a2e1b37..338d821d150f 100644
--- a/packages/angular/cli/commands/new-impl.ts
+++ b/packages/angular/cli/commands/new-impl.ts
@@ -44,11 +44,13 @@ export class NewCommand extends SchematicCommand {
collectionName = this.parseCollectionName(options);
}
+ // Register the version of the CLI in the registry.
const packageJson = require('../package.json');
- options.version = packageJson.version;
+ const version = packageJson.version;
// Ensure skipGit has a boolean value.
options.skipGit = options.skipGit === undefined ? false : options.skipGit;
+ this._workflow.registry.addSmartDefaultProvider('ng-cli-version', () => version);
return this.runSchematic({
collectionName: collectionName,
diff --git a/packages/schematics/angular/ng-new/schema.json b/packages/schematics/angular/ng-new/schema.json
index c4846c957fba..2c927a03f9bf 100644
--- a/packages/schematics/angular/ng-new/schema.json
+++ b/packages/schematics/angular/ng-new/schema.json
@@ -90,7 +90,10 @@
"version": {
"type": "string",
"description": "The version of the Angular CLI to use.",
- "visible": false
+ "visible": false,
+ "$default": {
+ "$source": "ng-cli-version"
+ }
},
"routing": {
"type": "boolean",
@@ -117,6 +120,5 @@
}
},
"required": [
- "version"
]
}
diff --git a/packages/schematics/angular/workspace/schema.json b/packages/schematics/angular/workspace/schema.json
index 7eac69c5d29a..c5f296fb06c7 100644
--- a/packages/schematics/angular/workspace/schema.json
+++ b/packages/schematics/angular/workspace/schema.json
@@ -69,7 +69,10 @@
"version": {
"type": "string",
"description": "The version of the Angular CLI to use.",
- "visible": false
+ "visible": false,
+ "$default": {
+ "$source": "ng-cli-version"
+ }
}
},
"required": [
From fb99a490bfe8527edc7b3e0356a61d99c50b3912 Mon Sep 17 00:00:00 2001
From: Hans
Date: Tue, 4 Sep 2018 14:29:54 -0700
Subject: [PATCH 0035/1703] feat(@angular-devkit/core): add a parseJsonFile
that shows file path on error
---
.../angular_devkit/core/src/json/parser.ts | 47 +++++++++++++++++--
1 file changed, 43 insertions(+), 4 deletions(-)
diff --git a/packages/angular_devkit/core/src/json/parser.ts b/packages/angular_devkit/core/src/json/parser.ts
index 809b96c318c7..90a9e1b12497 100644
--- a/packages/angular_devkit/core/src/json/parser.ts
+++ b/packages/angular_devkit/core/src/json/parser.ts
@@ -25,15 +25,26 @@ import {
Position,
} from './interface';
+export class JsonException extends BaseException {}
/**
* A character was invalid in this context.
*/
-export class InvalidJsonCharacterException extends BaseException {
+export class InvalidJsonCharacterException extends JsonException {
+ invalidChar: string;
+ line: number;
+ character: number;
+ offset: number;
+
constructor(context: JsonParserContext) {
const pos = context.previous;
- super(`Invalid JSON character: ${JSON.stringify(_peek(context))} `
- + `at ${pos.line}:${pos.character}.`);
+ const invalidChar = JSON.stringify(_peek(context));
+ super(`Invalid JSON character: ${invalidChar} at ${pos.line}:${pos.character}.`);
+
+ this.invalidChar = invalidChar;
+ this.line = pos.line;
+ this.offset = pos.offset;
+ this.character = pos.character;
}
}
@@ -41,12 +52,20 @@ export class InvalidJsonCharacterException extends BaseException {
/**
* More input was expected, but we reached the end of the stream.
*/
-export class UnexpectedEndOfInputException extends BaseException {
+export class UnexpectedEndOfInputException extends JsonException {
constructor(_context: JsonParserContext) {
super(`Unexpected end of file.`);
}
}
+/**
+ * An error happened within a file.
+ */
+export class PathSpecificJsonException extends JsonException {
+ constructor(public path: string, public exception: JsonException) {
+ super(`An error happened at file path ${JSON.stringify(path)}: ${exception.message}`);
+ }
+}
/**
* Context passed around the parser with information about where we currently are in the parse.
@@ -860,3 +879,23 @@ export function parseJson(input: string, mode = JsonParseMode.Default): JsonValu
return parseJsonAst(input, mode).value;
}
+
+/**
+ * Parse a JSON string into its value. This discards the AST and only returns the value itself.
+ * It also absorbs JSON parsing errors and return a new error with the path in it. Useful for
+ * showing errors when parsing from a file.
+ * @param input The string to parse.
+ * @param mode The mode to parse the input with. {@see JsonParseMode}.
+ * @returns {JsonValue} The value represented by the JSON string.
+ */
+export function parseJsonFile(input: string, mode = JsonParseMode.Default, path: string) {
+ try {
+ return parseJson(input, mode);
+ } catch (e) {
+ if (e instanceof JsonException) {
+ throw new PathSpecificJsonException(path, e);
+ } else {
+ throw e;
+ }
+ }
+}
From ff1baab7fca4938ba717a87692b6869eb0ba8579 Mon Sep 17 00:00:00 2001
From: Hans
Date: Tue, 4 Sep 2018 14:42:53 -0700
Subject: [PATCH 0036/1703] feat(@angular/cli): remove yargs-parser and
implement parsing
This is fully backward compatible.
---
packages/angular/cli/custom-typings.d.ts | 38 --
packages/angular/cli/models/command-runner.ts | 500 ++++--------------
packages/angular/cli/models/command.ts | 200 ++++---
packages/angular/cli/models/parser.ts | 296 +++++++++++
packages/angular/cli/models/parser_spec.ts | 73 +++
packages/angular/cli/package.json | 4 +-
yarn.lock | 6 -
7 files changed, 581 insertions(+), 536 deletions(-)
delete mode 100644 packages/angular/cli/custom-typings.d.ts
create mode 100644 packages/angular/cli/models/parser.ts
create mode 100644 packages/angular/cli/models/parser_spec.ts
diff --git a/packages/angular/cli/custom-typings.d.ts b/packages/angular/cli/custom-typings.d.ts
deleted file mode 100644
index b33ea8011637..000000000000
--- a/packages/angular/cli/custom-typings.d.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-/**
- * @license
- * Copyright Google Inc. All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.io/license
- */
-
-// tslint:disable:no-global-tslint-disable no-any
-declare module 'yargs-parser' {
- const parseOptions: any;
- const yargsParser: (args: string | string[], options?: typeof parseOptions) => T;
- export = yargsParser;
-}
-
-declare module 'json-schema-traverse' {
- import { JsonObject } from '@angular-devkit/core';
- interface TraverseOptions {
- allKeys?: boolean;
- }
- type TraverseCallback = (
- schema: JsonObject,
- jsonPointer: string,
- rootSchema: string,
- parentJsonPointer: string,
- parentKeyword: string,
- parentSchema: string,
- property: string) => void;
-
- interface TraverseCallbacks {
- pre?: TraverseCallback;
- post?: TraverseCallback;
- }
-
- const traverse: (schema: object, options: TraverseOptions, cbs: TraverseCallbacks) => void;
-
- export = traverse;
-}
diff --git a/packages/angular/cli/models/command-runner.ts b/packages/angular/cli/models/command-runner.ts
index 8792328df6f1..b1cef78b87d6 100644
--- a/packages/angular/cli/models/command-runner.ts
+++ b/packages/angular/cli/models/command-runner.ts
@@ -5,454 +5,178 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
-
-// tslint:disable:no-global-tslint-disable no-any
import {
- JsonObject,
- deepCopy,
+ JsonParseMode,
+ isJsonObject,
+ json,
logging,
- parseJson,
schema,
- strings as coreStrings,
+ strings,
tags,
} from '@angular-devkit/core';
-import { ExportStringRef } from '@angular-devkit/schematics/tools';
import { readFileSync } from 'fs';
-import { dirname, join } from 'path';
-import { of, throwError } from 'rxjs';
-import { concatMap } from 'rxjs/operators';
-import * as yargsParser from 'yargs-parser';
-import {
- Command,
- CommandConstructor,
- CommandContext,
- CommandScope,
- Option,
-} from '../models/command';
+import { dirname, join, resolve } from 'path';
+import { of } from 'rxjs';
import { findUp } from '../utilities/find-up';
-import { insideProject } from '../utilities/project';
-import { convertSchemaToOptions, parseSchema } from './json-schema';
+import { parseJsonSchemaToCommandDescription } from '../utilities/json-schema';
+import { Command } from './command';
+import {
+ CommandDescription,
+ CommandDescriptionMap,
+ CommandProject,
+} from './interface';
+import * as parser from './parser';
-interface CommandMap {
+export interface CommandMapOptions {
[key: string]: string;
}
-interface CommandMetadata {
- description: string;
- $aliases?: string[];
- $impl: string;
- $scope?: 'in' | 'out';
- $type?: 'architect' | 'schematic';
- $hidden?: boolean;
-}
-
-interface CommandLocation {
- path: string;
- text: string;
- rawData: CommandMetadata;
-}
-
-// Based off https://en.wikipedia.org/wiki/Levenshtein_distance
-// No optimization, really.
-function levenshtein(a: string, b: string): number {
- /* base case: empty strings */
- if (a.length == 0) {
- return b.length;
- }
- if (b.length == 0) {
- return a.length;
- }
-
- // Test if last characters of the strings match.
- const cost = a[a.length - 1] == b[b.length - 1] ? 0 : 1;
-
- /* return minimum of delete char from s, delete char from t, and delete char from both */
- return Math.min(
- levenshtein(a.slice(0, -1), b) + 1,
- levenshtein(a, b.slice(0, -1)) + 1,
- levenshtein(a.slice(0, -1), b.slice(0, -1)) + cost,
- );
-}
-
/**
* Run a command.
* @param args Raw unparsed arguments.
* @param logger The logger to use.
- * @param context Execution context.
+ * @param project Project data structure containing project information.
+ * @param commands The map of supported commands.
*/
export async function runCommand(
args: string[],
logger: logging.Logger,
- context: CommandContext,
- commandMap?: CommandMap,
+ project: CommandProject,
+ commands?: CommandMapOptions,
): Promise {
-
- // if not args supplied, just run the help command.
- if (!args || args.length === 0) {
- args = ['help'];
- }
- const rawOptions = yargsParser(args, { alias: { help: ['h'] }, boolean: [ 'help' ] });
- let commandName = rawOptions._[0] || '';
-
- // remove the command name
- rawOptions._ = rawOptions._.slice(1);
- const executionScope = insideProject()
- ? CommandScope.inProject
- : CommandScope.outsideProject;
-
- if (commandMap === undefined) {
+ if (commands === undefined) {
const commandMapPath = findUp('commands.json', __dirname);
if (commandMapPath === null) {
- logger.fatal('Unable to find command map.');
-
- return 1;
+ throw new Error('Unable to find command map.');
}
const cliDir = dirname(commandMapPath);
const commandsText = readFileSync(commandMapPath).toString('utf-8');
- const commandJson = JSON.parse(commandsText) as { [name: string]: string };
+ const commandJson = json.parseJsonFile(
+ commandsText,
+ JsonParseMode.Loose,
+ commandMapPath,
+ ) as { [name: string]: string };
- commandMap = {};
+ commands = {};
for (const commandName of Object.keys(commandJson)) {
- commandMap[commandName] = join(cliDir, commandJson[commandName]);
+ commands[commandName] = resolve(cliDir, commandJson[commandName]);
}
}
- let commandMetadata = commandName ? findCommand(commandMap, commandName) : null;
-
- if (!commandMetadata && (rawOptions.v || rawOptions.version)) {
- commandName = 'version';
- commandMetadata = findCommand(commandMap, commandName);
- } else if (!commandMetadata && rawOptions.help) {
- commandName = 'help';
- commandMetadata = findCommand(commandMap, commandName);
- }
-
- if (!commandMetadata) {
- if (!commandName) {
- logger.error(tags.stripIndent`
- We could not find a command from the arguments and the help command seems to be disabled.
- This is an issue with the CLI itself. If you see this comment, please report it and
- provide your repository.
- `);
-
- return 1;
- } else {
- const commandsDistance = {} as { [name: string]: number };
- const allCommands = Object.keys(commandMap).sort((a, b) => {
- if (!(a in commandsDistance)) {
- commandsDistance[a] = levenshtein(a, commandName);
- }
- if (!(b in commandsDistance)) {
- commandsDistance[b] = levenshtein(b, commandName);
- }
-
- return commandsDistance[a] - commandsDistance[b];
- });
-
- logger.error(tags.stripIndent`
- The specified command ("${commandName}") is invalid. For a list of available options,
- run "ng help".
- Did you mean "${allCommands[0]}"?
- `);
+ // This registry is exclusively used for flattening schemas, and not for validating.
+ const registry = new schema.CoreSchemaRegistry([]);
+ registry.registerUriHandler((uri: string) => {
+ if (uri.startsWith('ng-cli://')) {
+ const content = readFileSync(join(__dirname, '..', uri.substr('ng-cli://'.length)), 'utf-8');
- return 1;
+ return of(JSON.parse(content));
}
- }
+ });
- const command = await createCommand(commandMetadata, context, logger);
- const metadataOptions = await convertSchemaToOptions(commandMetadata.text);
- if (command === null) {
- logger.error(tags.stripIndent`Command (${commandName}) failed to instantiate.`);
+ // Normalize the commandMap
+ const commandMap: CommandDescriptionMap = {};
+ for (const name of Object.keys(commands)) {
+ const schemaPath = commands[name];
+ const schemaContent = readFileSync(schemaPath, 'utf-8');
+ const schema = json.parseJsonFile(schemaContent, JsonParseMode.Loose, schemaPath);
+ if (!isJsonObject(schema)) {
+ throw new Error('Invalid command JSON loaded from ' + JSON.stringify(schemaPath));
+ }
- return 1;
+ commandMap[name] = await parseJsonSchemaToCommandDescription(
+ name,
+ schemaPath,
+ registry,
+ schema,
+ );
}
- // Add the options from the metadata to the command object.
- command.addOptions(metadataOptions);
- let options = parseOptions(args, metadataOptions);
- args = await command.initializeRaw(args);
-
- const optionsCopy = deepCopy(options);
- await processRegistry(optionsCopy, commandMetadata);
- await command.initialize(optionsCopy);
- // Reparse options after initializing the command.
- options = parseOptions(args, command.options);
+ let commandName: string | undefined = undefined;
+ for (let i = 0; i < args.length; i++) {
+ const arg = args[i];
- if (commandName === 'help') {
- options.commandInfo = getAllCommandInfo(commandMap);
+ if (arg in commandMap) {
+ commandName = arg;
+ args.splice(i, 1);
+ break;
+ } else if (!arg.startsWith('-')) {
+ commandName = arg;
+ args.splice(i, 1);
+ break;
+ }
}
- if (options.help) {
- command.printHelp(commandName, commandMetadata.rawData.description, options);
+ // if no commands were found, use `help`.
+ if (commandName === undefined) {
+ commandName = 'help';
+ }
- return;
- } else {
- const commandScope = mapCommandScope(commandMetadata.rawData.$scope);
- if (commandScope !== undefined && commandScope !== CommandScope.everywhere) {
- if (commandScope !== executionScope) {
- let errorMessage;
- if (commandScope === CommandScope.inProject) {
- errorMessage = `This command can only be run inside of a CLI project.`;
- } else {
- errorMessage = `This command can not be run inside of a CLI project.`;
- }
- logger.fatal(errorMessage);
+ let description: CommandDescription | null = null;
- return 1;
- }
- if (commandScope === CommandScope.inProject) {
- if (!context.project.configFile) {
- logger.fatal('Invalid project: missing workspace file.');
-
- return 1;
+ if (commandName !== undefined) {
+ if (commandMap[commandName]) {
+ description = commandMap[commandName];
+ } else {
+ Object.keys(commandMap).forEach(name => {
+ const commandDescription = commandMap[name];
+ const aliases = commandDescription.aliases;
+
+ let found = false;
+ if (aliases) {
+ if (aliases.some(alias => alias === commandName)) {
+ found = true;
+ }
}
- if (['.angular-cli.json', 'angular-cli.json'].includes(context.project.configFile)) {
- // --------------------------------------------------------------------------------
- // If changing this message, please update the same message in
- // `packages/@angular/cli/bin/ng-update-message.js`
- const message = tags.stripIndent`
- The Angular CLI configuration format has been changed, and your existing configuration
- can be updated automatically by running the following command:
-
- ng update @angular/cli
- `;
-
- logger.warn(message);
-
- return 1;
+ if (found) {
+ if (description) {
+ throw new Error('Found multiple commands with the same alias.');
+ }
+ commandName = name;
+ description = commandDescription;
}
- }
+ });
}
}
- delete options.h;
- delete options.help;
- await processRegistry(options, commandMetadata);
- const isValid = await command.validate(options);
- if (!isValid) {
- logger.fatal(`Validation error. Invalid command options.`);
+ if (!commandName) {
+ logger.error(tags.stripIndent`
+ We could not find a command from the arguments and the help command seems to be disabled.
+ This is an issue with the CLI itself. If you see this comment, please report it and
+ provide your repository.
+ `);
return 1;
}
- return command.run(options);
-}
-
-async function processRegistry(
- options: {_: (string | boolean | number)[]}, commandMetadata: CommandLocation) {
- const rawArgs = options._;
- const registry = new schema.CoreSchemaRegistry([]);
- registry.addSmartDefaultProvider('argv', (schema: JsonObject) => {
- if ('index' in schema) {
- return rawArgs[Number(schema['index'])];
- } else {
- return rawArgs;
- }
- });
-
- const jsonSchema = parseSchema(commandMetadata.text);
- if (jsonSchema === null) {
- throw new Error('');
- }
- await registry.compile(jsonSchema).pipe(
- concatMap(validator => validator(options)), concatMap(validatorResult => {
- if (validatorResult.success) {
- return of(options);
- } else {
- return throwError(new schema.SchemaValidationException(validatorResult.errors));
+ if (!description) {
+ const commandsDistance = {} as { [name: string]: number };
+ const name = commandName;
+ const allCommands = Object.keys(commandMap).sort((a, b) => {
+ if (!(a in commandsDistance)) {
+ commandsDistance[a] = strings.levenshtein(a, name);
}
- })).toPromise();
-}
-
-export function parseOptions(args: string[], optionsAndArguments: Option[]) {
- const parser = yargsParser;
-
- // filter out arguments
- const options = optionsAndArguments
- .filter(opt => {
- let isOption = true;
- if (opt.$default !== undefined && opt.$default.$source === 'argv') {
- isOption = false;
- }
-
- return isOption;
- });
-
- const aliases: { [key: string]: string[]; } = options
- .reduce((aliases: { [key: string]: string; }, opt) => {
- if (!opt || !opt.aliases || opt.aliases.length === 0) {
- return aliases;
- }
-
- aliases[opt.name] = (opt.aliases || [])
- .filter(a => a.length === 1)[0];
-
- return aliases;
- }, {});
-
- const booleans = options
- .filter(o => o.type && o.type === 'boolean')
- .map(o => o.name);
-
- const defaults = options
- .filter(o => o.default !== undefined || booleans.indexOf(o.name) !== -1)
- .reduce((defaults: {[key: string]: string | number | boolean | undefined }, opt: Option) => {
- defaults[opt.name] = opt.default;
-
- return defaults;
- }, {});
-
- const strings = options
- .filter(o => o.type === 'string')
- .map(o => o.name);
-
- const numbers = options
- .filter(o => o.type === 'number')
- .map(o => o.name);
-
-
- aliases.help = ['h'];
- booleans.push('help');
-
- const yargsOptions = {
- alias: aliases,
- boolean: booleans,
- default: defaults,
- string: strings,
- number: numbers,
- };
-
- const parsedOptions = parser(args, yargsOptions);
-
- // Remove aliases.
- options
- .reduce((allAliases, option) => {
- if (!option || !option.aliases || option.aliases.length === 0) {
- return allAliases;
+ if (!(b in commandsDistance)) {
+ commandsDistance[b] = strings.levenshtein(b, name);
}
- return allAliases.concat([...option.aliases]);
- }, [] as string[])
- .forEach((alias: string) => {
- delete parsedOptions[alias];
+ return commandsDistance[a] - commandsDistance[b];
});
- // Remove undefined booleans
- booleans
- .filter(b => parsedOptions[b] === undefined)
- .map(b => coreStrings.camelize(b))
- .forEach(b => delete parsedOptions[b]);
+ logger.error(tags.stripIndent`
+ The specified command ("${commandName}") is invalid. For a list of available options,
+ run "ng help".
- // remove options with dashes.
- Object.keys(parsedOptions)
- .filter(key => key.indexOf('-') !== -1)
- .forEach(key => delete parsedOptions[key]);
+ Did you mean "${allCommands[0]}"?
+ `);
- // remove the command name
- parsedOptions._ = parsedOptions._.slice(1);
-
- return parsedOptions;
-}
-
-// Find a command.
-function findCommand(map: CommandMap, name: string): CommandLocation | null {
- // let Cmd: CommandConstructor = map[name];
- let commandName = name;
-
- if (!map[commandName]) {
- // find command via aliases
- commandName = Object.keys(map)
- .filter(key => {
- // get aliases for the key
- const metadataText = readFileSync(map[key]).toString('utf-8');
- const metadata = JSON.parse(metadataText);
- const aliases = metadata['$aliases'];
- if (!aliases) {
- return false;
- }
- const foundAlias = aliases.filter((alias: string) => alias === name);
-
- return foundAlias.length > 0;
- })[0];
- }
-
- const metadataPath = map[commandName];
-
- if (!metadataPath) {
- return null;
- }
- const metadataText = readFileSync(metadataPath).toString('utf-8');
-
- const metadata = parseJson(metadataText) as any;
-
- return {
- path: metadataPath,
- text: metadataText,
- rawData: metadata,
- };
-}
-
-// Create an instance of a command.
-async function createCommand(metadata: CommandLocation,
- context: CommandContext,
- logger: logging.Logger): Promise {
- const schema = parseSchema(metadata.text);
- if (schema === null) {
- return null;
- }
- const implPath = schema.$impl;
- if (typeof implPath !== 'string') {
- throw new Error('Implementation path is incorrect');
- }
-
- const implRef = new ExportStringRef(implPath, dirname(metadata.path));
-
- const ctor = implRef.ref as CommandConstructor;
-
- return new ctor(context, logger);
-}
-
-function mapCommandScope(scope: 'in' | 'out' | undefined): CommandScope {
- let commandScope = CommandScope.everywhere;
- switch (scope) {
- case 'in':
- commandScope = CommandScope.inProject;
- break;
- case 'out':
- commandScope = CommandScope.outsideProject;
- break;
+ return 1;
}
- return commandScope;
-}
-
-interface CommandInfo {
- name: string;
- description: string;
- aliases: string[];
- hidden: boolean;
-}
-function getAllCommandInfo(map: CommandMap): CommandInfo[] {
- return Object.keys(map)
- .map(name => {
- return {
- name: name,
- metadata: findCommand(map, name),
- };
- })
- .map(info => {
- if (info.metadata === null) {
- return null;
- }
+ const parsedOptions = parser.parseArguments(args, description.options);
+ Command.setCommandMap(commandMap);
+ const command = new description.impl({ project }, description, logger);
- return {
- name: info.name,
- description: info.metadata.rawData.description,
- aliases: info.metadata.rawData.$aliases || [],
- hidden: info.metadata.rawData.$hidden || false,
- };
- })
- .filter(info => info !== null) as CommandInfo[];
+ return await command.validateAndRun(parsedOptions);
}
diff --git a/packages/angular/cli/models/command.ts b/packages/angular/cli/models/command.ts
index 511abc6a813d..53a1e4307179 100644
--- a/packages/angular/cli/models/command.ts
+++ b/packages/angular/cli/models/command.ts
@@ -7,151 +7,149 @@
*/
// tslint:disable:no-global-tslint-disable no-any
-import { JsonValue, logging, terminal } from '@angular-devkit/core';
-
-export interface CommandConstructor {
- new(context: CommandContext, logger: logging.Logger): Command;
- readonly name: string;
- aliases: string[];
- scope: CommandScope;
-}
-
-export enum CommandScope {
- everywhere,
- inProject,
- outsideProject,
-}
-
-export enum ArgumentStrategy {
- MapToOptions,
- Nothing,
+import { logging, strings, tags, terminal } from '@angular-devkit/core';
+import { getWorkspace } from '../utilities/config';
+import {
+ Arguments,
+ CommandContext,
+ CommandDescription,
+ CommandDescriptionMap,
+ CommandProject,
+ CommandScope,
+ Option,
+} from './interface';
+
+export interface BaseCommandOptions {
+ help?: boolean;
+ help_json?: boolean;
+ '--': string[];
}
-export abstract class Command {
- protected _rawArgs: string[];
+export abstract class Command {
public allowMissingWorkspace = false;
+ public project: CommandProject;
- constructor(context: CommandContext, logger: logging.Logger) {
- this.logger = logger;
- if (context) {
- this.project = context.project;
- }
+ protected static commandMap: CommandDescriptionMap;
+ static setCommandMap(map: CommandDescriptionMap) {
+ this.commandMap = map;
}
- public addOptions(options: Option[]) {
- this.options = (this.options || []).concat(options);
+ constructor(
+ context: CommandContext,
+ public readonly description: CommandDescription,
+ protected readonly logger: logging.Logger,
+ ) {
+ this.project = context.project;
}
- async initializeRaw(args: string[]): Promise {
- this._rawArgs = args;
-
- return args;
- }
- async initialize(_options: any): Promise {
+ async initialize(options: T): Promise {
return;
}
- validate(_options: T): boolean | Promise {
- return true;
- }
+ async printHelp(options: T): Promise {
+ await this.printHelpUsage();
+ await this.printHelpOptions();
- printHelp(commandName: string, description: string, options: any): void {
- if (description) {
- this.logger.info(description);
- }
- this.printHelpUsage(commandName, this.options);
- this.printHelpOptions(this.options);
+ return 0;
}
- private _getArguments(options: Option[]) {
- function _getArgIndex(def: OptionSmartDefault | undefined): number {
- if (def === undefined || def.$source !== 'argv' || typeof def.index !== 'number') {
- // If there's no proper order, this argument is wonky. We will show it at the end only
- // (after all other arguments).
- return Infinity;
- }
-
- return def.index;
- }
+ async printJsonHelp(_options: T): Promise {
+ this.logger.info(JSON.stringify(this.description));
- return options
- .filter(opt => this.isArgument(opt))
- .sort((a, b) => _getArgIndex(a.$default) - _getArgIndex(b.$default));
+ return 0;
}
- protected printHelpUsage(name: string, options: Option[]) {
- const args = this._getArguments(options);
- const opts = options.filter(opt => !this.isArgument(opt));
+ protected async printHelpUsage() {
+ this.logger.info(this.description.description);
+
+ const name = this.description.name;
+ const args = this.description.options.filter(x => x.positional !== undefined);
+ const opts = this.description.options.filter(x => x.positional === undefined);
+
const argDisplay = args && args.length > 0
? ' ' + args.map(a => `<${a.name}>`).join(' ')
: '';
const optionsDisplay = opts && opts.length > 0
? ` [options]`
: ``;
+
this.logger.info(`usage: ng ${name}${argDisplay}${optionsDisplay}`);
+ this.logger.info('');
}
- protected isArgument(option: Option) {
- let isArg = false;
- if (option.$default !== undefined && option.$default.$source === 'argv') {
- isArg = true;
- }
-
- return isArg;
- }
+ protected async printHelpOptions(options: Option[] = this.description.options) {
+ const args = options.filter(opt => opt.positional !== undefined);
+ const opts = options.filter(opt => opt.positional === undefined);
- protected printHelpOptions(options: Option[]) {
- if (!options) {
- return;
- }
- const args = options.filter(opt => this.isArgument(opt));
- const opts = options.filter(opt => !this.isArgument(opt));
if (args.length > 0) {
this.logger.info(`arguments:`);
args.forEach(o => {
this.logger.info(` ${terminal.cyan(o.name)}`);
- this.logger.info(` ${o.description}`);
+ if (o.description) {
+ this.logger.info(` ${o.description}`);
+ }
});
}
- if (this.options.length > 0) {
+ if (options.length > 0) {
+ if (args.length > 0) {
+ this.logger.info('');
+ }
this.logger.info(`options:`);
opts
.filter(o => !o.hidden)
- .sort((a, b) => a.name >= b.name ? 1 : -1)
+ .sort((a, b) => a.name.localeCompare(b.name))
.forEach(o => {
const aliases = o.aliases && o.aliases.length > 0
? '(' + o.aliases.map(a => `-${a}`).join(' ') + ')'
: '';
- this.logger.info(` ${terminal.cyan('--' + o.name)} ${aliases}`);
- this.logger.info(` ${o.description}`);
+ this.logger.info(` ${terminal.cyan('--' + strings.dasherize(o.name))} ${aliases}`);
+ if (o.description) {
+ this.logger.info(` ${o.description}`);
+ }
});
}
}
- abstract run(options: T): number | void | Promise;
- public options: Option[];
- public additionalSchemas: string[] = [];
- protected readonly logger: logging.Logger;
- protected readonly project: any;
-}
-
-export interface CommandContext {
- project: any;
-}
+ async validateScope(): Promise {
+ switch (this.description.scope) {
+ case CommandScope.OutProject:
+ if (this.project.configFile || getWorkspace('local') !== null) {
+ this.logger.fatal(tags.oneLine`
+ The ${this.description.name} command requires to be run outside of a project, but a
+ project definition was found at "${this.project.root}".
+ `);
+ throw 1;
+ }
+ break;
+ case CommandScope.InProject:
+ if (!this.project.configFile || getWorkspace('local') === null) {
+ this.logger.fatal(tags.oneLine`
+ The ${this.description.name} command requires to be run in an Angular project, but a
+ project definition could not be found.
+ `);
+ throw 1;
+ }
+ break;
+ case CommandScope.Everywhere:
+ // Can't miss this.
+ break;
+ }
+ }
-export interface Option {
- name: string;
- description: string;
- type: string;
- default?: string | number | boolean;
- required?: boolean;
- aliases?: string[];
- format?: string;
- hidden?: boolean;
- $default?: OptionSmartDefault;
-}
+ abstract async run(options: T & Arguments): Promise;
-export interface OptionSmartDefault {
- $source: string;
- [key: string]: JsonValue;
+ async validateAndRun(options: T & Arguments): Promise {
+ if (!options.help && !options.help_json) {
+ await this.validateScope();
+ }
+ await this.initialize(options);
+
+ if (options.help) {
+ return this.printHelp(options);
+ } else if (options.help_json) {
+ return this.printJsonHelp(options);
+ } else {
+ return await this.run(options);
+ }
+ }
}
diff --git a/packages/angular/cli/models/parser.ts b/packages/angular/cli/models/parser.ts
new file mode 100644
index 000000000000..1af0c368dd0c
--- /dev/null
+++ b/packages/angular/cli/models/parser.ts
@@ -0,0 +1,296 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ *
+ */
+import { strings } from '@angular-devkit/core';
+import { Arguments, Option, Value } from './interface';
+
+
+function _coerceType(str: string | undefined, type: string, v?: Value): Value | undefined {
+ switch (type) {
+ case 'any':
+ if (Array.isArray(v)) {
+ return v.concat(str || '');
+ }
+
+ return _coerceType(str, 'boolean', v) !== undefined ? _coerceType(str, 'boolean', v)
+ : _coerceType(str, 'number', v) !== undefined ? _coerceType(str, 'number', v)
+ : _coerceType(str, 'string', v);
+
+ case 'string':
+ return str || '';
+
+ case 'boolean':
+ switch (str) {
+ case 'false':
+ return false;
+
+ case undefined:
+ case '':
+ case 'true':
+ return true;
+
+ default:
+ return undefined;
+ }
+
+ case 'number':
+ if (str === undefined) {
+ return 0;
+ } else if (Number.isFinite(+str)) {
+ return +str;
+ } else {
+ return undefined;
+ }
+
+ case 'array':
+ return Array.isArray(v) ? v.concat(str || '') : [str || ''];
+
+ default:
+ return undefined;
+ }
+}
+
+function _coerce(str: string | undefined, o: Option | null, v?: Value): Value | undefined {
+ return _coerceType(str, o ? o.type : 'any', v);
+}
+
+
+function _getOptionFromName(name: string, options: Option[]): Option | undefined {
+ const cName = strings.camelize(name);
+
+ for (const option of options) {
+ if (option.name == name || option.name == cName) {
+ return option;
+ }
+
+ if (option.aliases.some(x => x == name || x == cName)) {
+ return option;
+ }
+ }
+
+ return undefined;
+}
+
+
+function _assignOption(
+ arg: string,
+ args: string[],
+ options: Option[],
+ parsedOptions: Arguments,
+ _positionals: string[],
+ leftovers: string[],
+) {
+ let key = arg.substr(2);
+ let option: Option | null = null;
+ let value = '';
+ const i = arg.indexOf('=');
+
+ // If flag is --no-abc AND there's no equal sign.
+ if (i == -1) {
+ if (key.startsWith('no-')) {
+ // Only use this key if the option matching the rest is a boolean.
+ const maybeOption = _getOptionFromName(key.substr(3), options);
+ if (maybeOption && maybeOption.type == 'boolean') {
+ value = 'false';
+ option = maybeOption;
+ }
+ } else if (key.startsWith('no')) {
+ // Only use this key if the option matching the rest is a boolean.
+ const maybeOption = _getOptionFromName(key.substr(2), options);
+ if (maybeOption && maybeOption.type == 'boolean') {
+ value = 'false';
+ option = maybeOption;
+ }
+ }
+
+ if (option === null) {
+ // Set it to true if it's a boolean and the next argument doesn't match true/false.
+ const maybeOption = _getOptionFromName(key, options);
+ if (maybeOption) {
+ // Not of type boolean, consume the next value.
+ value = args[0];
+ // Only absorb it if it leads to a value.
+ if (_coerce(value, maybeOption) !== undefined) {
+ args.shift();
+ } else {
+ value = '';
+ }
+ option = maybeOption;
+ }
+ }
+ } else {
+ key = arg.substring(0, i);
+ option = _getOptionFromName(key, options) || null;
+ if (option) {
+ value = arg.substring(i + 1);
+ if (option.type === 'boolean' && _coerce(value, option) === undefined) {
+ value = 'true';
+ }
+ }
+ }
+ if (option === null) {
+ if (args[0] && !args[0].startsWith('--')) {
+ leftovers.push(arg, args[0]);
+ args.shift();
+ } else {
+ leftovers.push(arg);
+ }
+ } else {
+ const v = _coerce(value, option, parsedOptions[option.name]);
+ if (v !== undefined) {
+ parsedOptions[option.name] = v;
+ }
+ }
+}
+
+
+/**
+ * Parse the arguments in a consistent way, but without having any option definition. This tries
+ * to assess what the user wants in a free form. For example, using `--name=false` will set the
+ * name properties to a boolean type.
+ * This should only be used when there's no schema available or if a schema is "true" (anything is
+ * valid).
+ *
+ * @param args Argument list to parse.
+ * @returns An object that contains a property per flags from the args.
+ */
+export function parseFreeFormArguments(args: string[]): Arguments {
+ const parsedOptions: Arguments = {};
+ const leftovers = [];
+
+ for (let arg = args.shift(); arg !== undefined; arg = args.shift()) {
+ if (arg == '--') {
+ leftovers.push(...args);
+ break;
+ }
+
+ if (arg.startsWith('--')) {
+ const eqSign = arg.indexOf('=');
+ let name: string;
+ let value: string | undefined;
+ if (eqSign !== -1) {
+ name = arg.substring(2, eqSign);
+ value = arg.substring(eqSign + 1);
+ } else {
+ name = arg.substr(2);
+ value = args.shift();
+ }
+
+ const v = _coerce(value, null, parsedOptions[name]);
+ if (v !== undefined) {
+ parsedOptions[name] = v;
+ }
+ } else if (arg.startsWith('-')) {
+ arg.split('').forEach(x => parsedOptions[x] = true);
+ } else {
+ leftovers.push(arg);
+ }
+ }
+
+ parsedOptions['--'] = leftovers;
+
+ return parsedOptions;
+}
+
+
+/**
+ * Parse the arguments in a consistent way, from a list of standardized options.
+ * The result object will have a key per option name, with the `_` key reserved for positional
+ * arguments, and `--` will contain everything that did not match. Any key that don't have an
+ * option will be pushed back in `--` and removed from the object. If you need to validate that
+ * there's no additionalProperties, you need to check the `--` key.
+ *
+ * @param args The argument array to parse.
+ * @param options List of supported options. {@see Option}.
+ * @returns An object that contains a property per option.
+ */
+export function parseArguments(args: string[], options: Option[] | null): Arguments {
+ if (options === null) {
+ options = [];
+ }
+
+ const leftovers: string[] = [];
+ const positionals: string[] = [];
+ const parsedOptions: Arguments = {};
+
+ for (let arg = args.shift(); arg !== undefined; arg = args.shift()) {
+ if (!arg) {
+ break;
+ }
+
+ if (arg == '--') {
+ // If we find a --, we're done.
+ leftovers.push(...args);
+ break;
+ }
+
+ if (arg.startsWith('--')) {
+ _assignOption(arg, args, options, parsedOptions, positionals, leftovers);
+ } else if (arg.startsWith('-')) {
+ // Argument is of form -abcdef. Starts at 1 because we skip the `-`.
+ for (let i = 1; i < arg.length; i++) {
+ const flag = arg[i];
+ // Treat the last flag as `--a` (as if full flag but just one letter). We do this in
+ // the loop because it saves us a check to see if the arg is just `-`.
+ if (i == arg.length - 1) {
+ _assignOption('--' + flag, args, options, parsedOptions, positionals, leftovers);
+ } else {
+ const maybeOption = _getOptionFromName(flag, options);
+ if (maybeOption) {
+ const v = _coerce(undefined, maybeOption, parsedOptions[maybeOption.name]);
+ if (v !== undefined) {
+ parsedOptions[maybeOption.name] = v;
+ }
+ }
+ }
+ }
+ } else {
+ positionals.push(arg);
+ }
+ }
+
+ // Deal with positionals.
+ if (positionals.length > 0) {
+ let pos = 0;
+ for (let i = 0; i < positionals.length;) {
+ let found = false;
+ let incrementPos = false;
+ let incrementI = true;
+
+ // We do this with a found flag because more than 1 option could have the same positional.
+ for (const option of options) {
+ // If any option has this positional and no value, we need to remove it.
+ if (option.positional === pos) {
+ if (parsedOptions[option.name] === undefined) {
+ parsedOptions[option.name] = positionals[i];
+ found = true;
+ } else {
+ incrementI = false;
+ }
+ incrementPos = true;
+ }
+ }
+
+ if (found) {
+ positionals.splice(i--, 1);
+ }
+ if (incrementPos) {
+ pos++;
+ }
+ if (incrementI) {
+ i++;
+ }
+ }
+ }
+
+ if (positionals.length > 0 || leftovers.length > 0) {
+ parsedOptions['--'] = [...positionals, ...leftovers];
+ }
+
+ return parsedOptions;
+}
diff --git a/packages/angular/cli/models/parser_spec.ts b/packages/angular/cli/models/parser_spec.ts
new file mode 100644
index 000000000000..961f37952aae
--- /dev/null
+++ b/packages/angular/cli/models/parser_spec.ts
@@ -0,0 +1,73 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ *
+ */
+import { Arguments, Option, OptionType } from './interface';
+import { parseArguments } from './parser';
+
+describe('parseArguments', () => {
+ const options: Option[] = [
+ { name: 'bool', aliases: [ 'b' ], type: OptionType.Boolean, description: '' },
+ { name: 'num', aliases: [ 'n' ], type: OptionType.Number, description: '' },
+ { name: 'str', aliases: [ 's' ], type: OptionType.String, description: '' },
+ { name: 'helloWorld', aliases: [], type: OptionType.String, description: '' },
+ { name: 'helloBool', aliases: [], type: OptionType.Boolean, description: '' },
+ { name: 'arr', aliases: [ 'a' ], type: OptionType.Array, description: '' },
+ { name: 'p1', positional: 0, aliases: [], type: OptionType.String, description: '' },
+ { name: 'p2', positional: 1, aliases: [], type: OptionType.String, description: '' },
+ ];
+
+ const tests: { [test: string]: Partial } = {
+ '--bool': { bool: true },
+ '--bool=1': { bool: true },
+ '--bool=true': { bool: true },
+ '--bool=false': { bool: false },
+ '--no-bool': { bool: false },
+ '--no-bool=true': { '--': ['--no-bool=true'] },
+ '--b=true': { bool: true },
+ '--b=false': { bool: false },
+ '--b true': { bool: true },
+ '--b false': { bool: false },
+ '--bool --num': { bool: true, num: 0 },
+ '--bool --num=true': { bool: true },
+ '--bool=true --num': { bool: true, num: 0 },
+ '--bool true --num': { bool: true, num: 0 },
+ '--bool=false --num': { bool: false, num: 0 },
+ '--bool false --num': { bool: false, num: 0 },
+ '--str false --num': { str: 'false', num: 0 },
+ '--str=false --num': { str: 'false', num: 0 },
+ '--str=false --num1': { str: 'false', '--': ['--num1'] },
+ '--str=false val1 --num1': { str: 'false', p1: 'val1', '--': ['--num1'] },
+ '--str=false val1 val2': { str: 'false', p1: 'val1', p2: 'val2' },
+ '--str=false val1 val2 --num1': { str: 'false', p1: 'val1', p2: 'val2', '--': ['--num1'] },
+ '--str=false val1 --num1 val2': { str: 'false', p1: 'val1', '--': ['--num1', 'val2'] },
+ 'val1 --num=1 val2': { num: 1, p1: 'val1', p2: 'val2' },
+ '--p1=val1 --num=1 val2': { num: 1, p1: 'val1', p2: 'val2' },
+ '--p1=val1 --num=1 --p2=val2 val3': { num: 1, p1: 'val1', p2: 'val2', '--': ['val3'] },
+ '--bool val1 --etc --num val2 --v': { bool: true, num: 0, p1: 'val1', p2: 'val2',
+ '--': ['--etc', '--v'] },
+ '--arr=a --arr=b --arr c d': { arr: ['a', 'b', 'c'], p1: 'd' },
+ '--arr=1 --arr --arr c d': { arr: ['1', '--arr'], p1: 'c', p2: 'd' },
+ '--str=1': { str: '1' },
+ '--hello-world=1': { helloWorld: '1' },
+ '--hello-bool': { helloBool: true },
+ '--helloBool': { helloBool: true },
+ '--no-helloBool': { helloBool: false },
+ '--noHelloBool': { helloBool: false },
+ '-b': { bool: true },
+ '-sb': { bool: true, str: '' },
+ '-bs': { bool: true, str: '' },
+ };
+
+ Object.entries(tests).forEach(([str, expected]) => {
+ it(`works for ${str}`, () => {
+ const actual = parseArguments(str.split(/\s+/), options);
+
+ expect(actual).toEqual(expected as Arguments);
+ });
+ });
+});
diff --git a/packages/angular/cli/package.json b/packages/angular/cli/package.json
index ef122769ae77..0c44982c52a7 100644
--- a/packages/angular/cli/package.json
+++ b/packages/angular/cli/package.json
@@ -36,12 +36,10 @@
"@schematics/angular": "0.0.0",
"@schematics/update": "0.0.0",
"inquirer": "^6.1.0",
- "json-schema-traverse": "^0.4.1",
"opn": "^5.3.0",
"rxjs": "~6.2.0",
"semver": "^5.1.0",
- "symbol-observable": "^1.2.0",
- "yargs-parser": "^10.0.0"
+ "symbol-observable": "^1.2.0"
},
"ng-update": {
"migrations": "@schematics/angular/migrations/migration-collection.json"
diff --git a/yarn.lock b/yarn.lock
index 41f1c83ef417..00e64d738515 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7978,12 +7978,6 @@ yallist@^3.0.0, yallist@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9"
-yargs-parser@^10.0.0:
- version "10.1.0"
- resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8"
- dependencies:
- camelcase "^4.1.0"
-
yargs-parser@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a"
From 95176771587e12244e7ad15c2e35a85e38bfd706 Mon Sep 17 00:00:00 2001
From: Hans
Date: Tue, 4 Sep 2018 14:45:15 -0700
Subject: [PATCH 0037/1703] feat(@angular-devkit/core): remove
addUndefinedDefaults as default post transform
And various bug fixes.
---
.../core/src/json/schema/index.ts | 1 +
.../core/src/json/schema/interface.ts | 42 +++-
.../core/src/json/schema/registry.ts | 218 ++++++++++++++----
.../core/src/json/schema/registry_spec.ts | 10 +-
.../core/src/json/schema/visitor.ts | 65 +++---
.../schematics/src/workflow/base.ts | 9 +-
.../testing/schematic-test-runner.ts | 1 +
.../schematics/tools/description.ts | 3 +
.../tools/workflow/node-workflow.ts | 4 +-
9 files changed, 265 insertions(+), 88 deletions(-)
diff --git a/packages/angular_devkit/core/src/json/schema/index.ts b/packages/angular_devkit/core/src/json/schema/index.ts
index 862c127a6b06..e5ff6c4a63c5 100644
--- a/packages/angular_devkit/core/src/json/schema/index.ts
+++ b/packages/angular_devkit/core/src/json/schema/index.ts
@@ -9,6 +9,7 @@ export * from './interface';
export * from './pointer';
export * from './registry';
export * from './visitor';
+export * from './utility';
import * as transforms from './transforms';
diff --git a/packages/angular_devkit/core/src/json/schema/interface.ts b/packages/angular_devkit/core/src/json/schema/interface.ts
index 6f0f092629c3..9861a30889ac 100644
--- a/packages/angular_devkit/core/src/json/schema/interface.ts
+++ b/packages/angular_devkit/core/src/json/schema/interface.ts
@@ -58,12 +58,13 @@ export interface SchemaValidatorResult {
}
export interface SchemaValidatorOptions {
- withPrompts: boolean;
+ applyPreTransforms?: boolean;
+ applyPostTransforms?: boolean;
+ withPrompts?: boolean;
}
export interface SchemaValidator {
- // tslint:disable-next-line:no-any
- (data: any, options?: Partial): Observable;
+ (data: JsonValue, options?: SchemaValidatorOptions): Observable;
}
export interface SchemaFormatter {
@@ -112,7 +113,42 @@ export type PromptProvider = (definitions: Array)
export interface SchemaRegistry {
compile(schema: Object): Observable;
+ flatten(schema: JsonObject | string): Observable;
addFormat(format: SchemaFormat): void;
addSmartDefaultProvider(source: string, provider: SmartDefaultProvider): void;
usePromptProvider(provider: PromptProvider): void;
+
+ /**
+ * Add a transformation step before the validation of any Json.
+ * @param {JsonVisitor} visitor The visitor to transform every value.
+ * @param {JsonVisitor[]} deps A list of other visitors to run before.
+ */
+ addPreTransform(visitor: JsonVisitor, deps?: JsonVisitor[]): void;
+
+ /**
+ * Add a transformation step after the validation of any Json. The JSON will not be validated
+ * after the POST, so if transformations are not compatible with the Schema it will not result
+ * in an error.
+ * @param {JsonVisitor} visitor The visitor to transform every value.
+ * @param {JsonVisitor[]} deps A list of other visitors to run before.
+ */
+ addPostTransform(visitor: JsonVisitor, deps?: JsonVisitor[]): void;
+}
+
+export interface JsonSchemaVisitor {
+ (
+ current: JsonObject | JsonArray,
+ pointer: JsonPointer,
+ parentSchema?: JsonObject | JsonArray,
+ index?: string,
+ ): void;
+}
+
+export interface JsonVisitor {
+ (
+ value: JsonValue,
+ pointer: JsonPointer,
+ schema?: JsonObject,
+ root?: JsonObject | JsonArray,
+ ): Observable | JsonValue;
}
diff --git a/packages/angular_devkit/core/src/json/schema/registry.ts b/packages/angular_devkit/core/src/json/schema/registry.ts
index 89130f10d563..785af9a7a7ac 100644
--- a/packages/angular_devkit/core/src/json/schema/registry.ts
+++ b/packages/angular_devkit/core/src/json/schema/registry.ts
@@ -7,12 +7,15 @@
*/
import * as ajv from 'ajv';
import * as http from 'http';
-import { Observable, from, of as observableOf, throwError } from 'rxjs';
+import { Observable, from, of, throwError } from 'rxjs';
import { concatMap, map, switchMap, tap } from 'rxjs/operators';
+import * as Url from 'url';
import { BaseException } from '../../exception/exception';
-import { PartiallyOrderedSet, isObservable } from '../../utils';
-import { JsonObject, JsonValue } from '../interface';
+import { PartiallyOrderedSet, deepCopy, isObservable } from '../../utils';
+import { JsonArray, JsonObject, JsonValue, isJsonObject } from '../interface';
import {
+ JsonPointer,
+ JsonVisitor,
PromptDefinition,
PromptProvider,
SchemaFormat,
@@ -24,10 +27,7 @@ import {
SchemaValidatorResult,
SmartDefaultProvider,
} from './interface';
-import { addUndefinedDefaults } from './transforms';
-import { JsonVisitor, visitJson } from './visitor';
-
-const serialize = require('fast-json-stable-stringify');
+import { visitJson, visitJsonSchema } from './visitor';
// This interface should be exported from ajv, but they only export the class and not the type.
interface AjvValidationError {
@@ -37,6 +37,14 @@ interface AjvValidationError {
validation: true;
}
+interface AjvRefMap {
+ refs: string[];
+ refVal: any; // tslint:disable-line:no-any
+ schema: JsonObject;
+}
+
+export type UriHandler = (uri: string) => Observable | null | undefined;
+
export class SchemaValidationException extends BaseException {
public readonly errors: SchemaValidatorError[];
@@ -81,10 +89,11 @@ interface SchemaInfo {
export class CoreSchemaRegistry implements SchemaRegistry {
private _ajv: ajv.Ajv;
private _uriCache = new Map();
+ private _uriHandlers = new Set();
private _pre = new PartiallyOrderedSet();
private _post = new PartiallyOrderedSet();
+
private _currentCompilationSchemaInfo?: SchemaInfo;
- private _validatorCache = new Map();
private _smartDefaultKeyword = false;
private _promptProvider?: PromptProvider;
@@ -110,8 +119,6 @@ export class CoreSchemaRegistry implements SchemaRegistry {
this._ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json'));
this._ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'));
-
- this.addPostTransform(addUndefinedDefaults);
}
private _fetch(uri: string): Promise {
@@ -121,12 +128,24 @@ export class CoreSchemaRegistry implements SchemaRegistry {
return Promise.resolve(maybeSchema);
}
+ // Try all handlers, one after the other.
+ for (const maybeHandler of this._uriHandlers) {
+ const handler = maybeHandler(uri);
+ if (handler) {
+ // The AJV API only understands Promises.
+ return handler.pipe(
+ tap(json => this._uriCache.set(uri, json)),
+ ).toPromise();
+ }
+ }
+
+ // If none are found, handle using http client.
return new Promise((resolve, reject) => {
http.get(uri, res => {
if (!res.statusCode || res.statusCode >= 300) {
// Consume the rest of the data to free memory.
res.resume();
- reject(`Request failed. Status Code: ${res.statusCode}`);
+ reject(new Error(`Request failed. Status Code: ${res.statusCode}`));
} else {
res.setEncoding('utf8');
let data = '';
@@ -175,9 +194,22 @@ export class CoreSchemaRegistry implements SchemaRegistry {
return {};
}
- // tslint:disable-next-line:no-any
- const id = (validate.schema as any).$id || (validate.schema as any).id;
- let fullReference = (ref[0] === '#' && id) ? id + ref : ref;
+ let refMap = validate as AjvRefMap;
+ const rootRefMap = validate.root as AjvRefMap;
+
+ // Resolve from the root if it's different.
+ if (validate.root && validate.schema !== rootRefMap.schema) {
+ refMap = rootRefMap;
+ }
+
+ const schema = refMap.schema ? typeof refMap.schema == 'object' && refMap.schema : null;
+ const maybeId = schema ? (schema as JsonObject).id || (schema as JsonObject).$id : null;
+
+ if (typeof maybeId == 'string') {
+ ref = Url.resolve(maybeId, ref);
+ }
+
+ let fullReference = (ref[0] === '#' && maybeId) ? maybeId + ref : ref;
if (fullReference.endsWith('#')) {
fullReference = fullReference.slice(0, -1);
}
@@ -185,21 +217,95 @@ export class CoreSchemaRegistry implements SchemaRegistry {
// tslint:disable-next-line:no-any
const context = validate.refVal[(validate.refs as any)[fullReference]];
- return { context, schema: context && context.schema as JsonObject };
+ if (typeof context == 'function') {
+ // Context will be a function if the schema isn't loaded yet, and an actual schema if it's
+ // synchronously available.
+ return { context, schema: context && context.schema as JsonObject };
+ } else {
+ return { context: validate, schema: context as JsonObject };
+ }
}
- compile(schema: JsonObject): Observable {
- const schemaKey = serialize(schema);
- const existingValidator = this._validatorCache.get(schemaKey);
- if (existingValidator) {
- return observableOf(existingValidator);
+ /**
+ * Flatten the Schema, resolving and replacing all the refs. Makes it into a synchronous schema
+ * that is also easier to traverse. Does not cache the result.
+ *
+ * @param schema The schema or URI to flatten.
+ * @returns An Observable of the flattened schema object.
+ */
+ flatten(schema: JsonObject): Observable {
+ this._ajv.removeSchema(schema);
+
+ // Supports both synchronous and asynchronous compilation, by trying the synchronous
+ // version first, then if refs are missing this will fails.
+ // We also add any refs from external fetched schemas so that those will also be used
+ // in synchronous (if available).
+ let validator: Observable;
+ try {
+ this._currentCompilationSchemaInfo = undefined;
+ validator = of(this._ajv.compile(schema)).pipe(
+ tap(() => this._currentCompilationSchemaInfo = undefined),
+ );
+ } catch (e) {
+ // Propagate the error.
+ if (!(e instanceof (ajv.MissingRefError as {} as Function))) {
+ return throwError(e);
+ }
+
+ this._currentCompilationSchemaInfo = undefined;
+ validator = from(this._ajv.compileAsync(schema)).pipe(
+ tap(() => this._currentCompilationSchemaInfo = undefined),
+ );
}
+ return validator.pipe(
+ switchMap(validate => {
+ const self = this;
+
+ function visitor(
+ current: JsonObject | JsonArray,
+ pointer: JsonPointer,
+ parentSchema?: JsonObject | JsonArray,
+ index?: string,
+ ) {
+ if (current
+ && parentSchema
+ && index
+ && isJsonObject(current)
+ && current.hasOwnProperty('$ref')
+ && typeof current['$ref'] == 'string'
+ ) {
+ const resolved = self._resolver(current['$ref'] as string, validate);
+
+ if (resolved.schema) {
+ (parentSchema as JsonObject)[index] = resolved.schema;
+ }
+ }
+ }
+
+ const schema = deepCopy(validate.schema as JsonObject);
+ visitJsonSchema(schema, visitor);
+
+ return of(schema);
+ }),
+ );
+ }
+
+ /**
+ * Compile and return a validation function for the Schema.
+ *
+ * @param schema The schema to validate. If a string, will fetch the schema before compiling it
+ * (using schema as a URI).
+ * @returns An Observable of the Validation function.
+ */
+ compile(schema: JsonObject): Observable {
const schemaInfo: SchemaInfo = {
smartDefaultRecord: new Map(),
promptDefinitions: [],
};
+ this._ajv.removeSchema(schema);
+
// Supports both synchronous and asynchronous compilation, by trying the synchronous
// version first, then if refs are missing this will fails.
// We also add any refs from external fetched schemas so that those will also be used
@@ -207,7 +313,7 @@ export class CoreSchemaRegistry implements SchemaRegistry {
let validator: Observable;
try {
this._currentCompilationSchemaInfo = schemaInfo;
- validator = observableOf(this._ajv.compile(schema));
+ validator = of(this._ajv.compile(schema));
} catch (e) {
// Propagate the error.
if (!(e instanceof (ajv.MissingRefError as {} as Function))) {
@@ -223,29 +329,36 @@ export class CoreSchemaRegistry implements SchemaRegistry {
return validator
.pipe(
- map(validate => (data, options) => {
+ map(validate => (data: JsonValue, options?: SchemaValidatorOptions) => {
const validationOptions: SchemaValidatorOptions = {
withPrompts: true,
+ applyPostTransforms: true,
+ applyPreTransforms: true,
...options,
};
const validationContext = {
promptFieldsWithValue: new Set(),
};
- return observableOf(data).pipe(
- ...[...this._pre].map(visitor => concatMap((data: JsonValue) => {
- return visitJson(data, visitor, schema, this._resolver, validate);
- })),
- ).pipe(
+ let result = of(data);
+ if (validationOptions.applyPreTransforms) {
+ result = result.pipe(
+ ...[...this._pre].map(visitor => concatMap((data: JsonValue) => {
+ return visitJson(data, visitor, schema, this._resolver, validate);
+ })),
+ );
+ }
+
+ return result.pipe(
switchMap(updateData => this._applySmartDefaults(
updateData,
schemaInfo.smartDefaultRecord,
)),
- switchMap((updatedData: JsonValue) => {
+ switchMap(updatedData => {
const result = validate.call(validationContext, updatedData);
return typeof result == 'boolean'
- ? observableOf([updatedData, result])
+ ? of([updatedData, result])
: from((result as Promise)
.then(r => [updatedData, true])
.catch((err: Error | AjvValidationError) => {
@@ -259,8 +372,8 @@ export class CoreSchemaRegistry implements SchemaRegistry {
}));
}),
switchMap(([data, valid]: [JsonValue, boolean]) => {
- if (!validationOptions.withPrompts) {
- return observableOf([data, valid]);
+ if (validationOptions.withPrompts === false) {
+ return of([data, valid]);
}
const definitions = schemaInfo.promptDefinitions
@@ -271,20 +384,26 @@ export class CoreSchemaRegistry implements SchemaRegistry {
map(data => [data, valid]),
);
} else {
- return observableOf([data, valid]);
+ return of([data, valid]);
}
}),
switchMap(([data, valid]: [JsonValue, boolean]) => {
if (valid) {
- return observableOf(data).pipe(
- ...[...this._post].map(visitor => concatMap((data: JsonValue) => {
- return visitJson(data as JsonValue, visitor, schema, this._resolver, validate);
- })),
- ).pipe(
+ let result = of(data);
+
+ if (validationOptions.applyPostTransforms) {
+ result = result.pipe(
+ ...[...this._post].map(visitor => concatMap((data: JsonValue) => {
+ return visitJson(data, visitor, schema, this._resolver, validate);
+ })),
+ );
+ }
+
+ return result.pipe(
map(data => [data, valid]),
);
} else {
- return observableOf([data, valid]);
+ return of([data, valid]);
}
}),
map(([data, valid]: [JsonValue, boolean]) => {
@@ -300,7 +419,6 @@ export class CoreSchemaRegistry implements SchemaRegistry {
}),
);
}),
- tap(v => this._validatorCache.set(schemaKey, v)),
);
}
@@ -339,14 +457,14 @@ export class CoreSchemaRegistry implements SchemaRegistry {
valid: true,
compile: (schema, _parentSchema, it) => {
const compilationSchemInfo = this._currentCompilationSchemaInfo;
- if (!compilationSchemInfo) {
- throw new Error('Invalid JSON schema compilation state');
+ if (compilationSchemInfo === undefined) {
+ return () => true;
}
// We cheat, heavily.
compilationSchemInfo.smartDefaultRecord.set(
// tslint:disable-next-line:no-any
- JSON.stringify((it as any).dataPathArr.slice(1, it.dataLevel + 1) as string[]),
+ JSON.stringify((it as any).dataPathArr.slice(1, (it as any).dataLevel + 1) as string[]),
schema,
);
@@ -364,6 +482,10 @@ export class CoreSchemaRegistry implements SchemaRegistry {
}
}
+ registerUriHandler(handler: UriHandler) {
+ this._uriHandlers.add(handler);
+ }
+
usePromptProvider(provider: PromptProvider) {
const isSetup = !!this._promptProvider;
@@ -378,8 +500,10 @@ export class CoreSchemaRegistry implements SchemaRegistry {
valid: true,
compile: (schema, parentSchema: JsonObject, it) => {
const compilationSchemInfo = this._currentCompilationSchemaInfo;
- if (!compilationSchemInfo) {
+ if (compilationSchemInfo === undefined) {
throw new Error('Invalid JSON schema compilation state');
+ } else if (compilationSchemInfo === null) {
+ return () => true;
}
// tslint:disable-next-line:no-any
@@ -477,7 +601,7 @@ export class CoreSchemaRegistry implements SchemaRegistry {
private _applyPrompts(data: T, prompts: Array): Observable {
const provider = this._promptProvider;
if (!provider) {
- return observableOf(data);
+ return of(data);
}
prompts.sort((a, b) => b.priority - a.priority);
@@ -564,16 +688,16 @@ export class CoreSchemaRegistry implements SchemaRegistry {
data: T,
smartDefaults: Map,
): Observable {
- return observableOf(data).pipe(
+ return of(data).pipe(
...[...smartDefaults.entries()].map(([pointer, schema]) => {
return concatMap(data => {
const fragments = JSON.parse(pointer);
const source = this._sourceMap.get((schema as JsonObject).$source as string);
- let value = source ? source(schema) : observableOf(undefined);
+ let value = source ? source(schema) : of(undefined);
if (!isObservable(value)) {
- value = observableOf(value);
+ value = of(value);
}
return (value as Observable<{}>).pipe(
diff --git a/packages/angular_devkit/core/src/json/schema/registry_spec.ts b/packages/angular_devkit/core/src/json/schema/registry_spec.ts
index 773a87a479bf..b221ac9cde91 100644
--- a/packages/angular_devkit/core/src/json/schema/registry_spec.ts
+++ b/packages/angular_devkit/core/src/json/schema/registry_spec.ts
@@ -9,11 +9,13 @@
import { of as observableOf } from 'rxjs';
import { map, mergeMap } from 'rxjs/operators';
import { CoreSchemaRegistry } from './registry';
+import { addUndefinedDefaults } from './transforms';
describe('CoreSchemaRegistry', () => {
it('works asynchronously', done => {
const registry = new CoreSchemaRegistry();
+ registry.addPostTransform(addUndefinedDefaults);
const data: any = {}; // tslint:disable-line:no-any
registry
@@ -45,6 +47,7 @@ describe('CoreSchemaRegistry', () => {
it('supports pre transforms', done => {
const registry = new CoreSchemaRegistry();
+ registry.addPostTransform(addUndefinedDefaults);
const data: any = {}; // tslint:disable-line:no-any
registry.addPreTransform((data, ptr) => {
@@ -82,6 +85,7 @@ describe('CoreSchemaRegistry', () => {
it('supports local references', done => {
const registry = new CoreSchemaRegistry();
+ registry.addPostTransform(addUndefinedDefaults);
const data = { numbers: { one: 1 } };
registry
@@ -108,6 +112,7 @@ describe('CoreSchemaRegistry', () => {
it('fails on invalid additionalProperties', done => {
const registry = new CoreSchemaRegistry();
+ registry.addPostTransform(addUndefinedDefaults);
const data = { notNum: 'foo' };
registry
@@ -129,6 +134,7 @@ describe('CoreSchemaRegistry', () => {
it('fails on invalid additionalProperties async', done => {
const registry = new CoreSchemaRegistry();
+ registry.addPostTransform(addUndefinedDefaults);
const data = { notNum: 'foo' };
registry
@@ -156,6 +162,7 @@ describe('CoreSchemaRegistry', () => {
// (i.e. not relyign on the observable).
it('works synchronously', done => {
const registry = new CoreSchemaRegistry();
+ registry.addPostTransform(addUndefinedDefaults);
const data: any = {}; // tslint:disable-line:no-any
let isDone = false;
@@ -298,7 +305,7 @@ describe('CoreSchemaRegistry', () => {
return schema['blue'];
});
- registry.addSmartDefaultProvider('test3', (schema) => {
+ registry.addSmartDefaultProvider('test3', () => {
return [ 1, 2, 3 ];
});
@@ -369,6 +376,7 @@ describe('CoreSchemaRegistry', () => {
it('adds undefined properties', done => {
const registry = new CoreSchemaRegistry();
+ registry.addPostTransform(addUndefinedDefaults);
const data: any = {}; // tslint:disable-line:no-any
registry
diff --git a/packages/angular_devkit/core/src/json/schema/visitor.ts b/packages/angular_devkit/core/src/json/schema/visitor.ts
index 8091785cf06d..0708c3564c51 100644
--- a/packages/angular_devkit/core/src/json/schema/visitor.ts
+++ b/packages/angular_devkit/core/src/json/schema/visitor.ts
@@ -9,27 +9,9 @@ import { Observable, concat, from, of as observableOf } from 'rxjs';
import { concatMap, ignoreElements, mergeMap, tap } from 'rxjs/operators';
import { isObservable } from '../../utils';
import { JsonArray, JsonObject, JsonValue } from '../interface';
-import { JsonPointer } from './interface';
+import { JsonPointer, JsonSchemaVisitor, JsonVisitor } from './interface';
import { buildJsonPointer, joinJsonPointer } from './pointer';
-export interface JsonSchemaVisitor {
- (
- current: JsonObject | JsonArray,
- pointer: JsonPointer,
- parentSchema?: JsonObject | JsonArray,
- index?: string,
- ): void;
-}
-
-export interface JsonVisitor {
- (
- value: JsonValue,
- pointer: JsonPointer,
- schema?: JsonObject,
- root?: JsonObject | JsonArray,
- ): Observable | JsonValue;
-}
-
export interface ReferenceResolver {
(ref: string, context?: ContextT): { context?: ContextT, schema?: JsonObject };
@@ -167,6 +149,13 @@ export function visitJsonSchema(schema: JsonObject, visitor: JsonSchemaVisitor)
not: true,
};
+ const arrayKeywords = {
+ items: true,
+ allOf: true,
+ anyOf: true,
+ oneOf: true,
+ };
+
const propsKeywords = {
definitions: true,
properties: true,
@@ -188,23 +177,11 @@ export function visitJsonSchema(schema: JsonObject, visitor: JsonSchemaVisitor)
for (const key of Object.keys(schema)) {
const sch = schema[key];
- if (Array.isArray(sch)) {
- if (key == 'items') {
- for (let i = 0; i < sch.length; i++) {
- _traverse(
- sch[i] as JsonArray,
- joinJsonPointer(jsonPtr, key, '' + i),
- rootSchema,
- schema,
- '' + i,
- );
- }
- }
- } else if (key in propsKeywords) {
+ if (key in propsKeywords) {
if (sch && typeof sch == 'object') {
for (const prop of Object.keys(sch)) {
_traverse(
- sch[prop] as JsonObject,
+ (sch as JsonObject)[prop] as JsonObject,
joinJsonPointer(jsonPtr, key, prop),
rootSchema,
schema,
@@ -214,6 +191,28 @@ export function visitJsonSchema(schema: JsonObject, visitor: JsonSchemaVisitor)
}
} else if (key in keywords) {
_traverse(sch as JsonObject, joinJsonPointer(jsonPtr, key), rootSchema, schema, key);
+ } else if (key in arrayKeywords) {
+ if (Array.isArray(sch)) {
+ for (let i = 0; i < sch.length; i++) {
+ _traverse(
+ sch[i] as JsonArray,
+ joinJsonPointer(jsonPtr, key, '' + i),
+ rootSchema,
+ sch,
+ '' + i,
+ );
+ }
+ }
+ } else if (Array.isArray(sch)) {
+ for (let i = 0; i < sch.length; i++) {
+ _traverse(
+ sch[i] as JsonArray,
+ joinJsonPointer(jsonPtr, key, '' + i),
+ rootSchema,
+ sch,
+ '' + i,
+ );
+ }
}
}
}
diff --git a/packages/angular_devkit/schematics/src/workflow/base.ts b/packages/angular_devkit/schematics/src/workflow/base.ts
index ae5ba62f8bfb..134cde1e61f6 100644
--- a/packages/angular_devkit/schematics/src/workflow/base.ts
+++ b/packages/angular_devkit/schematics/src/workflow/base.ts
@@ -62,7 +62,14 @@ export abstract class BaseWorkflow implements Workflow {
constructor(options: BaseWorkflowOptions) {
this._host = options.host;
this._engineHost = options.engineHost;
- this._registry = options.registry || new schema.CoreSchemaRegistry(standardFormats);
+
+ if (options.registry) {
+ this._registry = options.registry;
+ } else {
+ this._registry = new schema.CoreSchemaRegistry(standardFormats);
+ this._registry.addPostTransform(schema.transforms.addUndefinedDefaults);
+ }
+
this._engine = new SchematicEngine(this._engineHost, this);
this._context = [];
diff --git a/packages/angular_devkit/schematics/testing/schematic-test-runner.ts b/packages/angular_devkit/schematics/testing/schematic-test-runner.ts
index a105d260bede..1c0c06ac1ce8 100644
--- a/packages/angular_devkit/schematics/testing/schematic-test-runner.ts
+++ b/packages/angular_devkit/schematics/testing/schematic-test-runner.ts
@@ -57,6 +57,7 @@ export class SchematicTestRunner {
this._logger = new logging.Logger('test');
const registry = new schema.CoreSchemaRegistry(formats.standardFormats);
+ registry.addPostTransform(schema.transforms.addUndefinedDefaults);
this._engineHost.registerOptionsTransform(validateOptionsWithSchema(registry));
this._engineHost.registerTaskExecutor(BuiltinTaskExecutor.NodePackage);
diff --git a/packages/angular_devkit/schematics/tools/description.ts b/packages/angular_devkit/schematics/tools/description.ts
index faf18574635c..a6501af85375 100644
--- a/packages/angular_devkit/schematics/tools/description.ts
+++ b/packages/angular_devkit/schematics/tools/description.ts
@@ -10,6 +10,7 @@ import {
Collection,
CollectionDescription,
RuleFactory,
+ Schematic,
SchematicDescription,
TypedSchematicContext,
} from '../src';
@@ -45,6 +46,8 @@ export interface FileSystemSchematicDescription extends FileSystemSchematicJsonD
*/
export declare type FileSystemCollection
= Collection;
+export declare type FileSystemSchematic
+ = Schematic;
export declare type FileSystemCollectionDesc
= CollectionDescription;
export declare type FileSystemSchematicDesc
diff --git a/packages/angular_devkit/schematics/tools/workflow/node-workflow.ts b/packages/angular_devkit/schematics/tools/workflow/node-workflow.ts
index 740f23e27a97..20ed4d9a9382 100644
--- a/packages/angular_devkit/schematics/tools/workflow/node-workflow.ts
+++ b/packages/angular_devkit/schematics/tools/workflow/node-workflow.ts
@@ -5,9 +5,8 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
-import { Path, schema, virtualFs } from '@angular-devkit/core';
+import { Path, virtualFs } from '@angular-devkit/core';
import {
- formats,
workflow,
} from '@angular-devkit/schematics'; // tslint:disable-line:no-implicit-dependencies
import { BuiltinTaskExecutor } from '../../tasks/node';
@@ -30,7 +29,6 @@ export class NodeWorkflow extends workflow.BaseWorkflow {
const engineHost = new NodeModulesEngineHost();
super({
host: host,
- registry: new schema.CoreSchemaRegistry(formats.standardFormats),
engineHost: engineHost,
force: options.force,
From fd7bcd25fe1af58bf25c22968852aa2cbdb4dde1 Mon Sep 17 00:00:00 2001
From: Hans
Date: Tue, 4 Sep 2018 14:45:59 -0700
Subject: [PATCH 0038/1703] fix(@angular-devkit/core): use architect key only
if it exists
And if target didnt exist.
---
.../angular_devkit/core/src/workspace/workspace.ts | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/packages/angular_devkit/core/src/workspace/workspace.ts b/packages/angular_devkit/core/src/workspace/workspace.ts
index a4252a9ee5b7..d03bdb42c898 100644
--- a/packages/angular_devkit/core/src/workspace/workspace.ts
+++ b/packages/angular_devkit/core/src/workspace/workspace.ts
@@ -63,6 +63,7 @@ export class Workspace {
constructor(private _root: Path, private _host: virtualFs.Host<{}>) {
this._registry = new schema.CoreSchemaRegistry();
+ this._registry.addPostTransform(schema.transforms.addUndefinedDefaults);
}
loadWorkspaceFromJson(json: {}) {
@@ -232,7 +233,9 @@ export class Workspace {
let workspaceTool = this._workspace[toolName];
// Try falling back to 'architect' if 'targets' is not there or is empty.
- if ((!workspaceTool || Object.keys(workspaceTool).length === 0) && toolName === 'targets') {
+ if ((!workspaceTool || Object.keys(workspaceTool).length === 0)
+ && toolName === 'targets'
+ && this._workspace['architect']) {
workspaceTool = this._workspace['architect'];
}
@@ -257,11 +260,12 @@ export class Workspace {
let projectTool = workspaceProject[toolName];
// Try falling back to 'architect' if 'targets' is not there or is empty.
- if ((!projectTool || Object.keys(projectTool).length === 0) && toolName === 'targets') {
+ if ((!projectTool || Object.keys(projectTool).length === 0)
+ && workspaceProject['architect']
+ && toolName === 'targets') {
projectTool = workspaceProject['architect'];
}
-
if (!projectTool) {
throw new ProjectToolNotFoundException(toolName);
}
From de7ec8487299bc2fe4a9820fc52c5b1d9af2a711 Mon Sep 17 00:00:00 2001
From: Hans
Date: Tue, 4 Sep 2018 14:54:37 -0700
Subject: [PATCH 0039/1703] feat(@angular/cli): add --helpJson (or --help-json)
too all commands
And other refactors.
The interface for the JSON is available in command.ts (the CommandDescription).
---
packages/angular/cli/commands/add-impl.ts | 57 +--
packages/angular/cli/commands/add.json | 31 +-
packages/angular/cli/commands/build-impl.ts | 6 +-
packages/angular/cli/commands/build.json | 28 +-
packages/angular/cli/commands/config-impl.ts | 10 +-
packages/angular/cli/commands/config.json | 4 +-
.../angular/cli/commands/definitions.json | 64 +++
packages/angular/cli/commands/doc-impl.ts | 22 +-
packages/angular/cli/commands/doc.json | 4 +-
packages/angular/cli/commands/e2e.json | 21 +-
.../angular/cli/commands/easter-egg-impl.ts | 2 +-
packages/angular/cli/commands/easter-egg.json | 4 +-
packages/angular/cli/commands/eject-impl.ts | 15 +-
packages/angular/cli/commands/eject.json | 9 +-
.../angular/cli/commands/generate-impl.ts | 104 ++--
packages/angular/cli/commands/generate.json | 49 +-
packages/angular/cli/commands/help-impl.ts | 33 +-
packages/angular/cli/commands/help.json | 5 +-
packages/angular/cli/commands/lint.json | 41 +-
packages/angular/cli/commands/new-impl.ts | 47 +-
packages/angular/cli/commands/new.json | 53 +--
packages/angular/cli/commands/run.json | 39 +-
packages/angular/cli/commands/serve.json | 27 +-
packages/angular/cli/commands/test.json | 27 +-
packages/angular/cli/commands/update-impl.ts | 86 ++--
packages/angular/cli/commands/update.json | 43 +-
packages/angular/cli/commands/version-impl.ts | 2 +-
packages/angular/cli/commands/version.json | 5 +-
packages/angular/cli/commands/xi18n.json | 23 +-
packages/angular/cli/lib/cli/index.ts | 5 +-
.../angular/cli/models/architect-command.ts | 217 +++------
packages/angular/cli/models/interface.ts | 110 +++++
packages/angular/cli/models/json-schema.ts | 109 -----
.../angular/cli/models/schematic-command.ts | 450 ++++++++----------
packages/angular/cli/utilities/json-schema.ts | 201 ++++++++
packages/angular/cli/utilities/project.ts | 8 +-
.../e2e/tests/basic/in-project-logic.ts | 15 +-
.../e2e/tests/generate/help-output.ts | 6 +-
tests/legacy-cli/e2e/utils/assets.ts | 2 +-
tests/legacy-cli/e2e/utils/process.ts | 11 +-
40 files changed, 998 insertions(+), 997 deletions(-)
create mode 100644 packages/angular/cli/commands/definitions.json
create mode 100644 packages/angular/cli/models/interface.ts
delete mode 100644 packages/angular/cli/models/json-schema.ts
create mode 100644 packages/angular/cli/utilities/json-schema.ts
diff --git a/packages/angular/cli/commands/add-impl.ts b/packages/angular/cli/commands/add-impl.ts
index ba33b8bcc95f..e2492826ea57 100644
--- a/packages/angular/cli/commands/add-impl.ts
+++ b/packages/angular/cli/commands/add-impl.ts
@@ -9,44 +9,24 @@
// tslint:disable:no-global-tslint-disable no-any
import { tags, terminal } from '@angular-devkit/core';
import { NodePackageDoesNotSupportSchematics } from '@angular-devkit/schematics/tools';
-import { parseOptions } from '../models/command-runner';
-import { SchematicCommand } from '../models/schematic-command';
+import { Arguments } from '../models/interface';
+import { BaseSchematicOptions, SchematicCommand } from '../models/schematic-command';
import { NpmInstall } from '../tasks/npm-install';
import { getPackageManager } from '../utilities/config';
+export interface AddCommandOptions extends BaseSchematicOptions {
+ collection: string;
+ help?: boolean;
+ help_json?: boolean;
+}
-export class AddCommand extends SchematicCommand {
+export class AddCommand<
+ T extends AddCommandOptions = AddCommandOptions,
+> extends SchematicCommand {
readonly allowPrivateSchematics = true;
- private async _parseSchematicOptions(collectionName: string): Promise {
- const schematicOptions = await this.getOptions({
- schematicName: 'ng-add',
- collectionName,
- });
- this.addOptions(schematicOptions);
-
- return parseOptions(this._rawArgs, this.options);
- }
-
- validate(options: any) {
- const collectionName = options._[0];
-
- if (!collectionName) {
- this.logger.fatal(
- `The "ng add" command requires a name argument to be specified eg. `
- + `${terminal.yellow('ng add [name] ')}. For more details, use "ng help".`,
- );
-
- return false;
- }
-
- return true;
- }
-
- async run(options: any) {
- const firstArg = options._[0];
-
- if (!firstArg) {
+ async run(options: AddCommandOptions & Arguments) {
+ if (!options.collection) {
this.logger.fatal(
`The "ng add" command requires a name argument to be specified eg. `
+ `${terminal.yellow('ng add [name] ')}. For more details, use "ng help".`,
@@ -59,16 +39,16 @@ export class AddCommand extends SchematicCommand {
const npmInstall: NpmInstall = require('../tasks/npm-install').default;
- const packageName = firstArg.startsWith('@')
- ? firstArg.split('/', 2).join('/')
- : firstArg.split('/', 1)[0];
+ const packageName = options.collection.startsWith('@')
+ ? options.collection.split('/', 2).join('/')
+ : options.collection.split('/', 1)[0];
// Remove the tag/version from the package name.
const collectionName = (
packageName.startsWith('@')
? packageName.split('@', 2).join('@')
: packageName.split('@', 1).join('@')
- ) + firstArg.slice(packageName.length);
+ ) + options.collection.slice(packageName.length);
// We don't actually add the package to package.json, that would be the work of the package
// itself.
@@ -79,11 +59,8 @@ export class AddCommand extends SchematicCommand {
this.project.root,
);
- // Reparse the options with the new schematic accessible.
- options = await this._parseSchematicOptions(collectionName);
-
const runOptions = {
- schematicOptions: options,
+ schematicOptions: options['--'] || [],
workingDir: this.project.root,
collectionName,
schematicName: 'ng-add',
diff --git a/packages/angular/cli/commands/add.json b/packages/angular/cli/commands/add.json
index 04a9f3a888ed..f61b1ac2b6e6 100644
--- a/packages/angular/cli/commands/add.json
+++ b/packages/angular/cli/commands/add.json
@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/schema",
- "id": "AddCommandOptions",
+ "$id": "ng-cli://commands/add.json",
"description": "Add support for a library to your project.",
"$longDescription": "",
@@ -8,16 +8,23 @@
"$impl": "./add-impl#AddCommand",
"type": "object",
- "properties": {
- "collection": {
- "type": "string",
- "description": "The package to be added.",
- "$default": {
- "$source": "argv",
- "index": 0
- }
+ "allOf": [
+ {
+ "properties": {
+ "collection": {
+ "type": "string",
+ "description": "The package to be added.",
+ "$default": {
+ "$source": "argv",
+ "index": 0
+ }
+ }
+ },
+ "required": [
+ ]
+ },
+ {
+ "$ref": "./definitions.json#/definitions/base"
}
- },
- "required": [
]
-}
\ No newline at end of file
+}
diff --git a/packages/angular/cli/commands/build-impl.ts b/packages/angular/cli/commands/build-impl.ts
index 60b82df73e29..46484827de12 100644
--- a/packages/angular/cli/commands/build-impl.ts
+++ b/packages/angular/cli/commands/build-impl.ts
@@ -12,15 +12,11 @@ import { Version } from '../upgrade/version';
export class BuildCommand extends ArchitectCommand {
public readonly target = 'build';
- public validate(options: ArchitectCommandOptions) {
+ public async run(options: ArchitectCommandOptions) {
// Check Angular and TypeScript versions.
Version.assertCompatibleAngularVersion(this.project.root);
Version.assertTypescriptVersion(this.project.root);
- return super.validate(options);
- }
-
- public async run(options: ArchitectCommandOptions) {
return this.runArchitectTarget(options);
}
}
diff --git a/packages/angular/cli/commands/build.json b/packages/angular/cli/commands/build.json
index 247914d3ba3f..db73c39b0605 100644
--- a/packages/angular/cli/commands/build.json
+++ b/packages/angular/cli/commands/build.json
@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/schema",
- "id": "BuildCommandOptions",
+ "$id": "ng-cli://commands/build.json",
"description": "Builds your app and places it into the output path (dist/ by default).",
"$longDescription": "",
@@ -9,26 +9,8 @@
"$type": "architect",
"$impl": "./build-impl#BuildCommand",
- "type": "object",
- "properties": {
- "project": {
- "type": "string",
- "description": "The name of the project to build.",
- "$default": {
- "$source": "argv",
- "index": 0
- }
- },
- "configuration": {
- "description": "Specify the configuration to use.",
- "type": "string",
- "aliases": ["c"]
- },
- "prod": {
- "description": "Flag to set configuration to 'production'.",
- "type": "boolean"
- }
- },
- "required": [
+ "allOf": [
+ { "$ref": "./definitions.json#/definitions/architect" },
+ { "$ref": "./definitions.json#/definitions/base" }
]
-}
\ No newline at end of file
+}
diff --git a/packages/angular/cli/commands/config-impl.ts b/packages/angular/cli/commands/config-impl.ts
index b1850b43999b..7499f542cc37 100644
--- a/packages/angular/cli/commands/config-impl.ts
+++ b/packages/angular/cli/commands/config-impl.ts
@@ -17,7 +17,7 @@ import {
tags,
} from '@angular-devkit/core';
import { writeFileSync } from 'fs';
-import { Command } from '../models/command';
+import { BaseCommandOptions, Command } from '../models/command';
import {
getWorkspace,
getWorkspaceRaw,
@@ -26,7 +26,7 @@ import {
} from '../utilities/config';
-export interface ConfigOptions {
+export interface ConfigOptions extends BaseCommandOptions {
jsonPath: string;
value?: string;
global?: boolean;
@@ -178,8 +178,8 @@ function normalizeValue(value: string, path: string): JsonValue {
return value;
}
-export class ConfigCommand extends Command {
- public run(options: ConfigOptions) {
+export class ConfigCommand extends Command {
+ public async run(options: T) {
const level = options.global ? 'global' : 'local';
let config =
@@ -210,7 +210,7 @@ export class ConfigCommand extends Command {
}
}
- private get(config: experimental.workspace.WorkspaceSchema, options: ConfigOptions) {
+ private get(config: experimental.workspace.WorkspaceSchema, options: T) {
let value;
if (options.jsonPath) {
value = getValueFromPath(config as {} as JsonObject, options.jsonPath);
diff --git a/packages/angular/cli/commands/config.json b/packages/angular/cli/commands/config.json
index cdcce065041c..35521ba50406 100644
--- a/packages/angular/cli/commands/config.json
+++ b/packages/angular/cli/commands/config.json
@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/schema",
- "id": "ConfigCommandOptions",
+ "$id": "ng-cli://commands/config.json",
"description": "Get/set configuration values.",
"$longDescription": "",
@@ -36,4 +36,4 @@
},
"required": [
]
-}
\ No newline at end of file
+}
diff --git a/packages/angular/cli/commands/definitions.json b/packages/angular/cli/commands/definitions.json
new file mode 100644
index 000000000000..5dd2700d5321
--- /dev/null
+++ b/packages/angular/cli/commands/definitions.json
@@ -0,0 +1,64 @@
+{
+ "$schema": "http://json-schema.org/schema",
+ "$id": "ng-cli://commands/definitions.json",
+
+ "definitions": {
+ "architect": {
+ "properties": {
+ "project": {
+ "type": "string",
+ "description": "The name of the project to build.",
+ "$default": {
+ "$source": "argv",
+ "index": 0
+ }
+ },
+ "configuration": {
+ "description": "Specify the configuration to use.",
+ "type": "string",
+ "aliases": [
+ "c"
+ ]
+ },
+ "prod": {
+ "description": "Flag to set configuration to 'production'.",
+ "type": "boolean"
+ }
+ }
+ },
+ "base": {
+ "type": "object",
+ "properties": {
+ "help": {
+ "type": "boolean",
+ "description": "Shows a help message."
+ },
+ "helpJson": {
+ "type": "boolean",
+ "description": "Shows the metadata associated with each flags, in JSON format."
+ }
+ }
+ },
+ "schematic": {
+ "properties": {
+ "dryRun": {
+ "type": "boolean",
+ "default": false,
+ "aliases": [ "d" ],
+ "description": "Run through without making any changes."
+ },
+ "force": {
+ "type": "boolean",
+ "default": false,
+ "aliases": [ "f" ],
+ "description": "Forces overwriting of files."
+ },
+ "interactive": {
+ "type": "boolean",
+ "default": "true",
+ "description": "Disables interactive inputs (i.e., prompts)."
+ }
+ }
+ }
+ }
+}
diff --git a/packages/angular/cli/commands/doc-impl.ts b/packages/angular/cli/commands/doc-impl.ts
index b9f6c9451fe4..543c9ecca89a 100644
--- a/packages/angular/cli/commands/doc-impl.ts
+++ b/packages/angular/cli/commands/doc-impl.ts
@@ -6,31 +6,23 @@
* found in the LICENSE file at https://angular.io/license
*/
-import { Command } from '../models/command';
+import { BaseCommandOptions, Command } from '../models/command';
const opn = require('opn');
-export interface Options {
+export interface DocCommandOptions extends BaseCommandOptions {
keyword: string;
search?: boolean;
}
-export class DocCommand extends Command {
- public validate(options: Options) {
- if (!options.keyword) {
- this.logger.error(`keyword argument is required.`);
-
- return false;
- }
-
- return true;
- }
-
- public async run(options: Options) {
+export class DocCommand extends Command {
+ public async run(options: T) {
let searchUrl = `https://angular.io/api?query=${options.keyword}`;
if (options.search) {
searchUrl = `https://www.google.com/search?q=site%3Aangular.io+${options.keyword}`;
}
- return opn(searchUrl);
+ return opn(searchUrl, {
+ wait: false,
+ });
}
}
diff --git a/packages/angular/cli/commands/doc.json b/packages/angular/cli/commands/doc.json
index d0fd2ce0c4fa..e3649a987960 100644
--- a/packages/angular/cli/commands/doc.json
+++ b/packages/angular/cli/commands/doc.json
@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/schema",
- "id": "DocCommandOptions",
+ "$id": "ng-cli://commands/doc.json",
"description": "Opens the official Angular API documentation for a given keyword.",
"$longDescription": "",
@@ -27,4 +27,4 @@
},
"required": [
]
-}
\ No newline at end of file
+}
diff --git a/packages/angular/cli/commands/e2e.json b/packages/angular/cli/commands/e2e.json
index 8f659194cf7d..6e7b1079a46a 100644
--- a/packages/angular/cli/commands/e2e.json
+++ b/packages/angular/cli/commands/e2e.json
@@ -1,7 +1,7 @@
{
"$schema": "http://json-schema.org/schema",
- "id": "E2eCommandOptions",
- "description": "",
+ "$id": "ng-cli://commands/e2e.json",
+ "description": "Runs the end-to-end tests.",
"$longDescription": "",
"$aliases": [ "e" ],
@@ -10,17 +10,8 @@
"$impl": "./e2e-impl#E2eCommand",
"type": "object",
- "properties": {
- "configuration": {
- "description": "Specify the configuration to use.",
- "type": "string",
- "aliases": ["c"]
- },
- "prod": {
- "description": "Flag to set configuration to 'production'.",
- "type": "boolean"
- }
- },
- "required": [
+ "allOf": [
+ { "$ref": "./definitions.json#/definitions/architect" },
+ { "$ref": "./definitions.json#/definitions/base" }
]
-}
\ No newline at end of file
+}
diff --git a/packages/angular/cli/commands/easter-egg-impl.ts b/packages/angular/cli/commands/easter-egg-impl.ts
index c91fd44fde6b..197bf9e675f4 100644
--- a/packages/angular/cli/commands/easter-egg-impl.ts
+++ b/packages/angular/cli/commands/easter-egg-impl.ts
@@ -14,7 +14,7 @@ function pickOne(of: string[]): string {
}
export class AwesomeCommand extends Command {
- run() {
+ async run() {
const phrase = pickOne([
`You're on it, there's nothing for me to do!`,
`Let's take a look... nope, it's all good!`,
diff --git a/packages/angular/cli/commands/easter-egg.json b/packages/angular/cli/commands/easter-egg.json
index 18833da40982..04e7afa1e45b 100644
--- a/packages/angular/cli/commands/easter-egg.json
+++ b/packages/angular/cli/commands/easter-egg.json
@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/schema",
- "id": "EasterEggCommandOptions",
+ "$id": "ng-cli://commands/easter-egg.json",
"description": "",
"$longDescription": "",
"$hidden": true,
@@ -8,4 +8,4 @@
"$impl": "./easter-egg-impl#AwesomeCommand",
"type": "object"
-}
\ No newline at end of file
+}
diff --git a/packages/angular/cli/commands/eject-impl.ts b/packages/angular/cli/commands/eject-impl.ts
index cf755606eca2..cdfe324e2231 100644
--- a/packages/angular/cli/commands/eject-impl.ts
+++ b/packages/angular/cli/commands/eject-impl.ts
@@ -7,19 +7,12 @@
*/
import { tags } from '@angular-devkit/core';
-import { Command, Option } from '../models/command';
+import { Command } from '../models/command';
export class EjectCommand extends Command {
- public readonly name = 'eject';
- public readonly description = 'Temporarily disabled. Ejects your app and output the proper '
- + 'webpack configuration and scripts.';
- public readonly arguments: string[] = [];
- public readonly options: Option[] = [];
- public static aliases = [];
-
- run() {
- this.logger.info(tags.stripIndents`
+ async run() {
+ this.logger.error(tags.stripIndents`
The 'eject' command has been temporarily disabled, as it is not yet compatible with the new
angular.json format. The new configuration format provides further flexibility to modify the
configuration of your workspace without ejecting. Ejection will be re-enabled in a future
@@ -27,5 +20,7 @@ export class EjectCommand extends Command {
If you need to eject today, use CLI 1.7 to eject your project.
`);
+
+ return 1;
}
}
diff --git a/packages/angular/cli/commands/eject.json b/packages/angular/cli/commands/eject.json
index aa90f156c46b..d5c527d02166 100644
--- a/packages/angular/cli/commands/eject.json
+++ b/packages/angular/cli/commands/eject.json
@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/schema",
- "id": "CommandOptions",
+ "$id": "ng-cli://commands/eject.json",
"description": "Temporarily disabled. Ejects your app and output the proper webpack configuration and scripts.",
"$longDescription": "",
@@ -8,5 +8,8 @@
"$scope": "in",
"$impl": "./eject-impl#EjectCommand",
- "type": "object"
-}
\ No newline at end of file
+ "type": "object",
+ "allOf": [
+ { "$ref": "./definitions.json#/definitions/base" }
+ ]
+}
diff --git a/packages/angular/cli/commands/generate-impl.ts b/packages/angular/cli/commands/generate-impl.ts
index 7e2580751137..7a58da674214 100644
--- a/packages/angular/cli/commands/generate-impl.ts
+++ b/packages/angular/cli/commands/generate-impl.ts
@@ -7,63 +7,64 @@
*/
// tslint:disable:no-global-tslint-disable no-any
-import { tags, terminal } from '@angular-devkit/core';
-import { SchematicCommand } from '../models/schematic-command';
+import { terminal } from '@angular-devkit/core';
+import { Option } from '../models/interface';
+import { BaseSchematicOptions, SchematicCommand } from '../models/schematic-command';
+import { parseJsonSchemaToOptions } from '../utilities/json-schema';
+export interface GenerateCommandOptions extends BaseSchematicOptions {
+ schematic?: string;
+}
-export class GenerateCommand extends SchematicCommand {
- private initialized = false;
- public async initialize(options: any) {
- if (this.initialized) {
- return;
- }
+export class GenerateCommand<
+ T extends GenerateCommandOptions = GenerateCommandOptions,
+> extends SchematicCommand {
+ async initialize(options: T) {
await super.initialize(options);
- this.initialized = true;
+ // Fill up the schematics property of the command description.
const [collectionName, schematicName] = this.parseSchematicInfo(options);
- if (!!schematicName) {
- const schematicOptions = await this.getOptions({
- schematicName,
- collectionName,
- });
- this.addOptions(schematicOptions);
- }
- }
- validate(options: any): boolean | Promise {
- if (!options.schematic) {
- this.logger.error(tags.oneLine`
- The "ng generate" command requires a
- schematic name to be specified.
- For more details, use "ng help".`);
+ const collection = this.getCollection(collectionName);
+ this.description.schematics = {};
- return false;
- }
+ const schematicNames = schematicName ? [schematicName] : collection.listSchematicNames();
+
+ for (const name of schematicNames) {
+ const schematic = this.getSchematic(collection, name, true);
+ let options: Option[] = [];
+ if (schematic.description.schemaJson) {
+ options = await parseJsonSchemaToOptions(
+ this._workflow.registry,
+ schematic.description.schemaJson,
+ );
+ }
- return true;
+ this.description.schematics[`${collectionName}:${name}`] = options;
+ }
}
- public run(options: any) {
+ public async run(options: T) {
const [collectionName, schematicName] = this.parseSchematicInfo(options);
- // remove the schematic name from the options
- delete options.schematic;
+ if (!schematicName || !collectionName) {
+ return this.printHelp(options);
+ }
return this.runSchematic({
collectionName,
schematicName,
- schematicOptions: this.removeLocalOptions(options),
- debug: options.debug,
- dryRun: options.dryRun,
- force: options.force,
- interactive: options.interactive,
+ schematicOptions: options['--'] || [],
+ debug: !!options.debug || false,
+ dryRun: !!options.dryRun || false,
+ force: !!options.force || false,
});
}
- private parseSchematicInfo(options: any) {
+ private parseSchematicInfo(options: { schematic?: string }): [string, string | undefined] {
let collectionName = this.getDefaultSchematicCollection();
- let schematicName: string = options.schematic;
+ let schematicName = options.schematic;
if (schematicName) {
if (schematicName.includes(':')) {
@@ -74,33 +75,14 @@ export class GenerateCommand extends SchematicCommand {
return [collectionName, schematicName];
}
- public printHelp(_name: string, _description: string, options: any) {
- const schematicName = options._[0];
- if (schematicName) {
- const optsWithoutSchematic = this.options
- .filter(o => !(o.name === 'schematic' && this.isArgument(o)));
- this.printHelpUsage(`generate ${schematicName}`, optsWithoutSchematic);
- this.printHelpOptions(this.options);
- } else {
- this.printHelpUsage('generate', this.options);
- const engineHost = this.getEngineHost();
- const [collectionName] = this.parseSchematicInfo(options);
- const collection = this.getCollection(collectionName);
- const schematicNames: string[] = engineHost.listSchematicNames(collection.description);
- this.logger.info('Available schematics:');
- schematicNames.forEach(schematicName => {
- this.logger.info(` ${schematicName}`);
- });
-
- this.logger.warn(`\nTo see help for a schematic run:`);
+ public async printHelp(options: T) {
+ await super.printHelp(options);
+
+ if (Object.keys(this.description.schematics || {}).length == 1) {
+ this.logger.info(`\nTo see help for a schematic run:`);
this.logger.info(terminal.cyan(` ng generate --help`));
}
- }
-
- private removeLocalOptions(options: any): any {
- const opts = Object.assign({}, options);
- delete opts.interactive;
- return opts;
+ return 0;
}
}
diff --git a/packages/angular/cli/commands/generate.json b/packages/angular/cli/commands/generate.json
index 3a784e3d47b9..f8668058cc9f 100644
--- a/packages/angular/cli/commands/generate.json
+++ b/packages/angular/cli/commands/generate.json
@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/schema",
- "id": "GenerateCommandOptions",
+ "$id": "ng-cli://commands/generate.json",
"description": "Generates and/or modifies files based on a schematic.",
"$longDescription": "",
@@ -9,34 +9,23 @@
"$type": "schematics",
"$impl": "./generate-impl#GenerateCommand",
- "type": "object",
- "properties": {
- "schematic": {
- "type": "string",
- "description": "The schematic or collection:schematic to generate.",
- "$default": {
- "$source": "argv",
- "index": 0
- }
+ "allOf": [
+ {
+ "type": "object",
+ "properties": {
+ "schematic": {
+ "type": "string",
+ "description": "The schematic or collection:schematic to generate.",
+ "$default": {
+ "$source": "argv",
+ "index": 0
+ }
+ }
+ },
+ "required": [
+ ]
},
- "dryRun": {
- "type": "boolean",
- "default": false,
- "aliases": ["d"],
- "description": "Run through without making any changes."
- },
- "force": {
- "type": "boolean",
- "default": false,
- "aliases": ["f"],
- "description": "Forces overwriting of files."
- },
- "interactive": {
- "type": "boolean",
- "default": "true",
- "description": "Disables interactive inputs (i.e., prompts)."
- }
- },
- "required": [
+ { "$ref": "./definitions.json#/definitions/base" },
+ { "$ref": "./definitions.json#/definitions/schematic" }
]
-}
\ No newline at end of file
+}
diff --git a/packages/angular/cli/commands/help-impl.ts b/packages/angular/cli/commands/help-impl.ts
index ac31379a27b8..a117a19fd209 100644
--- a/packages/angular/cli/commands/help-impl.ts
+++ b/packages/angular/cli/commands/help-impl.ts
@@ -5,36 +5,23 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
-
-// tslint:disable:no-global-tslint-disable no-any
import { terminal } from '@angular-devkit/core';
import { Command } from '../models/command';
-interface CommandInfo {
- name: string;
- description: string;
- hidden: boolean;
- aliases: string[];
-}
-
export class HelpCommand extends Command {
- run(options: any) {
+ async run() {
this.logger.info(`Available Commands:`);
- options.commandInfo
- .filter((cmd: CommandInfo) => !cmd.hidden)
- .forEach((cmd: CommandInfo) => {
- let aliasInfo = '';
- if (cmd.aliases.length > 0) {
- aliasInfo = ` (${cmd.aliases.join(', ')})`;
- }
- this.logger.info(` ${terminal.cyan(cmd.name)}${aliasInfo} ${cmd.description}`);
- });
+ for (const name of Object.keys(Command.commandMap)) {
+ const cmd = Command.commandMap[name];
- this.logger.info(`\nFor more detailed help run "ng [command name] --help"`);
- }
+ if (cmd.hidden) {
+ continue;
+ }
- printHelp(_commandName: string, _description: string, options: any) {
- return this.run(options);
+ const aliasInfo = cmd.aliases.length > 0 ? ` (${cmd.aliases.join(', ')})` : '';
+ this.logger.info(` ${terminal.cyan(cmd.name)}${aliasInfo} ${cmd.description}`);
+ }
+ this.logger.info(`\nFor more detailed help run "ng [command name] --help"`);
}
}
diff --git a/packages/angular/cli/commands/help.json b/packages/angular/cli/commands/help.json
index 6ce3882e17aa..51c7adc53398 100644
--- a/packages/angular/cli/commands/help.json
+++ b/packages/angular/cli/commands/help.json
@@ -1,11 +1,12 @@
{
"$schema": "http://json-schema.org/schema",
- "id": "HelpCommandOptions",
+ "$id": "ng-cli://commands/help.json",
"description": "Displays help for the Angular CLI.",
"$longDescription": "",
+ "$scope": "all",
"$aliases": [],
"$impl": "./help-impl#HelpCommand",
"type": "object"
-}
\ No newline at end of file
+}
diff --git a/packages/angular/cli/commands/lint.json b/packages/angular/cli/commands/lint.json
index 6340ca1e3d4a..7d007122c28a 100644
--- a/packages/angular/cli/commands/lint.json
+++ b/packages/angular/cli/commands/lint.json
@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/schema",
- "id": "LintCommandOptions",
+ "$id": "ng-cli://commands/lint.json",
"description": "Lints code in existing project.",
"$longDescription": "",
@@ -10,21 +10,30 @@
"$impl": "./lint-impl#LintCommand",
"type": "object",
- "properties": {
- "project": {
- "type": "string",
- "description": "The name of the project to lint.",
- "$default": {
- "$source": "argv",
- "index": 0
- }
+ "allOf": [
+ {
+ "properties": {
+ "project": {
+ "type": "string",
+ "description": "The name of the project to lint.",
+ "$default": {
+ "$source": "argv",
+ "index": 0
+ }
+ },
+ "configuration": {
+ "description": "Specify the configuration to use.",
+ "type": "string",
+ "aliases": [
+ "c"
+ ]
+ }
+ },
+ "required": [
+ ]
},
- "configuration": {
- "description": "Specify the configuration to use.",
- "type": "string",
- "aliases": ["c"]
+ {
+ "$ref": "./definitions.json#/definitions/base"
}
- },
- "required": [
]
-}
\ No newline at end of file
+}
diff --git a/packages/angular/cli/commands/new-impl.ts b/packages/angular/cli/commands/new-impl.ts
index 338d821d150f..94ae330611eb 100644
--- a/packages/angular/cli/commands/new-impl.ts
+++ b/packages/angular/cli/commands/new-impl.ts
@@ -7,32 +7,19 @@
*/
// tslint:disable:no-global-tslint-disable no-any
-import { SchematicCommand } from '../models/schematic-command';
+import { BaseSchematicOptions, SchematicCommand } from '../models/schematic-command';
+
+export interface NewCommandOptions extends BaseSchematicOptions {
+ skipGit?: boolean;
+ collection?: string;
+}
+
export class NewCommand extends SchematicCommand {
public readonly allowMissingWorkspace = true;
private schematicName = 'ng-new';
- private initialized = false;
- public async initialize(options: any) {
- if (this.initialized) {
- return;
- }
-
- await super.initialize(options);
-
- this.initialized = true;
-
- const collectionName = this.parseCollectionName(options);
-
- const schematicOptions = await this.getOptions({
- schematicName: this.schematicName,
- collectionName,
- });
- this.addOptions(this.options.concat(schematicOptions));
- }
-
- public async run(options: any) {
+ public async run(options: NewCommandOptions) {
if (options.dryRun) {
options.skipGit = true;
}
@@ -48,33 +35,19 @@ export class NewCommand extends SchematicCommand {
const packageJson = require('../package.json');
const version = packageJson.version;
- // Ensure skipGit has a boolean value.
- options.skipGit = options.skipGit === undefined ? false : options.skipGit;
this._workflow.registry.addSmartDefaultProvider('ng-cli-version', () => version);
return this.runSchematic({
collectionName: collectionName,
schematicName: this.schematicName,
- schematicOptions: this.removeLocalOptions(options),
+ schematicOptions: options['--'] || [],
debug: options.debug,
dryRun: options.dryRun,
force: options.force,
- interactive: options.interactive,
});
}
private parseCollectionName(options: any): string {
- const collectionName = options.collection || options.c || this.getDefaultSchematicCollection();
-
- return collectionName;
- }
-
- private removeLocalOptions(options: any): any {
- const opts = Object.assign({}, options);
- delete opts.verbose;
- delete opts.collection;
- delete opts.interactive;
-
- return opts;
+ return options.collection || this.getDefaultSchematicCollection();
}
}
diff --git a/packages/angular/cli/commands/new.json b/packages/angular/cli/commands/new.json
index b1f8db9f577b..ed301b03a217 100644
--- a/packages/angular/cli/commands/new.json
+++ b/packages/angular/cli/commands/new.json
@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/schema",
- "id": "NewCommandOptions",
+ "$id": "ng-cli://commands/new.json",
"description": "Creates a new directory and a new Angular app.",
"$longDescription": "",
@@ -10,35 +10,24 @@
"$impl": "./new-impl#NewCommand",
"type": "object",
- "properties": {
- "collection": {
- "type": "string",
- "aliases": ["c"],
- "description": "Schematics collection to use."
+ "allOf": [
+ {
+ "properties": {
+ "collection": {
+ "type": "string",
+ "aliases": [ "c" ],
+ "description": "Schematics collection to use."
+ },
+ "verbose": {
+ "type": "boolean",
+ "default": false,
+ "aliases": [ "v" ],
+ "description": "Adds more details to output logging."
+ }
+ },
+ "required": []
},
- "dryRun": {
- "type": "boolean",
- "default": false,
- "aliases": ["d"],
- "description": "Run through without making any changes."
- },
- "force": {
- "type": "boolean",
- "default": false,
- "aliases": ["f"],
- "description": "Forces overwriting of files."
- },
- "verbose": {
- "type": "boolean",
- "default": false,
- "aliases": ["v"],
- "description": "Adds more details to output logging."
- },
- "interactive": {
- "type": "boolean",
- "default": "true",
- "description": "Disables interactive inputs (i.e., prompts)."
- }
- },
- "required": []
-}
\ No newline at end of file
+ { "$ref": "./definitions.json#/definitions/base" },
+ { "$ref": "./definitions.json#/definitions/schematic" }
+ ]
+}
diff --git a/packages/angular/cli/commands/run.json b/packages/angular/cli/commands/run.json
index c46b8b3e72e1..51b74bbacf55 100644
--- a/packages/angular/cli/commands/run.json
+++ b/packages/angular/cli/commands/run.json
@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/schema",
- "id": "RunCommandOptions",
+ "$id": "ng-cli://commands/run.json",
"description": "Runs Architect targets.",
"$longDescription": "",
@@ -10,21 +10,28 @@
"$impl": "./run-impl#RunCommand",
"type": "object",
- "properties": {
- "target": {
- "type": "string",
- "description": "The target to run.",
- "$default": {
- "$source": "argv",
- "index": 0
- }
+ "allOf": [
+ {
+ "properties": {
+ "target": {
+ "type": "string",
+ "description": "The target to run.",
+ "$default": {
+ "$source": "argv",
+ "index": 0
+ }
+ },
+ "configuration": {
+ "description": "Specify the configuration to use.",
+ "type": "string",
+ "aliases": [ "c" ]
+ }
+ },
+ "required": [
+ ]
},
- "configuration": {
- "description": "Specify the configuration to use.",
- "type": "string",
- "aliases": ["c"]
+ {
+ "$ref": "./definitions.json#/definitions/base"
}
- },
- "required": [
]
-}
\ No newline at end of file
+}
diff --git a/packages/angular/cli/commands/serve.json b/packages/angular/cli/commands/serve.json
index d10ebccee6a9..9b01a5039cb5 100644
--- a/packages/angular/cli/commands/serve.json
+++ b/packages/angular/cli/commands/serve.json
@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/schema",
- "id": "ServeCommandOptions",
+ "$id": "ng-cli://commands/serve.json",
"description": "Builds and serves your app, rebuilding on file changes.",
"$longDescription": "",
@@ -10,25 +10,8 @@
"$impl": "./serve-impl#ServeCommand",
"type": "object",
- "properties": {
- "project": {
- "type": "string",
- "description": "The name of the project to serve.",
- "$default": {
- "$source": "argv",
- "index": 0
- }
- },
- "configuration": {
- "description": "Specify the configuration to use.",
- "type": "string",
- "aliases": ["c"]
- },
- "prod": {
- "description": "Flag to set configuration to 'production'.",
- "type": "boolean"
- }
- },
- "required": [
+ "allOf": [
+ { "$ref": "./definitions.json#/definitions/architect" },
+ { "$ref": "./definitions.json#/definitions/base" }
]
-}
\ No newline at end of file
+}
diff --git a/packages/angular/cli/commands/test.json b/packages/angular/cli/commands/test.json
index f987e7fc5cbd..79c5a0ef8c85 100644
--- a/packages/angular/cli/commands/test.json
+++ b/packages/angular/cli/commands/test.json
@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/schema",
- "id": "TestCommandOptions",
+ "$id": "ng-cli://commands/test.json",
"description": "Run unit tests in existing project.",
"$longDescription": "",
@@ -10,25 +10,8 @@
"$impl": "./test-impl#TestCommand",
"type": "object",
- "properties": {
- "project": {
- "type": "string",
- "description": "The name of the project to test.",
- "$default": {
- "$source": "argv",
- "index": 0
- }
- },
- "configuration": {
- "description": "Specify the configuration to use.",
- "type": "string",
- "aliases": ["c"]
- },
- "prod": {
- "description": "Flag to set configuration to 'production'.",
- "type": "boolean"
- }
- },
- "required": [
+ "allOf": [
+ { "$ref": "./definitions.json#/definitions/architect" },
+ { "$ref": "./definitions.json#/definitions/base" }
]
-}
\ No newline at end of file
+}
diff --git a/packages/angular/cli/commands/update-impl.ts b/packages/angular/cli/commands/update-impl.ts
index 0594d3c54921..4dbe42cd67f6 100644
--- a/packages/angular/cli/commands/update-impl.ts
+++ b/packages/angular/cli/commands/update-impl.ts
@@ -5,77 +5,83 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
-
-// tslint:disable:no-global-tslint-disable no-any
import { normalize } from '@angular-devkit/core';
-import { CommandScope, Option } from '../models/command';
-import { CoreSchematicOptions, SchematicCommand } from '../models/schematic-command';
+import { Arguments, Option } from '../models/interface';
+import { BaseSchematicOptions, SchematicCommand } from '../models/schematic-command';
import { findUp } from '../utilities/find-up';
+import { parseJsonSchemaToOptions } from '../utilities/json-schema';
-export interface UpdateOptions extends CoreSchematicOptions {
+export interface UpdateOptions extends BaseSchematicOptions {
next: boolean;
schematic?: boolean;
+ dryRun: boolean;
+ force: boolean;
}
+type UpdateSchematicOptions = Arguments & {
+ migrateOnly?: boolean;
+ from?: string;
+ packages?: string | string[];
+};
+
-export class UpdateCommand extends SchematicCommand {
- public readonly name = 'update';
- public readonly description = 'Updates your application and its dependencies.';
- public static aliases: string[] = [];
- public static scope = CommandScope.everywhere;
- public arguments: string[] = [ 'packages' ];
- public options: Option[] = [
- // Remove the --force flag.
- ...this.coreOptions.filter(option => option.name !== 'force'),
- ];
+export class UpdateCommand extends SchematicCommand {
public readonly allowMissingWorkspace = true;
private collectionName = '@schematics/update';
private schematicName = 'update';
- private initialized = false;
- public async initialize(options: any) {
- if (this.initialized) {
- return;
- }
- await super.initialize(options);
- this.initialized = true;
+ async initialize(input: T) {
+ await super.initialize(input);
- const schematicOptions = await this.getOptions({
- schematicName: this.schematicName,
- collectionName: this.collectionName,
- });
- this.addOptions(schematicOptions);
+ // Set the options.
+ const collection = this.getCollection(this.collectionName);
+ const schematic = this.getSchematic(collection, this.schematicName, true);
+ const options = await parseJsonSchemaToOptions(
+ this._workflow.registry,
+ schematic.description.schemaJson || {},
+ );
+
+ this.description.options.push(...options);
}
- async validate(options: any) {
- if (options._[0] == '@angular/cli'
- && options.migrateOnly === undefined
- && options.from === undefined) {
+ async parseArguments(schematicOptions: string[], schema: Option[]): Promise {
+ const args = await super.parseArguments(schematicOptions, schema) as UpdateSchematicOptions;
+ const maybeArgsLeftovers = args['--'];
+
+ if (maybeArgsLeftovers
+ && maybeArgsLeftovers.length == 1
+ && maybeArgsLeftovers[0] == '@angular/cli'
+ && args.migrateOnly === undefined
+ && args.from === undefined) {
// Check for a 1.7 angular-cli.json file.
const oldConfigFileNames = [
normalize('.angular-cli.json'),
normalize('angular-cli.json'),
];
- const oldConfigFilePath =
- findUp(oldConfigFileNames, process.cwd())
- || findUp(oldConfigFileNames, __dirname);
+ const oldConfigFilePath = findUp(oldConfigFileNames, process.cwd())
+ || findUp(oldConfigFileNames, __dirname);
if (oldConfigFilePath) {
- options.migrateOnly = true;
- options.from = '1.0.0';
+ args.migrateOnly = true;
+ args.from = '1.0.0';
}
}
- return super.validate(options);
- }
+ // Move `--` to packages.
+ if (args.packages == undefined && args['--']) {
+ args.packages = args['--'];
+ delete args['--'];
+ }
+ return args;
+ }
- public async run(options: UpdateOptions) {
+ async run(options: UpdateOptions) {
return this.runSchematic({
collectionName: this.collectionName,
schematicName: this.schematicName,
- schematicOptions: options,
+ schematicOptions: options['--'],
dryRun: options.dryRun,
force: false,
showNothingDone: false,
diff --git a/packages/angular/cli/commands/update.json b/packages/angular/cli/commands/update.json
index 7a47adf3c7bb..0f179aa44cd8 100644
--- a/packages/angular/cli/commands/update.json
+++ b/packages/angular/cli/commands/update.json
@@ -1,32 +1,39 @@
{
"$schema": "http://json-schema.org/schema",
- "id": "UpdateCommandOptions",
+ "$id": "ng-cli://commands/update.json",
"description": "Updates your application and its dependencies.",
"$longDescription": "",
+ "$scope": "all",
"$aliases": [],
"$type": "schematics",
"$impl": "./update-impl#UpdateCommand",
"type": "object",
- "properties": {
- "packages": {
- "type": "array",
- "items": {
- "type": "string"
+ "allOf": [
+ {
+ "properties": {
+ "packages": {
+ "type": "array",
+ "description": "The names of package(s) to update",
+ "$default": {
+ "$source": "argv"
+ }
+ },
+ "dryRun": {
+ "type": "boolean",
+ "default": false,
+ "aliases": [
+ "d"
+ ],
+ "description": "Run through without making any changes."
+ }
},
- "description": "The names of package(s) to update",
- "$default": {
- "$source": "argv"
- }
+ "required": [
+ ]
},
- "dryRun": {
- "type": "boolean",
- "default": false,
- "aliases": ["d"],
- "description": "Run through without making any changes."
+ {
+ "$ref": "./definitions.json#/definitions/base"
}
- },
- "required": [
]
-}
\ No newline at end of file
+}
diff --git a/packages/angular/cli/commands/version-impl.ts b/packages/angular/cli/commands/version-impl.ts
index 2b9b740d6d58..64e10026ab35 100644
--- a/packages/angular/cli/commands/version-impl.ts
+++ b/packages/angular/cli/commands/version-impl.ts
@@ -17,7 +17,7 @@ import { findUp } from '../utilities/find-up';
export class VersionCommand extends Command {
public static aliases = ['v'];
- public run() {
+ async run() {
const pkg = require(path.resolve(__dirname, '..', 'package.json'));
let projPkg;
try {
diff --git a/packages/angular/cli/commands/version.json b/packages/angular/cli/commands/version.json
index 8d8fda9f1c96..66ff3c3aa364 100644
--- a/packages/angular/cli/commands/version.json
+++ b/packages/angular/cli/commands/version.json
@@ -1,11 +1,12 @@
{
"$schema": "http://json-schema.org/schema",
- "id": "VersionCommandOptions",
+ "$id": "ng-cli://commands/version.json",
"description": "Outputs Angular CLI version.",
"$longDescription": "",
"$aliases": [ "v" ],
+ "$scope": "all",
"$impl": "./version-impl#VersionCommand",
"type": "object"
-}
\ No newline at end of file
+}
diff --git a/packages/angular/cli/commands/xi18n.json b/packages/angular/cli/commands/xi18n.json
index 0c55c95e5a1c..082ae7ca1ba2 100644
--- a/packages/angular/cli/commands/xi18n.json
+++ b/packages/angular/cli/commands/xi18n.json
@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/schema",
- "id": "Xi18nCommandOptions",
+ "$id": "ng-cli://commands/xi18n.json",
"description": "Extracts i18n messages from source code.",
"$longDescription": "",
@@ -10,21 +10,8 @@
"$impl": "./xi18n-impl#Xi18nCommand",
"type": "object",
- "properties": {
- "project": {
- "type": "string",
- "description": "The name of the project to extract.",
- "$default": {
- "$source": "argv",
- "index": 0
- }
- },
- "configuration": {
- "description": "Specify the configuration to use.",
- "type": "string",
- "aliases": ["c"]
- }
- },
- "required": [
+ "allOf": [
+ { "$ref": "./definitions.json#/definitions/architect" },
+ { "$ref": "./definitions.json#/definitions/base" }
]
-}
\ No newline at end of file
+}
diff --git a/packages/angular/cli/lib/cli/index.ts b/packages/angular/cli/lib/cli/index.ts
index 6be758d2b05e..b6940090de15 100644
--- a/packages/angular/cli/lib/cli/index.ts
+++ b/packages/angular/cli/lib/cli/index.ts
@@ -25,12 +25,9 @@ export default async function(options: { testing?: boolean, cliArgs: string[] })
if (projectDetails === null) {
projectDetails = { root: process.cwd() };
}
- const context = {
- project: projectDetails,
- };
try {
- const maybeExitCode = await runCommand(options.cliArgs, logger, context);
+ const maybeExitCode = await runCommand(options.cliArgs, logger, projectDetails);
if (typeof maybeExitCode === 'number') {
console.assert(Number.isInteger(maybeExitCode));
diff --git a/packages/angular/cli/models/architect-command.ts b/packages/angular/cli/models/architect-command.ts
index aacacfe9a588..f1f61a90b103 100644
--- a/packages/angular/cli/models/architect-command.ts
+++ b/packages/angular/cli/models/architect-command.ts
@@ -5,95 +5,47 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
-import {
- Architect,
- BuildEvent,
- BuilderDescription,
- TargetSpecifier,
-} from '@angular-devkit/architect';
-import {
- JsonObject,
- UnknownException,
- experimental,
- schema,
- strings,
- tags,
-} from '@angular-devkit/core';
+import { Architect, BuildEvent, TargetSpecifier } from '@angular-devkit/architect';
+import { experimental, json, schema, tags } from '@angular-devkit/core';
import { NodeJsSyncHost, createConsoleLogger } from '@angular-devkit/core/node';
-import { from, of } from 'rxjs';
+import { from } from 'rxjs';
import { concatMap, map, tap, toArray } from 'rxjs/operators';
-import { Command, Option } from './command';
+import { parseJsonSchemaToOptions } from '../utilities/json-schema';
+import { BaseCommandOptions, Command } from './command';
+import { parseArguments } from './parser';
import { WorkspaceLoader } from './workspace-loader';
-export interface ProjectAndConfigurationOptions {
+export interface ArchitectCommandOptions extends BaseCommandOptions {
project?: string;
configuration?: string;
prod: boolean;
-}
-
-export interface TargetOptions {
target?: string;
}
-export type ArchitectCommandOptions = ProjectAndConfigurationOptions & TargetOptions & JsonObject;
-
export abstract class ArchitectCommand extends Command {
-
private _host = new NodeJsSyncHost();
- private _architect: Architect;
- private _workspace: experimental.workspace.Workspace;
- private _logger = createConsoleLogger();
- // If this command supports running multiple targets.
- protected multiTarget = false;
+ protected _architect: Architect;
+ protected _workspace: experimental.workspace.Workspace;
+ protected _logger = createConsoleLogger();
- readonly Options: Option[] = [{
- name: 'configuration',
- description: 'The configuration',
- type: 'string',
- aliases: ['c'],
- }];
+ protected _registry: json.schema.SchemaRegistry;
- readonly arguments = ['project'];
+ // If this command supports running multiple targets.
+ protected multiTarget = false;
target: string | undefined;
public async initialize(options: ArchitectCommandOptions): Promise {
- return this._loadWorkspaceAndArchitect().pipe(
- concatMap(() => {
- const targetSpec: TargetSpecifier = this._makeTargetSpecifier(options);
-
- if (this.target && !targetSpec.project) {
- const projects = this.getProjectNamesByTarget(this.target);
-
- if (projects.length === 1) {
- // If there is a single target, use it to parse overrides.
- targetSpec.project = projects[0];
- } else {
- // Multiple targets can have different, incompatible options.
- // We only lookup options for single targets.
- return of(null);
- }
- }
-
- if (!targetSpec.project || !targetSpec.target) {
- throw new Error('Cannot determine project or target for Architect command.');
- }
+ await super.initialize(options);
- const builderConfig = this._architect.getBuilderConfiguration(targetSpec);
+ this._registry = new json.schema.CoreSchemaRegistry();
+ this._registry.addPostTransform(json.schema.transforms.addUndefinedDefaults);
- return this._architect.getBuilderDescription(builderConfig).pipe(
- tap(builderDesc => { this.mapArchitectOptions(builderDesc.schema); }),
- );
- }),
- ).toPromise()
- .then(() => { });
- }
+ await this._loadWorkspaceAndArchitect().toPromise();
- public validate(options: ArchitectCommandOptions) {
if (!options.project && this.target) {
const projectNames = this.getProjectNamesByTarget(this.target);
- const { overrides } = this._makeTargetSpecifier(options);
- if (projectNames.length > 1 && Object.keys(overrides || {}).length > 0) {
+ if (projectNames.length > 1 && options['--'] && options['--'].length > 0) {
// Verify that all builders are the same, otherwise error out (since the meaning of an
// option could vary from builder to builder).
@@ -120,91 +72,63 @@ export abstract class ArchitectCommand extends Command
}
}
- return true;
- }
+ const targetSpec: TargetSpecifier = this._makeTargetSpecifier(options);
+
+ if (this.target && !targetSpec.project) {
+ const projects = this.getProjectNamesByTarget(this.target);
- protected mapArchitectOptions(schema: JsonObject) {
- const properties = schema.properties;
- if (typeof properties != 'object' || properties === null || Array.isArray(properties)) {
- throw new UnknownException('Invalid schema.');
+ if (projects.length === 1) {
+ // If there is a single target, use it to parse overrides.
+ targetSpec.project = projects[0];
+ }
}
- const keys = Object.keys(properties);
- keys
- .map(key => {
- const value = properties[key];
- if (typeof value != 'object') {
- throw new UnknownException('Invalid schema.');
- }
- return {
- ...value,
- name: strings.dasherize(key),
- } as any; // tslint:disable-line:no-any
- })
- .map(opt => {
- const types = ['string', 'boolean', 'integer', 'number'];
- // Ignore arrays / objects.
- if (types.indexOf(opt.type) === -1) {
- return null;
- }
+ if ((!targetSpec.project || !targetSpec.target) && !this.multiTarget) {
+ throw new Error('Cannot determine project or target for Architect command.');
+ }
+ }
- let aliases: string[] = [];
- if (opt.alias) {
- aliases = [...aliases, opt.alias];
- }
- if (opt.aliases) {
- aliases = [...aliases, ...opt.aliases];
- }
- const schematicDefault = opt.default;
-
- return {
- ...opt,
- aliases,
- default: undefined, // do not carry over schematics defaults
- schematicDefault,
- hidden: opt.visible === false,
- };
- })
- .filter(x => x)
- .forEach(option => this.addOptions(option));
+ async run(options: ArchitectCommandOptions) {
+ return await this.runArchitectTarget(options);
}
- protected prodOption: Option = {
- name: 'prod',
- description: 'Flag to set configuration to "prod".',
- type: 'boolean',
- };
+ protected async runArchitectTarget(options: ArchitectCommandOptions): Promise {
+ const runSingleTarget = async (targetSpec: TargetSpecifier) => {
+ // We need to build the builderSpec twice because architect does not understand
+ // overrides separately (getting the configuration builds the whole project, including
+ // overrides).
+ const builderConf = this._architect.getBuilderConfiguration(targetSpec);
+ const builderDesc = await this._architect.getBuilderDescription(builderConf).toPromise();
+ const targetOptionArray = await parseJsonSchemaToOptions(this._registry, builderDesc.schema);
+ const overrides = parseArguments(options['--'] || [], targetOptionArray);
+
+ if (overrides['--']) {
+ (overrides['--'] || []).forEach(additional => {
+ this.logger.warn(`Unknown option: '${additional.split(/=/)[0]}'`);
+ });
- protected configurationOption: Option = {
- name: 'configuration',
- description: 'Specify the configuration to use.',
- type: 'string',
- aliases: ['c'],
- };
+ return 1;
+ }
+ const realBuilderConf = this._architect.getBuilderConfiguration({ ...targetSpec, overrides });
- protected async runArchitectTarget(options: ArchitectCommandOptions): Promise {
- delete options._;
- const targetSpec = this._makeTargetSpecifier(options);
-
- const runSingleTarget = (targetSpec: TargetSpecifier) => this._architect.run(
- this._architect.getBuilderConfiguration(targetSpec),
- { logger: this._logger },
- ).pipe(
- map((buildEvent: BuildEvent) => buildEvent.success ? 0 : 1),
- );
+ return this._architect.run(realBuilderConf, { logger: this._logger }).pipe(
+ map((buildEvent: BuildEvent) => buildEvent.success ? 0 : 1),
+ ).toPromise();
+ };
try {
+ const targetSpec = this._makeTargetSpecifier(options);
if (!targetSpec.project && this.target) {
// This runs each target sequentially.
// Running them in parallel would jumble the log messages.
return await from(this.getProjectNamesByTarget(this.target)).pipe(
- concatMap(project => runSingleTarget({ ...targetSpec, project })),
+ concatMap(project => from(runSingleTarget({ ...targetSpec, project }))),
toArray(),
map(results => results.every(res => res === 0) ? 0 : 1),
)
.toPromise();
} else {
- return await runSingleTarget(targetSpec).toPromise();
+ return await runSingleTarget(targetSpec);
}
} catch (e) {
if (e instanceof schema.SchemaValidationException) {
@@ -268,32 +192,22 @@ export abstract class ArchitectCommand extends Command
);
}
- private _makeTargetSpecifier(options: ArchitectCommandOptions): TargetSpecifier {
- let project, target, configuration, overrides;
-
- if (options.target) {
- [project, target, configuration] = options.target.split(':');
+ private _makeTargetSpecifier(commandOptions: ArchitectCommandOptions): TargetSpecifier {
+ let project, target, configuration;
- overrides = { ...options };
- delete overrides.target;
+ if (commandOptions.target) {
+ [project, target, configuration] = commandOptions.target.split(':');
- if (overrides.configuration) {
- configuration = overrides.configuration;
- delete overrides.configuration;
+ if (commandOptions.configuration) {
+ configuration = commandOptions.configuration;
}
} else {
- project = options.project;
+ project = commandOptions.project;
target = this.target;
- configuration = options.configuration;
- if (!configuration && options.prod) {
+ configuration = commandOptions.configuration;
+ if (!configuration && commandOptions.prod) {
configuration = 'production';
}
-
- overrides = { ...options };
-
- delete overrides.configuration;
- delete overrides.prod;
- delete overrides.project;
}
if (!project) {
@@ -307,7 +221,6 @@ export abstract class ArchitectCommand extends Command
project,
configuration,
target,
- overrides,
};
}
}
diff --git a/packages/angular/cli/models/interface.ts b/packages/angular/cli/models/interface.ts
new file mode 100644
index 000000000000..1bfdf7c1269b
--- /dev/null
+++ b/packages/angular/cli/models/interface.ts
@@ -0,0 +1,110 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+import { json, logging } from '@angular-devkit/core';
+
+export type Value = number | string | boolean | (number | string | boolean)[];
+
+export type Arguments = {
+ [argName: string]: Value;
+} & {
+ '--'?: string[];
+}
+
+export interface CommandInterface {
+ initialize(options: T): Promise;
+ printHelp(options: T): Promise;
+ printJsonHelp(_options: T): Promise;
+ run(options: T & Arguments): Promise;
+ validateAndRun(options: T & Arguments): Promise;
+}
+
+export interface CommandConstructor {
+ new(
+ context: CommandContext,
+ description: CommandDescription,
+ logger: logging.Logger,
+ ): CommandInterface;
+}
+
+export interface CommandProject {
+ root: string;
+ configFile?: string;
+}
+
+export interface CommandContext {
+ project: CommandProject;
+}
+
+export enum OptionType {
+ String = 'string',
+ Number = 'number',
+ Boolean = 'boolean',
+ Array = 'array',
+ Any = 'any',
+}
+
+export interface Option {
+ name: string;
+ description: string;
+ type: OptionType;
+ types?: OptionType[];
+ aliases: string[];
+ required?: boolean;
+ format?: string;
+ hidden?: boolean;
+ default?: string | number | boolean;
+ positional?: number;
+ $default?: OptionSmartDefault;
+}
+
+export enum CommandScope {
+ InProject = 'in',
+ OutProject = 'out',
+ Everywhere = 'all',
+
+ Default = InProject,
+}
+
+export enum CommandType {
+ Custom = 'custom',
+ Architect = 'architect',
+ Schematic = 'schematics',
+
+ Default = Custom,
+}
+
+export interface CommandDescription {
+ name: string;
+ description: string;
+ options: Option[];
+
+ aliases: string[];
+ scope: CommandScope;
+
+ impl: CommandConstructor;
+
+ hidden: boolean;
+
+ type: CommandType;
+
+ schematics?: {
+ [name: string]: Option[];
+ };
+ architect?: {
+ [name: string]: Option[];
+ };
+}
+
+export interface OptionSmartDefault {
+ $source: string;
+ [key: string]: json.JsonValue;
+}
+
+export interface CommandDescriptionMap {
+ [key: string]: CommandDescription;
+}
diff --git a/packages/angular/cli/models/json-schema.ts b/packages/angular/cli/models/json-schema.ts
deleted file mode 100644
index 1dcb8aee16d3..000000000000
--- a/packages/angular/cli/models/json-schema.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-/**
- * @license
- * Copyright Google Inc. All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.io/license
- */
-import { JsonObject, isJsonObject, parseJson } from '@angular-devkit/core';
-import * as jsonSchemaTraverse from 'json-schema-traverse';
-import { Option, OptionSmartDefault } from './command';
-
-export async function convertSchemaToOptions(schema: string): Promise