From 2316ea26c59d88101b71297fabd54bc9298bc14b Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Fri, 30 Jun 2017 17:51:00 -0700 Subject: [PATCH 01/33] refactor: remove unused dependency and update package package-lock.json was out of date. --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 451e21c447..ba73fe1dab 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,6 @@ "homepage": "https://github.com/angular/devkit", "dependencies": { "@ngtools/json-schema": "^1.0.9", - "@ngtools/logger": "^1.0.1", "@types/common-tags": "^1.2.4", "@types/glob": "^5.0.29", "@types/istanbul": "^0.4.29", From 091a39f0f234c6ed690c3a06b57e77bb649c6569 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Mon, 18 Sep 2017 13:17:14 -0700 Subject: [PATCH 02/33] refactor: move template implementation to core This used to be only used by schematics, but the Core might need it for using templates internally. --- packages/angular_devkit/core/src/utils/index.ts | 1 + .../src/rules/template => core/src/utils}/template.ts | 0 packages/angular_devkit/schematics/src/rules/template.ts | 3 +-- 3 files changed, 2 insertions(+), 2 deletions(-) rename packages/angular_devkit/{schematics/src/rules/template => core/src/utils}/template.ts (100%) diff --git a/packages/angular_devkit/core/src/utils/index.ts b/packages/angular_devkit/core/src/utils/index.ts index c52df75a5b..0fafc46ac2 100644 --- a/packages/angular_devkit/core/src/utils/index.ts +++ b/packages/angular_devkit/core/src/utils/index.ts @@ -7,3 +7,4 @@ */ export * from './object'; export * from './strings'; +export * from './template'; diff --git a/packages/angular_devkit/schematics/src/rules/template/template.ts b/packages/angular_devkit/core/src/utils/template.ts similarity index 100% rename from packages/angular_devkit/schematics/src/rules/template/template.ts rename to packages/angular_devkit/core/src/utils/template.ts diff --git a/packages/angular_devkit/schematics/src/rules/template.ts b/packages/angular_devkit/schematics/src/rules/template.ts index 08aa0cd677..50eee2dbe8 100644 --- a/packages/angular_devkit/schematics/src/rules/template.ts +++ b/packages/angular_devkit/schematics/src/rules/template.ts @@ -5,11 +5,10 @@ * 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 { BaseException, normalize } from '@angular-devkit/core'; +import { BaseException, normalize, template as templateImpl } from '@angular-devkit/core'; import { FileOperator, Rule } from '../engine/interface'; import { FileEntry } from '../tree/interface'; import { chain, forEach } from './base'; -import { template as templateImpl } from './template/template'; import { isBinary } from './utils/is-binary'; From 870166145248b82b0b04ebae83cbb101efe76831 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Thu, 21 Sep 2017 15:13:34 -0700 Subject: [PATCH 03/33] build: add support for requiring ejs files Those files are using template() to be built, and will be built in place and released on npm. You can require those templates if you need, but they need to have a .ejs extension. This required embedding the escape function so that templates' source can be executed without any arguments. Additionally, also added default options to the template function. --- lib/bootstrap-local.js | 9 ++++++++ .../angular_devkit/core/src/utils/template.ts | 21 +++++++++++-------- scripts/build.ts | 18 ++++++++++++++++ 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/lib/bootstrap-local.js b/lib/bootstrap-local.js index 613a571944..774be40be7 100644 --- a/lib/bootstrap-local.js +++ b/lib/bootstrap-local.js @@ -85,6 +85,15 @@ require.extensions['.ts'] = function (m, filename) { }; +require.extensions['.ejs'] = function (m, filename) { + const source = fs.readFileSync(filename).toString(); + const template = require('@angular-devkit/core').template; + const result = template(source, { sourceURL: filename, module: true }); + + return m._compile('module.exports.default = ' + result.toString(), filename); +}; + + // If we're running locally, meaning npm linked. This is basically "developer mode". if (!__dirname.match(new RegExp(`\\${path.sep}node_modules\\${path.sep}`))) { const packages = require('./packages').packages; diff --git a/packages/angular_devkit/core/src/utils/template.ts b/packages/angular_devkit/core/src/utils/template.ts index 570bdfedca..92200a2ccd 100644 --- a/packages/angular_devkit/core/src/utils/template.ts +++ b/packages/angular_devkit/core/src/utils/template.ts @@ -54,11 +54,6 @@ const stringEscapes: {[char: string]: string} = { const reUnescapedString = /['\n\r\u2028\u2029\\]/g; -function _escape(s: string) { - return s ? s.replace(reUnescapedHtml, key => kHtmlEscapes[key]) : ''; -} - - /** * An equivalent of lodash templates, which is based on John Resig's `tmpl` implementation * (http://ejohn.org/blog/javascript-micro-templating/) and Laura Doktorova's doT.js @@ -71,12 +66,14 @@ function _escape(s: string) { * @param options * @return {any} */ -export function template(content: string, options: TemplateOptions): (input: T) => string { +export function template(content: string, options?: TemplateOptions): (input: T) => string { const interpolate = kInterpolateRe; let isEvaluating; let index = 0; let source = `__p += '`; + options = options || {}; + // Compile the regexp to match each delimiter. const reDelimiters = RegExp( `${kEscapeRe.source}|${interpolate.source}|${kEvaluateRe.source}|$`, 'g'); @@ -117,7 +114,13 @@ export function template(content: string, options: TemplateOptions): (input: obj || (obj = {}); let __t; let __p = ''; - const __e = _.escape; + + const __escapes = ${JSON.stringify(kHtmlEscapes)}; + const __escapesre = new RegExp('${reUnescapedHtml.source.replace(/'/g, '\\\'')}', 'g'); + + const __e = function(s) { + return s ? s.replace(__escapesre, key => __escapes[key]) : ''; + }; with (obj) { ${source.replace(/\n/g, '\n ')} } @@ -125,8 +128,8 @@ export function template(content: string, options: TemplateOptions): (input: }; `; - const fn = Function('_', sourceURL + source); - const result = fn({ escape: _escape }); + const fn = Function(sourceURL + source); + const result = fn(); // Provide the compiled function's source by its `toString` method or // the `source` property as a convenience for inlining compiled templates. diff --git a/scripts/build.ts b/scripts/build.ts index 76c4889496..28e07ac9ca 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -230,6 +230,24 @@ export default function(argv: { local?: boolean }, logger: Logger) { files.forEach(fileName => _rm(fileName)); } + logger.info('Building ejs templates...'); + const templateLogger = new Logger('templates', logger); + const templateCompiler = require('@angular-devkit/core').template; + for (const packageName of sortedPackages) { + templateLogger.info(packageName); + const pkg = packages[packageName]; + const files = glob.sync(path.join(pkg.dist, '**/*.ejs')); + templateLogger.info(` ${files.length} ejs files found...`); + files.forEach(fileName => { + const fn = templateCompiler(fs.readFileSync(fileName).toString()); + _rm(fileName); + fs.writeFileSync( + fileName.replace(/\.ejs$/, '.js'), + fn.source.replace(/^\s*return /, 'module.exports.default = '), + ); + }); + } + logger.info('Setting versions...'); const { versions } = require(path.join(__dirname, '../versions.json')); From 10309fcf32fcff4b07222aefcb8ed38f1e968da2 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Thu, 21 Sep 2017 15:16:45 -0700 Subject: [PATCH 04/33] test: allow benchmarks to disable tslint rules Previously only spec files could do it. Benchmark files are similar enough to specs that they should be allowed to disable tslint globally. --- rules/noGlobalTslintDisableRule.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rules/noGlobalTslintDisableRule.ts b/rules/noGlobalTslintDisableRule.ts index 01e6d6d827..bd1b3aca30 100644 --- a/rules/noGlobalTslintDisableRule.ts +++ b/rules/noGlobalTslintDisableRule.ts @@ -46,6 +46,10 @@ class Walker extends Lint.RuleWalker { if (sourceFile.fileName.match(/_spec.ts$/)) { return; } + // Ignore benchmark files. + if (sourceFile.fileName.match(/_benchmark.ts$/)) { + return; + } // Find all comment nodes. const ranges = this._findComments(sourceFile); From e8ee67e032d0ac317f0d7af406eb84f388ad7f87 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Thu, 21 Sep 2017 15:18:07 -0700 Subject: [PATCH 05/33] refactor: add literal template handlers to core utils The only one thats supported right now is stripIndents. This is in order to remove the common-tags dependency from the CLI. --- .../angular_devkit/core/src/utils/index.ts | 1 + .../angular_devkit/core/src/utils/literals.ts | 24 +++++++++++++++++++ .../core/src/utils/literals_spec.ts | 22 +++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 packages/angular_devkit/core/src/utils/literals.ts create mode 100644 packages/angular_devkit/core/src/utils/literals_spec.ts diff --git a/packages/angular_devkit/core/src/utils/index.ts b/packages/angular_devkit/core/src/utils/index.ts index 0fafc46ac2..6b0a8b58ba 100644 --- a/packages/angular_devkit/core/src/utils/index.ts +++ b/packages/angular_devkit/core/src/utils/index.ts @@ -5,6 +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 */ +export * from './literals'; export * from './object'; export * from './strings'; export * from './template'; diff --git a/packages/angular_devkit/core/src/utils/literals.ts b/packages/angular_devkit/core/src/utils/literals.ts new file mode 100644 index 0000000000..7ca3b42293 --- /dev/null +++ b/packages/angular_devkit/core/src/utils/literals.ts @@ -0,0 +1,24 @@ +/** + * @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 + */ + +export function stripIndents(strings: TemplateStringsArray, ...values: string[]) { + const endResult = strings.map((s, i) => s + (i < values.length ? values[i] : '')).join(''); + + // Remove the shortest leading indentation from each line. + const match = endResult.match(/^[ \t]*(?=\S)/gm); + + // Return early if there's nothing to strip. + if (match === null) { + return endResult; + } + + const indent = Math.min(...match.map(el => el.length)); + const regexp = new RegExp('^[ \\t]{' + indent + '}', 'gm'); + + return (indent > 0 ? endResult.replace(regexp, '') : endResult).replace(/[ \t]*$/, ''); +} diff --git a/packages/angular_devkit/core/src/utils/literals_spec.ts b/packages/angular_devkit/core/src/utils/literals_spec.ts new file mode 100644 index 0000000000..de528a3779 --- /dev/null +++ b/packages/angular_devkit/core/src/utils/literals_spec.ts @@ -0,0 +1,22 @@ +/** + * @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 { stripIndents } from './literals'; + +describe('literals', () => { + describe('stripIndents', () => { + it('works', () => { + const test = stripIndents` + hello world + how are you? + test + `; + + expect(test).toBe('\nhello world\n how are you?\ntest\n'); + }); + }); +}); From 215c23883713e252f3300b98024e7cbc961ca926 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Thu, 21 Sep 2017 15:22:36 -0700 Subject: [PATCH 06/33] feat(@angular-devkit/core): add JSON-schema feature This is exported a "schema" from core. This replaces the @ngtools/json-schema library from before. Benchmarks show a 4x improvements on access, at the cost of having to eval a lot of code when parsing the schema, in order to build the proxies and validators for values. This has support for future improvements such as subschema and schema references, which were not possible with the old library. Additionally, the hit for parsing can be reduced significantly later. For now, compatibility with the CLI configuration was the main goal of this feature. Parity with the old system has been achieved (except for .d.ts serialization which is coming next), and new features have been added. --- .../angular_devkit/core/src/json/index.ts | 3 + .../core/src/json/schema/index.ts | 16 + .../core/src/json/schema/registry.ts | 39 ++ .../core/src/json/schema/schema.ts | 109 ++++ .../src/json/schema/serializers/interface.ts | 13 + .../src/json/schema/serializers/javascript.ts | 105 +++ .../serializers/javascript_benchmark.ts | 52 ++ .../schema/serializers/serializers_spec.ts | 46 ++ .../serializers/templates/javascript/index.ts | 21 + .../templates/javascript/prop-any.ejs | 14 + .../templates/javascript/prop-array.ejs | 126 ++++ .../templates/javascript/prop-boolean.ejs | 22 + .../templates/javascript/prop-number.ejs | 47 ++ .../templates/javascript/prop-object.ejs | 143 +++++ .../templates/javascript/prop-string.ejs | 39 ++ .../serializers/templates/javascript/root.ejs | 20 + .../templates/javascript/subschema.ejs | 40 ++ .../schema/serializers/0.0.javascript_spec.ts | 41 ++ .../json/schema/serializers/0.schema.json | 18 + .../schema/serializers/1.0.javascript_spec.ts | 64 ++ .../json/schema/serializers/1.schema.json | 44 ++ .../schema/serializers/schema_benchmark.json | 599 ++++++++++++++++++ 22 files changed, 1621 insertions(+) create mode 100644 packages/angular_devkit/core/src/json/schema/index.ts create mode 100644 packages/angular_devkit/core/src/json/schema/registry.ts create mode 100644 packages/angular_devkit/core/src/json/schema/schema.ts create mode 100644 packages/angular_devkit/core/src/json/schema/serializers/interface.ts create mode 100644 packages/angular_devkit/core/src/json/schema/serializers/javascript.ts create mode 100644 packages/angular_devkit/core/src/json/schema/serializers/javascript_benchmark.ts create mode 100644 packages/angular_devkit/core/src/json/schema/serializers/serializers_spec.ts create mode 100644 packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/index.ts create mode 100644 packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-any.ejs create mode 100644 packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-array.ejs create mode 100644 packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-boolean.ejs create mode 100644 packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-number.ejs create mode 100644 packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-object.ejs create mode 100644 packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-string.ejs create mode 100644 packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/root.ejs create mode 100644 packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/subschema.ejs create mode 100644 tests/@angular_devkit/core/json/schema/serializers/0.0.javascript_spec.ts create mode 100644 tests/@angular_devkit/core/json/schema/serializers/0.schema.json create mode 100644 tests/@angular_devkit/core/json/schema/serializers/1.0.javascript_spec.ts create mode 100644 tests/@angular_devkit/core/json/schema/serializers/1.schema.json create mode 100644 tests/@angular_devkit/core/json/schema/serializers/schema_benchmark.json diff --git a/packages/angular_devkit/core/src/json/index.ts b/packages/angular_devkit/core/src/json/index.ts index d68b312805..4109871211 100644 --- a/packages/angular_devkit/core/src/json/index.ts +++ b/packages/angular_devkit/core/src/json/index.ts @@ -7,3 +7,6 @@ */ export * from './interface'; export * from './parser'; + +import * as schema from './schema'; +export { schema }; diff --git a/packages/angular_devkit/core/src/json/schema/index.ts b/packages/angular_devkit/core/src/json/schema/index.ts new file mode 100644 index 0000000000..28983353e5 --- /dev/null +++ b/packages/angular_devkit/core/src/json/schema/index.ts @@ -0,0 +1,16 @@ +/** + * @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 { JavascriptSerializer } from './serializers/javascript'; + +export * from './registry'; +export * from './schema'; + + +export const serializers = { + JavascriptSerializer, +}; diff --git a/packages/angular_devkit/core/src/json/schema/registry.ts b/packages/angular_devkit/core/src/json/schema/registry.ts new file mode 100644 index 0000000000..703f68c8bd --- /dev/null +++ b/packages/angular_devkit/core/src/json/schema/registry.ts @@ -0,0 +1,39 @@ +/** + * @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 { BaseException } from '../../exception/exception'; +import { JsonSchema } from './schema'; + + +export class JsonSchemaNotFoundException extends BaseException { + constructor(ref: string) { super(`Reference "${ref}" could not be found in registry.`); } +} + + +export class JsonSchemaRegistry { + private _cache = new Map(); + + constructor() {} + + addSchema(ref: string, schema: JsonSchema) { + this._cache.set(ref, schema); + } + + hasSchema(ref: string) { + return this._cache.has(ref); + } + + getSchemaFromRef(ref: string): JsonSchema { + const schemaCache = this._cache.get(ref); + + if (!schemaCache) { + throw new JsonSchemaNotFoundException(ref); + } + + return schemaCache; + } +} diff --git a/packages/angular_devkit/core/src/json/schema/schema.ts b/packages/angular_devkit/core/src/json/schema/schema.ts new file mode 100644 index 0000000000..d9428fdb7a --- /dev/null +++ b/packages/angular_devkit/core/src/json/schema/schema.ts @@ -0,0 +1,109 @@ +/** + * @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 { JsonArray, JsonValue } from '../interface'; + + +export interface JsonSchemaBase { + $schema?: string; + $id?: string; + + // Metadata. + id?: string; + title?: string; + description?: string; + readonly?: boolean; + + // Reference properties. + $ref?: string; + allOf?: JsonSchema[]; + anyOf?: JsonSchema[]; + oneOf?: JsonSchema[]; + + // Structural properties. + definitions?: { [name: string]: JsonSchema }; +} + +export interface JsonSchemaString { + type: 'string'; + default?: string; + + minLength?: number; + maxLength?: number; + pattern?: string; + format?: string; +} + +export interface JsonSchemaNumberBase { + default?: number; + multipleOf?: number; + + // Range. + minimum?: number; + maximum?: number; + exclusiveMinimum?: boolean; + exclusiveMaximum?: boolean; +} + +export interface JsonSchemaNumber extends JsonSchemaNumberBase { + type: 'number'; +} + +export interface JsonSchemaInteger extends JsonSchemaNumberBase { + type: 'integer'; +} + +export interface JsonSchemaBoolean { + type: 'boolean'; + default?: boolean; +} + +export interface JsonSchemaObject extends JsonSchemaBase { + type: 'object'; + + // Object properties. + properties?: { [name: string]: JsonSchema }; + patternProperties?: { [pattern: string]: JsonSchema }; + required?: string[]; + minProperties?: number; + maxProperties?: number; + + dependencies?: { [name: string]: JsonSchema | string[]; }; + + additionalProperties?: boolean | JsonSchema; +} + +export interface JsonSchemaArray extends JsonSchemaBase { + type: 'array'; + + additionalItems?: boolean | JsonSchema; + items?: JsonSchema | JsonSchema[]; + maxItems?: number; + minItems?: number; + uniqueItems?: boolean; +} + +export interface JsonSchemaOneOfType extends JsonSchemaBase { + type: JsonSchemaBaseType[]; +} + +export interface JsonSchemaAny { + // Type related properties. + type: undefined; + default?: JsonValue; + enum?: JsonArray; +} + + +export type JsonSchema = JsonSchemaString + | JsonSchemaNumber + | JsonSchemaInteger + | JsonSchemaObject + | JsonSchemaArray + | JsonSchemaOneOfType + | JsonSchemaAny; +export type JsonSchemaBaseType = undefined | 'string' | 'number' | 'object' | 'array' | 'boolean'; diff --git a/packages/angular_devkit/core/src/json/schema/serializers/interface.ts b/packages/angular_devkit/core/src/json/schema/serializers/interface.ts new file mode 100644 index 0000000000..a1bd731ca2 --- /dev/null +++ b/packages/angular_devkit/core/src/json/schema/serializers/interface.ts @@ -0,0 +1,13 @@ +/** + * @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 { JsonSchemaRegistry } from '../registry'; + + +export abstract class JsonSchemaSerializer { + abstract serialize(ref: string, registry: JsonSchemaRegistry): T; +} diff --git a/packages/angular_devkit/core/src/json/schema/serializers/javascript.ts b/packages/angular_devkit/core/src/json/schema/serializers/javascript.ts new file mode 100644 index 0000000000..60323bee3b --- /dev/null +++ b/packages/angular_devkit/core/src/json/schema/serializers/javascript.ts @@ -0,0 +1,105 @@ +/** + * @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 { BaseException } from '../../../exception/exception'; +import { camelize, classify } from '../../../utils/strings'; +import { JsonSchemaRegistry } from '../registry'; +import { JsonSchema } from '../schema'; +import { JsonSchemaSerializer } from './interface'; + + +export class InvalidRangeException extends BaseException { + constructor(name: string, value: T, comparator: string, expected: T) { + super(`Property ${JSON.stringify(name)} expected a value ` + + `${comparator} ${JSON.stringify(expected)}, received ${JSON.stringify(value)}.`); + } +} +export class InvalidValueException extends BaseException { + constructor(name: string, value: {}, expected: string) { + super(`Property ${JSON.stringify(name)} expected a value of type ${expected}, ` + + `received ${value}.`); + } +} +export class InvalidSchemaException extends BaseException { + constructor(schema: JsonSchema) { + super(`Invalid schema: ${JSON.stringify(schema)}`); + } +} +export class InvalidPropertyNameException extends BaseException { + constructor(public readonly path: string) { + super(`Property ${JSON.stringify(path)} does not exist in the schema, and no additional ` + + `properties are allowed.`); + } +} +export class RequiredValueMissingException extends BaseException { + constructor(public readonly path: string) { + super(`Property ${JSON.stringify(path)} is required but missing.`); + } +} + + +const exceptions = { + InvalidRangeException, + InvalidSchemaException, + InvalidValueException, + InvalidPropertyNameException, + RequiredValueMissingException, +}; + + +const symbols = { + Schema: Symbol('schema'), +}; + + +export interface JavascriptSerializerOptions { + // Do not throw an exception if an extra property is passed, simply ignore it. + ignoreExtraProperties?: boolean; + // Allow accessing undefined objects, which might have default property values. + allowAccessUndefinedObjects?: boolean; +} + + +export class JavascriptSerializer extends JsonSchemaSerializer<(value: T) => T> { + private _uniqueSet = new Set(); + + constructor(private _options?: JavascriptSerializerOptions) { super(); } + + protected _unique(name: string) { + let i = 1; + let result = name; + while (this._uniqueSet.has(result)) { + result = name + i; + i++; + } + this._uniqueSet.add(result); + + return result; + } + + serialize(ref: string, registry: JsonSchemaRegistry) { + const rootSchema = registry.getSchemaFromRef(ref); + const { root, templates } = require('./templates/javascript'); + + const source = root({ + exceptions, + name: '', + options: this._options || {}, + schema: rootSchema, + strings: { + classify, + camelize, + }, + symbols, + templates, + }); + + const fn = new Function('registry', 'exceptions', 'symbols', 'value', source); + + return (value: T) => fn(registry, exceptions, symbols, value); + } +} diff --git a/packages/angular_devkit/core/src/json/schema/serializers/javascript_benchmark.ts b/packages/angular_devkit/core/src/json/schema/serializers/javascript_benchmark.ts new file mode 100644 index 0000000000..b38822b398 --- /dev/null +++ b/packages/angular_devkit/core/src/json/schema/serializers/javascript_benchmark.ts @@ -0,0 +1,52 @@ +/** + * @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 +import { benchmark } from '@_/benchmark'; +import { SchemaClassFactory } from '@ngtools/json-schema'; +import * as fs from 'fs'; +import * as path from 'path'; +import { JsonSchemaRegistry } from '../registry'; +import { JsonSchema } from '../schema'; +import { JavascriptSerializer } from './javascript'; + +describe('JavaScript Serializer', () => { + // Schema for the Angular-CLI config. + const jsonPath = path.join( + (global as any)._DevKitRoot, + 'tests/@angular_devkit/core/json/schema/serializers/schema_benchmark.json', + ); + const jsonContent = fs.readFileSync(jsonPath).toString(); + const complexSchema: JsonSchema = JSON.parse(jsonContent); + + const registry = new JsonSchemaRegistry(); + registry.addSchema('', complexSchema); + + benchmark('schema parsing', () => { + new JavascriptSerializer().serialize('', registry)({}); + }, () => { + const SchemaMetaClass = SchemaClassFactory(complexSchema); + const schemaClass = new SchemaMetaClass({}); + schemaClass.$$root(); + }); + + (function() { + const registry = new JsonSchemaRegistry(); + registry.addSchema('', complexSchema); + const coreRoot = new JavascriptSerializer().serialize('', registry)({}); + + const SchemaMetaClass = SchemaClassFactory(complexSchema); + const schemaClass = new SchemaMetaClass({}); + const ngtoolsRoot = schemaClass.$$root(); + + benchmark('schema access', () => { + coreRoot.project = { name: 'abc' }; + }, () => { + ngtoolsRoot.project = { name: 'abc' }; + }); + })(); +}); diff --git a/packages/angular_devkit/core/src/json/schema/serializers/serializers_spec.ts b/packages/angular_devkit/core/src/json/schema/serializers/serializers_spec.ts new file mode 100644 index 0000000000..08cd8af44d --- /dev/null +++ b/packages/angular_devkit/core/src/json/schema/serializers/serializers_spec.ts @@ -0,0 +1,46 @@ +/** + * @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 +import * as fs from 'fs'; +import * as path from 'path'; +import { JsonSchemaRegistry } from '../registry'; + +describe('serializers', () => { + const devkitRoot = (global as any)._DevKitRoot; + const root = path.join(devkitRoot, 'tests/@angular_devkit/core/json/schema/serializers'); + const allFiles = fs.readdirSync(root); + const schemas = allFiles.filter(x => x.match(/^\d+\.schema\.json$/)); + + for (const schemaName of schemas) { + // tslint:disable-next-line:non-null-operator + const schemaN = schemaName.match(/^\d+/) ![0] || '0'; + const schema = JSON.parse(fs.readFileSync(path.join(root, schemaName)).toString()); + + const serializers = allFiles.filter(x => { + return x.startsWith(schemaN + '.') && x.match(/^\d+\.\d+\..*_spec\.[jt]s$/); + }); + + for (const serializerName of serializers) { + // tslint:disable-next-line:non-null-operator + const [, indexN, serializerN] = serializerName.match(/^\d+\.(\d+)\.(.*)_spec/) !; + const serializer = require(path.join(root, serializerName)); + + const registry = new JsonSchemaRegistry(); + + for (const fnName of Object.keys(serializer)) { + if (typeof serializer[fnName] != 'function') { + continue; + } + + it(`${JSON.stringify(serializerN)} (${schemaN}.${indexN}.${fnName})`, () => { + serializer[fnName](registry, schema); + }); + } + } + } +}); diff --git a/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/index.ts b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/index.ts new file mode 100644 index 0000000000..13686cc191 --- /dev/null +++ b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/index.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 + */ + +export const templates: { [name: string]: (value: {}) => string } = { + subschema: require('./subschema').default, + + prop_any: require('./prop-any').default, + prop_array: require('./prop-array').default, + prop_boolean: require('./prop-boolean').default, + prop_number: require('./prop-number').default, + prop_integer: require('./prop-number').default, + prop_object: require('./prop-object').default, + prop_string: require('./prop-string').default, +}; + +export const root = require('./root').default as (value: {}) => string; diff --git a/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-any.ejs b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-any.ejs new file mode 100644 index 0000000000..66191f8333 --- /dev/null +++ b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-any.ejs @@ -0,0 +1,14 @@ +{ + value: undefined, + get() { return this.value === undefined ? <%= 'default' in schema ? JSON.stringify(schema.default) : 'undefined' %> : this.value; }, + set(v) { + if (v === undefined && <%= !required %>) { + this.value = undefined; + return; + } + this.value = v; + }, + isDefined() { return this.value !== undefined; }, + remove() { this.set(undefined); }, + schema() { return <%= JSON.stringify(schema) %>; }, +} diff --git a/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-array.ejs b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-array.ejs new file mode 100644 index 0000000000..b862287837 --- /dev/null +++ b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-array.ejs @@ -0,0 +1,126 @@ +(function() { +<% + if ('default' in schema && !Array.isArray(schema.default) + || 'minItems' in schema && (typeof schema.minItems != 'number' || schema.minItems < 0) + || 'maxItems' in schema && (typeof schema.maxItems != 'number' || schema.maxItems < 0) + || 'uniqueItems' in schema && typeof schema.uniqueItems != 'boolean') { + throw new exceptions.InvalidSchemaException(schema); + } + + const required = (schema.required || []); + const extras = { + exceptions, + options, + path, + strings, + symbols, + templates, + }; +%> + +const itemHandler = () => (<%= + templates.subschema({ + name: '???', + required: false, + schema: schema.additionalProperties, + ...extras, + }) +%>); + + +const items = []; +const arrayFunctions = { + get length() { return items.length; }, + push(...x) { items.push(...x); }, + pop() { return items.pop(); }, + shift() { return items.shift(); }, + unshift() { return items.unshift(); }, + slice(start, end) { return items.slice(start, end); }, +}; + + +let defined = false; +const proxy = new Proxy({}, { + isExtensible() { return false; }, + has(target, prop) { + return (prop in items); + }, + get(target, prop) { + if (prop === symbols.Schema) { + return arrayHandler.schema; + } + if (prop === symbols.Serialize) { + return () => arrayHandler.serialize(); + } + + if (prop >= 0 && prop in value) { + return value[prop].get(); + } + if (prop in arrayFunctions) { + return arrayFunctions[prop]; + } + return undefined; + }, + set(target, prop, v) { + if (prop >= 0) { + if (!(prop in items)) { + items[prop] = itemHandler(); + } + items[prop].set(v); + return true; + } + return false; + }, + deleteProperty(target, prop) { + if (prop >= 0 && prop in value) { + value[prop].remove(); + return true; + } + return false; + }, + defineProperty(target, prop, descriptor) { + return false; + }, + getOwnPropertyDescriptor(target, prop) { + if (prop >= 0 && prop in value) { + return { configurable: true, enumerable: true }; + } + }, + ownKeys(target) { + return Object.keys(items); + }, +}); + +const arrayHandler = { + set(v) { + if (v === undefined) { + defined = false; + return; + } + + defined = true; + for (const key of Object.keys(v)) { + proxy[key] = v[key]; + } + + // Validate required fields. + <% for (const key of required) { %> + if (!(<%= JSON.stringify(key) %> in v)) { + throw new exceptions.RequiredValueMissingException(<%= JSON.stringify(path) %> + '/' + <%= JSON.stringify(key) %>); + }<% } %> + }, + get() { + if (defined) { + return proxy; + } else { + return <%= 'default' in schema ? JSON.stringify(schema.default) : 'undefined' %>; + } + }, + isDefined() { return defined; }, + remove() { this.set(undefined); }, + schema: <%= JSON.stringify(schema) %>, +}; + +return arrayHandler; + +})() diff --git a/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-boolean.ejs b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-boolean.ejs new file mode 100644 index 0000000000..1294e09863 --- /dev/null +++ b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-boolean.ejs @@ -0,0 +1,22 @@ +{ +<% + if ('default' in schema && typeof schema.default != 'boolean') { + throw new exceptions.InvalidSchemaException(schema); + } +%> + value: undefined, + get() { return this.value === undefined ? <%= schema.default || 'undefined' %> : this.value; }, + set(v) { + if (v === undefined && <%= !required %>) { + this.value = undefined; + return; + } + if (typeof v != 'boolean') { + throw new exceptions.InvalidValueException(<%= JSON.stringify(name) %>, typeof v, 'boolean'); + } + this.value = v; + }, + isDefined() { return this.value !== undefined; }, + remove() { this.set(undefined); }, + schema() { return <%= JSON.stringify(schema) %>; }, +} diff --git a/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-number.ejs b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-number.ejs new file mode 100644 index 0000000000..3b76609ec4 --- /dev/null +++ b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-number.ejs @@ -0,0 +1,47 @@ +{ +<% + if ('default' in schema && typeof schema.default != 'number' + || 'minimum' in schema && typeof schema.minimum != 'number' + || 'maximum' in schema && typeof schema.maximum != 'number' + || 'multipleOf' in schema && typeof schema.multipleOf != 'number' + || (!('minimum' in schema) && 'exclusiveMinimum' in schema) + || (!('maximum' in schema) && 'exclusiveMaximum' in schema)) { + throw new exceptions.InvalidSchemaException(schema); + } +%> + value: undefined, + get() { return this.value === undefined ? <%= schema.default || 'undefined' %> : this.value; }, + set(v) { + if (v === undefined && <%= !required %>) { + this.value = undefined; + return; + } + if (typeof v != 'number') { + throw new exceptions.InvalidValueException(<%= JSON.stringify(name) %>, typeof v, 'number'); + }<% +if (schema.type == 'integer') { %> + if (v % 1 != 0) { + throw new exceptions.InvalidValueException(<%= JSON.stringify(name) %>, v, 'integer'); + }<% +} +if ('minimum' in schema) { %> + if (v <%= schema.exclusiveMinimum ? '<=' : '<' %> <%= schema.minimum %>) { + throw new exceptions.InvalidRangeException(<%= JSON.stringify(name) %>, v, '>=', <%= schema.minimum %>); + }<% +} +if ('maximum' in schema) { %> + if (v <%= schema.exclusiveMaximum ? '>=' : '>' %> <%= schema.maximum %>) { + throw new exceptions.InvalidRangeException(<%= JSON.stringify(name) %>, v, '>=', <%= schema.maximum %>); + }<% +} +if ('multipleOf' in schema) { %> + if (v % <%= schema.multipleOf %> != 0) { + throw new exceptions.InvalidRangeException(<%= JSON.stringify(name) %>, v, 'multiple of', <%= schema.maximum %>); + }<% +} %> + this.value = v; + }, + isDefined() { return this.value !== undefined; }, + remove() { this.set(undefined); }, + schema() { return <%= JSON.stringify(schema) %>; }, +} diff --git a/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-object.ejs b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-object.ejs new file mode 100644 index 0000000000..bc9e159f8f --- /dev/null +++ b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-object.ejs @@ -0,0 +1,143 @@ +(function() { +<% + const required = (schema.required || []); + const extras = { + exceptions, + options, + path, + strings, + symbols, + templates, + }; +%> + +const additionalProperties = {}; +const additionalPropertyHandler = <% +if (!('additionalProperties' in schema)) { %>null<% } else { %> () => (<%= + templates.subschema({ + name: '???', + required: false, + schema: schema.additionalProperties, + ...extras, + }) %>)<% +} +%>; + +const handlers = Object.create(null); +<% + for (const propName of Object.keys(schema.properties)) { + const _key = JSON.stringify(propName); + const _name = propName.match(/^[_a-zA-Z][_a-zA-Z0-9]*$/) ? propName : `[${_key}]`; +%>handlers[<%= JSON.stringify(_name) %>] = <%= + templates.subschema({ + name: _name, + required: required.indexOf(propName) != -1, + schema: schema.properties[propName], + ...extras, +}) %><% +} +%> + + +let defined = false; +const proxy = new Proxy({}, { + isExtensible() { return false; }, + has(target, prop) { + return (prop in handlers && handlers[prop].isDefined()) + || (additionalPropertyHandler + ? (prop in additionalProperties && additionalProperties[prop].isDefined()) + : false); + }, + get(target, prop) { + if (prop === symbols.Schema) { + return objectHandler.schema; + } + if (prop === symbols.Serialize) { + return () => objectHandler.serialize(); + } + if (prop in handlers) { + return handlers[prop].get(); + } + return undefined; + }, + set(target, prop, v) { + defined = true; + if (prop in handlers) { + handlers[prop].set(v); + return true; + } else if (additionalPropertyHandler) { + if (!(prop in additionalProperties)) { + additionalProperties[prop] = additionalPropertyHandler(prop); + } + additionalProperties[prop].set(v); + return true; + } else { + <% if (options.ignoreExtraProperties !== true) { + %>throw new exceptions.InvalidPropertyNameException(<%= JSON.stringify(path) %> + '/' + prop);<% + } else { + // Just ignore the property. + %>return true;<% + } %> + } + }, + deleteProperty(target, prop) { + if (prop in handlers) { + handlers[prop].remove(); + return true; + } else if (additionalPropertyHandler && prop in additionalProperties) { + delete additionalProperties[prop]; + } + }, + defineProperty(target, prop, descriptor) { + return false; + }, + getOwnPropertyDescriptor(target, prop) { + if (prop in handlers) { + return { configurable: true, enumerable: true }; + } + }, + ownKeys(target) { + return [].concat( + Object.keys(handlers).filter(key => handlers[key].isDefined()), + additionalPropertyHandler ? Object.keys(additionalProperties) : [], + ); + }, +}); + +const objectHandler = { + set(v) { + if (v === undefined) { + defined = false; + return; + } + + defined = true; + for (const key of Object.keys(v)) { + proxy[key] = v[key]; + } + + // Validate required fields. + <% for (const key of required) { %> + if (!(<%= JSON.stringify(key) %> in v)) { + throw new exceptions.RequiredValueMissingException(<%= JSON.stringify(path) %> + '/' + <%= JSON.stringify(key) %>); + }<% } %> + }, + get() { + if (<%= options.allowAccessUndefinedObjects === true %> || this.isDefined()) { + return proxy; + } else { + return <%= 'default' in schema ? JSON.stringify(schema.default) : 'undefined' %>; + } + }, + isDefined() { + return defined + && (Object.keys(handlers).some(x => handlers[x].isDefined()) + || Object.keys(additionalProperties).some(x => additionalProperties[x].isDefined())); + }, + remove() { this.set(undefined); }, + schema: <%= JSON.stringify(schema) %>, +}; + +return objectHandler; + +})() diff --git a/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-string.ejs b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-string.ejs new file mode 100644 index 0000000000..20b84a86bf --- /dev/null +++ b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-string.ejs @@ -0,0 +1,39 @@ +{ +<% +if ('default' in schema && typeof schema.default != 'string' + || 'minLength' in schema && (typeof schema.minLength != 'number' || schema.minLength < 0) + || 'maxLength' in schema && (typeof schema.maxLength != 'number' || schema.maxLength < 0) + || 'pattern' in schema && typeof schema.pattern != 'string' + || 'format' in schema && typeof schema.format != 'string') { + throw new exceptions.InvalidSchemaException(schema); +} + +const pattern = ('pattern' in schema) ? new RegExp(schema.pattern) : null; +%> + value: undefined, + get() { return this.value === undefined ? <%= JSON.stringify(schema.default) || 'undefined' %> : this.value; }, + set(v) { + if (v === undefined && <%= !required %>) { + this.value = undefined; + return; + } + if (typeof v != 'string') { + throw new exceptions.InvalidValueException(<%= JSON.stringify(name) %>, typeof v, 'string'); + }<% +if ('minLength' in schema) { %> + if (v.length <= <%= schema.minLength %>) { + throw new exceptions.InvalidRangeException(<%= JSON.stringify(name) %>, v, 'longer', <%= schema.minLength %>); + }<% +} +if ('maxLength' in schema) { %> + if (v.length >= <%= schema.maxLength %>) { + throw new exceptions.InvalidRangeException(<%= JSON.stringify(name) %>, v, 'smaller', <%= schema.maxLength %>); + }<% +} +%> + this.value = v; + }, + isDefined() { return this.value !== undefined; }, + remove() { this.set(undefined); }, + schema() { return <%= JSON.stringify(schema) %>; }, +} diff --git a/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/root.ejs b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/root.ejs new file mode 100644 index 0000000000..11fe8ac330 --- /dev/null +++ b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/root.ejs @@ -0,0 +1,20 @@ +<% +const extras = { + exceptions, + options, + strings, + symbols, + templates, +}; +%> + +const holder = <%= templates.subschema({ + name: name, + path: '', + required: false, + schema: schema, + ...extras, +}) %>; + +holder.set(value); +return holder.get(); diff --git a/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/subschema.ejs b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/subschema.ejs new file mode 100644 index 0000000000..074f779fff --- /dev/null +++ b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/subschema.ejs @@ -0,0 +1,40 @@ +<% +const extras = { + exceptions, + options, + path: (path ? path + '/' : '') + name, + strings, + symbols, + templates, +}; + +if (!schema) { + %>null<% +} else if (schema === true) { +%><%= + templates.prop_any({ + name, + required, + schema: {}, + ...extras, + }) +%><% +} else if (!('type' in schema)) { +%><%= + templates.prop_any({ + name, + required, + schema: {}, + ...extras, +}) +%><% +} else { +%><%= + templates['prop_' + schema.type]({ + name: name, + required, + schema: schema, + ...extras, +}) %><% +} +%> diff --git a/tests/@angular_devkit/core/json/schema/serializers/0.0.javascript_spec.ts b/tests/@angular_devkit/core/json/schema/serializers/0.0.javascript_spec.ts new file mode 100644 index 0000000000..bc9e7f0488 --- /dev/null +++ b/tests/@angular_devkit/core/json/schema/serializers/0.0.javascript_spec.ts @@ -0,0 +1,41 @@ +/** + * @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 +import { schema } from '@angular-devkit/core'; + +const { serializers } = schema; + + +export function works(registry: schema.JsonSchemaRegistry, schema: any) { + const value = { + 'firstName': 'Hans', + 'lastName': 'Larsen', + 'age': 30, + }; + + registry.addSchema('', schema); + + const v = (new serializers.JavascriptSerializer()).serialize('', registry)(value); + + expect(v.firstName).toBe('Hans'); + expect(v.lastName).toBe('Larsen'); + expect(v.age).toBe(30); + + v.age = 10; + expect(v.age).toBe(10); + + expect(() => v.age = -1).toThrow(); + expect(() => v.age = 'hello').toThrow(); + expect(() => v.age = []).toThrow(); + expect(() => v.age = undefined).not.toThrow(); + + expect(() => v.firstName = 0).toThrow(); + expect(() => v.firstName = []).toThrow(); + // This should throw as the value is required. + expect(() => v.firstName = undefined).toThrow(); +} diff --git a/tests/@angular_devkit/core/json/schema/serializers/0.schema.json b/tests/@angular_devkit/core/json/schema/serializers/0.schema.json new file mode 100644 index 0000000000..d764bc8154 --- /dev/null +++ b/tests/@angular_devkit/core/json/schema/serializers/0.schema.json @@ -0,0 +1,18 @@ +{ + "title": "Person", + "type": "object", + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "age": { + "description": "Age in years", + "type": "integer", + "minimum": 0 + } + }, + "required": ["firstName", "lastName"] +} diff --git a/tests/@angular_devkit/core/json/schema/serializers/1.0.javascript_spec.ts b/tests/@angular_devkit/core/json/schema/serializers/1.0.javascript_spec.ts new file mode 100644 index 0000000000..ec8e7d8038 --- /dev/null +++ b/tests/@angular_devkit/core/json/schema/serializers/1.0.javascript_spec.ts @@ -0,0 +1,64 @@ +/** + * @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 +import { schema } from '@angular-devkit/core'; + +const { serializers } = schema; + + +export function works(registry: schema.JsonSchemaRegistry, schema: any) { + const value = { + 'requiredKey': 1, + }; + + registry.addSchema('', schema); + const v = (new serializers.JavascriptSerializer()).serialize('', registry)(value); + + expect(v.requiredKey).toBe(1); + expect(() => delete v.requiredKey).toThrow(); + + expect(v.stringKeyDefault).toBe('defaultValue'); + v.stringKeyDefault = 'hello'; + expect(v.stringKeyDefault).toBe('hello'); + delete v.stringKeyDefault; + expect(v.stringKeyDefault).toBe('defaultValue'); + + expect(v.objectKey1).toBe(undefined); + v.objectKey1 = { stringKey: 'str' }; + expect(v.objectKey1.stringKey).toBe('str'); + + expect(v.objectKey1.objectKey).toBe(undefined); + v.objectKey1.objectKey = { stringKey: 'str2' }; + expect(v.objectKey1.objectKey.stringKey).toBe('str2'); + + expect(Object.keys(v.objectKey1)).toEqual(['stringKey', 'objectKey']); +} + + +export function accessUndefined(registry: schema.JsonSchemaRegistry, schema: any) { + const value = { + 'requiredKey': 1, + }; + + registry.addSchema('', schema); + const v = (new serializers.JavascriptSerializer({ + allowAccessUndefinedObjects: true, + })).serialize('', registry)(value); + + // Access an undefined property. + v.objectKey1.stringKey = 'hello'; + expect(v.objectKey1).not.toBe(undefined); + expect(v.objectKey1.stringKey).toBe('hello'); + expect(v.objectKey1.numberKey).toBe(undefined); + expect(v).toEqual({ 'requiredKey': 1, 'objectKey1': { 'stringKey': 'hello' } }); + v.objectKey1.stringKey = undefined; + expect(v).toEqual({ 'requiredKey': 1 }); + + expect(v.stringKeyDefault).toBe('defaultValue'); + expect(v.objectKey1.stringKeyDefault).toBe('defaultValue2'); +} diff --git a/tests/@angular_devkit/core/json/schema/serializers/1.schema.json b/tests/@angular_devkit/core/json/schema/serializers/1.schema.json new file mode 100644 index 0000000000..668a5764ad --- /dev/null +++ b/tests/@angular_devkit/core/json/schema/serializers/1.schema.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "JsonSchema", + "type": "object", + "properties": { + "requiredKey": { + "type": "number" + }, + "stringKeyDefault": { + "type": "string", + "default": "defaultValue" + }, + "stringKey": { + "type": "string" + }, + "booleanKey": { + "type": "boolean" + }, + "numberKey": { + "type": "number" + }, + "objectKey1": { + "type": "object", + "properties": { + "stringKey": { + "type": "string" + }, + "stringKeyDefault": { + "type": "string", + "default": "defaultValue2" + }, + "objectKey": { + "type": "object", + "properties": { + "stringKey": { + "type": "string" + } + } + } + } + } + }, + "required": ["requiredKey"] +} diff --git a/tests/@angular_devkit/core/json/schema/serializers/schema_benchmark.json b/tests/@angular_devkit/core/json/schema/serializers/schema_benchmark.json new file mode 100644 index 0000000000..af5c8d521e --- /dev/null +++ b/tests/@angular_devkit/core/json/schema/serializers/schema_benchmark.json @@ -0,0 +1,599 @@ +{ + "$schema": "http://json-schema.org/schema", + "id": "https://github.com/angular/angular-cli/blob/master/packages/@angular/cli/lib/config/schema.json#CliConfig", + "title": "Angular CLI Config Schema", + "type": "object", + "properties": { + "$schema": { + "type": "string" + }, + "project": { + "description": "The global configuration of the project.", + "type": "object", + "properties": { + "name": { + "description": "The name of the project.", + "type": "string" + }, + "ejected": { + "description": "Whether or not this project was ejected.", + "type": "boolean", + "default": false + } + }, + "additionalProperties": false + }, + "apps": { + "description": "Properties of the different applications in this project.", + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the app." + }, + "root": { + "type": "string", + "description": "The root directory of the app." + }, + "outDir": { + "type": "string", + "default": "dist/", + "description": "The output directory for build results." + }, + "assets": { + "type": "array", + "description": "List of application assets.", + "items": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "glob": { + "type": "string", + "default": "", + "description": "The pattern to match." + }, + "input": { + "type": "string", + "default": "", + "description": "The dir to search within." + }, + "output": { + "type": "string", + "default": "", + "description": "The output path (relative to the outDir)." + } + }, + "additionalProperties": false + } + ] + }, + "default": [] + }, + "deployUrl": { + "type": "string", + "description": "URL where files will be deployed." + }, + "baseHref": { + "type": "string", + "description": "Base url for the application being built." + }, + "platform": { + "type": "string", + "enum": ["browser", "server"], + "default": "browser", + "description": "The runtime platform of the app." + }, + "index": { + "type": "string", + "default": "index.html", + "description": "The name of the start HTML file." + }, + "main": { + "type": "string", + "description": "The name of the main entry-point file." + }, + "polyfills": { + "type": "string", + "description": "The name of the polyfills file." + }, + "test": { + "type": "string", + "description": "The name of the test entry-point file." + }, + "tsconfig": { + "type": "string", + "default": "tsconfig.app.json", + "description": "The name of the TypeScript configuration file." + }, + "testTsconfig": { + "type": "string", + "description": "The name of the TypeScript configuration file for unit tests." + }, + "prefix": { + "type": "string", + "description": "The prefix to apply to generated selectors." + }, + "serviceWorker": { + "description": "Experimental support for a service worker from @angular/service-worker.", + "type": "boolean", + "default": false + }, + "styles": { + "description": "Global styles to be included in the build.", + "type": "array", + "items": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "input": { + "type": "string" + } + }, + "additionalProperties": true + } + ] + }, + "additionalProperties": false + }, + "stylePreprocessorOptions": { + "description": "Options to pass to style preprocessors", + "type": "object", + "properties": { + "includePaths": { + "description": "Paths to include. Paths will be resolved to project root.", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + } + }, + "additionalProperties": false + }, + "scripts": { + "description": "Global scripts to be included in the build.", + "type": "array", + "items": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "input": { + "type": "string" + } + }, + "additionalProperties": true, + "required": [ + "input" + ] + } + ] + }, + "additionalProperties": false + }, + "environmentSource":{ + "description": "Source file for environment config.", + "type": "string" + }, + "environments": { + "description": "Name and corresponding file for environment config.", + "type": "object", + "additionalProperties": true + } + }, + "additionalProperties": false + }, + "additionalProperties": false + }, + "e2e": { + "type": "object", + "description": "Configuration for end-to-end tests.", + "properties": { + "protractor": { + "type": "object", + "properties": { + "config": { + "description": "Path to the config file.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "lint": { + "description": "Properties to be passed to TSLint.", + "type": "array", + "items": { + "type": "object", + "properties": { + "files": { + "description": "File glob(s) to lint.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ], + "default": [] + }, + "project": { + "description": "Location of the tsconfig.json project file. Will also use as files to lint if 'files' property not present.", + "type": "string" + }, + "tslintConfig": { + "description": "Location of the tslint.json configuration.", + "type": "string" + }, + "exclude": { + "description": "File glob(s) to ignore.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ], + "default": [] + } + }, + "required": [ + "project" + ], + "additionalProperties": false + } + }, + "test": { + "description": "Configuration for unit tests.", + "type": "object", + "properties": { + "karma": { + "type": "object", + "properties": { + "config": { + "description": "Path to the karma config file.", + "type": "string" + } + }, + "additionalProperties": false + }, + "codeCoverage": { + "type": "object", + "properties": { + "exclude": { + "description": "Globs to exclude from code coverage.", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "defaults": { + "description": "Specify the default values for generating.", + "type": "object", + "properties": { + "styleExt": { + "description": "The file extension to be used for style files.", + "type": "string" + }, + "poll": { + "description": "How often to check for file updates.", + "type": "number" + }, + "lintFix": { + "description": "Use lint to fix files after generation", + "type": "boolean", + "default": false + }, + "class": { + "description": "Options for generating a class.", + "type": "object", + "properties": { + "spec": { + "description": "Specifies if a spec file is generated.", + "type": "boolean", + "default": false + } + } + }, + "component": { + "description": "Options for generating a component.", + "type": "object", + "properties": { + "flat": { + "description": "Flag to indicate if a dir is created.", + "type": "boolean", + "default": false + }, + "spec": { + "description": "Specifies if a spec file is generated.", + "type": "boolean", + "default": true + }, + "inlineStyle": { + "description": "Specifies if the style will be in the ts file.", + "type": "boolean", + "default": false + }, + "inlineTemplate": { + "description": "Specifies if the template will be in the ts file.", + "type": "boolean", + "default": false + }, + "viewEncapsulation": { + "description": "Specifies the view encapsulation strategy.", + "enum": ["Emulated", "Native", "None"], + "type": "string" + }, + "changeDetection": { + "description": "Specifies the change detection strategy.", + "enum": ["Default", "OnPush"], + "type": "string" + } + } + }, + "directive": { + "description": "Options for generating a directive.", + "type": "object", + "properties": { + "flat": { + "description": "Flag to indicate if a dir is created.", + "type": "boolean", + "default": true + }, + "spec": { + "description": "Specifies if a spec file is generated.", + "type": "boolean", + "default": true + } + } + }, + "guard": { + "description": "Options for generating a guard.", + "type": "object", + "properties": { + "flat": { + "description": "Flag to indicate if a dir is created.", + "type": "boolean", + "default": true + }, + "spec": { + "description": "Specifies if a spec file is generated.", + "type": "boolean", + "default": true + } + } + }, + "interface": { + "description": "Options for generating an interface.", + "type": "object", + "properties": { + "prefix": { + "description": "Prefix to apply to interface names. (i.e. I)", + "type": "string", + "default": "" + } + } + }, + "module": { + "description": "Options for generating a module.", + "type": "object", + "properties": { + "flat": { + "description": "Flag to indicate if a dir is created.", + "type": "boolean", + "default": false + }, + "spec": { + "description": "Specifies if a spec file is generated.", + "type": "boolean", + "default": false + } + } + }, + "pipe": { + "description": "Options for generating a pipe.", + "type": "object", + "properties": { + "flat": { + "description": "Flag to indicate if a dir is created.", + "type": "boolean", + "default": true + }, + "spec": { + "description": "Specifies if a spec file is generated.", + "type": "boolean", + "default": true + } + } + }, + "service": { + "description": "Options for generating a service.", + "type": "object", + "properties": { + "flat": { + "description": "Flag to indicate if a dir is created.", + "type": "boolean", + "default": true + }, + "spec": { + "description": "Specifies if a spec file is generated.", + "type": "boolean", + "default": true + } + } + }, + "build": { + "description": "Properties to be passed to the build command.", + "type": "object", + "properties": { + "sourcemaps": { + "description": "Output sourcemaps.", + "type": "boolean" + }, + "baseHref": { + "description": "Base url for the application being built.", + "type": "string" + }, + "progress": { + "description": "The ssl key used by the server.", + "type": "boolean", + "default": true + }, + "poll": { + "description": "Enable and define the file watching poll time period (milliseconds).", + "type": "number" + }, + "deleteOutputPath": { + "description": "Delete output path before build.", + "type": "boolean", + "default": true + }, + "preserveSymlinks": { + "description": "Do not use the real path when resolving modules.", + "type": "boolean", + "default": false + }, + "showCircularDependencies": { + "description": "Show circular dependency warnings on builds.", + "type": "boolean", + "default": true + }, + "commonChunk": { + "description": "Use a separate bundle containing code used across multiple bundles.", + "type": "boolean", + "default": true + }, + "namedChunks": { + "description": "Use file name for lazy loaded chunks.", + "type": "boolean" + } + } + }, + "serve": { + "description": "Properties to be passed to the serve command.", + "type": "object", + "properties": { + "port": { + "description": "The port the application will be served on.", + "type": "number", + "default": 4200 + }, + "host": { + "description": "The host the application will be served on.", + "type": "string", + "default": "localhost" + + }, + "ssl": { + "description": "Enables ssl for the application.", + "type": "boolean", + "default": false + + }, + "sslKey": { + "description": "The ssl key used by the server.", + "type": "string", + "default": "ssl/server.key" + + }, + "sslCert": { + "description": "The ssl certificate used by the server.", + "type": "string", + "default": "ssl/server.crt" + }, + "proxyConfig": { + "description": "Proxy configuration file.", + "type": "string" + } + } + }, + "schematics": { + "description": "Properties about schematics.", + "type": "object", + "properties": { + "collection": { + "description": "The schematics collection to use.", + "type": "string", + "default": "@schematics/angular" + }, + "newApp": { + "description": "The new app schematic.", + "type": "string", + "default": "application" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "packageManager": { + "description": "Specify which package manager tool to use.", + "enum": [ "npm", "cnpm", "yarn", "default" ], + "default": "default", + "type": "string" + }, + "warnings": { + "description": "Allow people to disable console warnings.", + "type": "object", + "properties": { + "hmrWarning": { + "description": "Show a warning when the user enabled the --hmr option.", + "type": "boolean", + "default": true + }, + "nodeDeprecation": { + "description": "Show a warning when the node version is incompatible.", + "type": "boolean", + "default": true + }, + "packageDeprecation": { + "description": "Show a warning when the user installed angular-cli.", + "type": "boolean", + "default": true + }, + "versionMismatch": { + "description": "Show a warning when the global version is newer than the local one.", + "type": "boolean", + "default": true + }, + "typescriptMismatch": { + "description": "Show a warning when the TypeScript version is incompatible", + "type": "boolean", + "default": true + } + } + } + }, + "additionalProperties": false +} From 850c540e757c7d732aa1d2f39dfed7b542ecc6ec Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Thu, 21 Sep 2017 15:26:03 -0700 Subject: [PATCH 07/33] feat(@angular-devkit/schematics): use the new json-schema library In the default schematics CLI we now use the core json-schema library. Options that have invalid type, or are not defined, or are required will now output a valid error message that is closer to the actual error. The next step is to properly log exceptions to the user so theyll know what action to take. --- .../schematics/bin/schematics.ts | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/packages/angular_devkit/schematics/bin/schematics.ts b/packages/angular_devkit/schematics/bin/schematics.ts index 53bab5223e..bf9b83b66b 100644 --- a/packages/angular_devkit/schematics/bin/schematics.ts +++ b/packages/angular_devkit/schematics/bin/schematics.ts @@ -6,7 +6,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 { createLogger } from '@angular-devkit/core'; +import { createLogger, schema } from '@angular-devkit/core'; import { DryRunEvent, DryRunSink, @@ -20,7 +20,6 @@ import { FileSystemSchematicDesc, NodeModulesEngineHost, } from '@angular-devkit/schematics/tools'; -import { SchemaClassFactory } from '@ngtools/json-schema'; import * as minimist from 'minimist'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/ignoreElements'; @@ -110,13 +109,18 @@ const { const engineHost = new NodeModulesEngineHost(); const engine = new SchematicEngine(engineHost); +const schemaRegistry = new schema.JsonSchemaRegistry(); + // Add support for schemaJson. engineHost.registerOptionsTransform((schematic: FileSystemSchematicDesc, options: {}) => { if (schematic.schema && schematic.schemaJson) { - const SchemaMetaClass = SchemaClassFactory<{}>(schematic.schemaJson); - const schemaClass = new SchemaMetaClass(options); + const schemaJson = schematic.schemaJson as schema.JsonSchemaObject; + const ref = schemaJson.$id || ('/' + schematic.collection.name + '/' + schematic.name); + schemaRegistry.addSchema(ref, schemaJson); + const serializer = new schema.serializers.JavascriptSerializer(); + const fn = serializer.serialize(ref, schemaRegistry); - return schemaClass.$$root(); + return fn(options); } return options; @@ -191,6 +195,16 @@ dryRunSink.reporter.subscribe((event: DryRunEvent) => { }); +/** + * Remove every options from argv that we support in schematics. + */ +const args = argv; +delete args._; +for (const a of [ 'dry-run', 'force', 'help', 'list-schematics', 'verbose' ]) { + delete args[a]; +} + + /** * The main path. Call the schematic with the host. This creates a new Context for the schematic * to run in, then call the schematic rule using the input Tree. This returns a new Tree as if @@ -203,7 +217,7 @@ dryRunSink.reporter.subscribe((event: DryRunEvent) => { * Then we proceed to run the dryRun commit. We run this before we then commit to the filesystem * (if --dry-run was not passed or an error was detected by dryRun). */ -schematic.call(argv, host) +schematic.call(args, host) .map((tree: Tree) => Tree.optimize(tree)) .concatMap((tree: Tree) => { return dryRunSink.commit(tree).ignoreElements().concat(Observable.of(tree)); From d05fd2952e33a5529261496a4c8d8c49eb5e1daa Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Thu, 21 Sep 2017 15:35:30 -0700 Subject: [PATCH 08/33] feat(@angular-devkit/schematics): support -- args Passing -- on the command line will continue parsing arguments after the --, but does not distinguish with arguments that are supported by schematics directly. e.g. "schematics test --arg1=abc -- --dry-run=hello" will have two arguments [ args, dry-run ]. If the -- was not passed in dry-run would be removed from the options to the schematics. --- .../schematics/bin/schematics.ts | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/angular_devkit/schematics/bin/schematics.ts b/packages/angular_devkit/schematics/bin/schematics.ts index bf9b83b66b..db7491cdbe 100644 --- a/packages/angular_devkit/schematics/bin/schematics.ts +++ b/packages/angular_devkit/schematics/bin/schematics.ts @@ -85,9 +85,12 @@ function parseSchematicName(str: string | null): { collection: string, schematic /** Parse the command line. */ +const booleanArgs = [ 'dry-run', 'force', 'help', 'list-schematics', 'verbose' ]; const argv = minimist(process.argv.slice(2), { - boolean: [ 'dry-run', 'force', 'help', 'list-schematics', 'verbose' ], + boolean: booleanArgs, + '--': true, }); + /** Create the DevKit Logger used through the CLI. */ const logger = createLogger(argv['verbose']); @@ -196,14 +199,23 @@ dryRunSink.reporter.subscribe((event: DryRunEvent) => { /** - * Remove every options from argv that we support in schematics. + * Remove every options from argv that we support in schematics itself. */ -const args = argv; -delete args._; -for (const a of [ 'dry-run', 'force', 'help', 'list-schematics', 'verbose' ]) { - delete args[a]; +const args = Object.assign({}, argv); +delete args['--']; +for (const key of booleanArgs) { + delete args[key]; } +/** + * Add options from `--` to args. + */ +const argv2 = minimist(argv['--']); +for (const key of Object.keys(argv2)) { + args[key] = argv2[key]; +} +delete args._; + /** * The main path. Call the schematic with the host. This creates a new Context for the schematic From 785436d2fc2003738e28a25718cc35f1002e104e Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Thu, 21 Sep 2017 15:40:00 -0700 Subject: [PATCH 09/33] fix(@angular-devkit/schematics): transform options as part of observable Moving this in the observable has the nice property that errors are propagated through the observable, and not thrown up in the call(). --- packages/angular_devkit/schematics/src/engine/schematic.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/angular_devkit/schematics/src/engine/schematic.ts b/packages/angular_devkit/schematics/src/engine/schematic.ts index e088eb1fe7..432bb8a1c5 100644 --- a/packages/angular_devkit/schematics/src/engine/schematic.ts +++ b/packages/angular_devkit/schematics/src/engine/schematic.ts @@ -48,9 +48,10 @@ export class SchematicImpl { + const transformedOptions = this._engine.transformOptions(this, options); + const result = this._factory(transformedOptions)(tree, context); if (result instanceof Observable) { return result; From 12b616ee9e12f49ab41f63baf37e201d2972a5a6 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Thu, 21 Sep 2017 16:00:12 -0700 Subject: [PATCH 10/33] fix(@angular-devkit/core): fix node 6 support Node 6 does not support "..." which was being used in our templates. This removes the need to support it. --- .../templates/javascript/prop-array.ejs | 26 ++++++------- .../templates/javascript/prop-object.ejs | 38 +++++++++---------- .../serializers/templates/javascript/root.ejs | 6 +-- .../templates/javascript/subschema.ejs | 16 ++++---- 4 files changed, 39 insertions(+), 47 deletions(-) diff --git a/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-array.ejs b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-array.ejs index b862287837..18acac6729 100644 --- a/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-array.ejs +++ b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-array.ejs @@ -9,29 +9,28 @@ const required = (schema.required || []); const extras = { - exceptions, - options, - path, - strings, - symbols, - templates, + exceptions: exceptions, + options: options, + path: path, + strings: strings, + symbols: symbols, + templates: templates, }; %> -const itemHandler = () => (<%= - templates.subschema({ +const itemHandler = function() { return (<%= + templates.subschema(Object.assign({ name: '???', required: false, schema: schema.additionalProperties, - ...extras, - }) -%>); + }, extras)) +%>); }; const items = []; const arrayFunctions = { get length() { return items.length; }, - push(...x) { items.push(...x); }, + push() { items.push.apply(items, arguments); }, pop() { return items.pop(); }, shift() { return items.shift(); }, unshift() { return items.unshift(); }, @@ -49,9 +48,6 @@ const proxy = new Proxy({}, { if (prop === symbols.Schema) { return arrayHandler.schema; } - if (prop === symbols.Serialize) { - return () => arrayHandler.serialize(); - } if (prop >= 0 && prop in value) { return value[prop].get(); diff --git a/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-object.ejs b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-object.ejs index bc9e159f8f..8242cc1fa5 100644 --- a/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-object.ejs +++ b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-object.ejs @@ -2,24 +2,23 @@ <% const required = (schema.required || []); const extras = { - exceptions, - options, - path, - strings, - symbols, - templates, + exceptions: exceptions, + options: options, + path: path, + strings: strings, + symbols: symbols, + templates: templates, }; %> const additionalProperties = {}; const additionalPropertyHandler = <% -if (!('additionalProperties' in schema)) { %>null<% } else { %> () => (<%= - templates.subschema({ +if (!('additionalProperties' in schema)) { %>null<% } else { %> function() { return (<%= + templates.subschema(Object.assign({ name: '???', required: false, schema: schema.additionalProperties, - ...extras, - }) %>)<% + }, extras)) %>); }<% } %>; @@ -29,12 +28,12 @@ const handlers = Object.create(null); const _key = JSON.stringify(propName); const _name = propName.match(/^[_a-zA-Z][_a-zA-Z0-9]*$/) ? propName : `[${_key}]`; %>handlers[<%= JSON.stringify(_name) %>] = <%= - templates.subschema({ + templates.subschema(Object.assign({ name: _name, required: required.indexOf(propName) != -1, schema: schema.properties[propName], - ...extras, -}) %><% + }, extras)) +%><% } %> @@ -52,9 +51,6 @@ const proxy = new Proxy({}, { if (prop === symbols.Schema) { return objectHandler.schema; } - if (prop === symbols.Serialize) { - return () => objectHandler.serialize(); - } if (prop in handlers) { return handlers[prop].get(); } @@ -98,8 +94,8 @@ const proxy = new Proxy({}, { }, ownKeys(target) { return [].concat( - Object.keys(handlers).filter(key => handlers[key].isDefined()), - additionalPropertyHandler ? Object.keys(additionalProperties) : [], + Object.keys(handlers).filter(function(key) { return handlers[key].isDefined(); }), + additionalPropertyHandler ? Object.keys(additionalProperties) : [] ); }, }); @@ -131,8 +127,10 @@ const objectHandler = { }, isDefined() { return defined - && (Object.keys(handlers).some(x => handlers[x].isDefined()) - || Object.keys(additionalProperties).some(x => additionalProperties[x].isDefined())); + && (Object.keys(handlers).some(function(x) { return handlers[x].isDefined(); }) + || Object.keys(additionalProperties).some(function(x) { + return additionalProperties[x].isDefined(); + })); }, remove() { this.set(undefined); }, schema: <%= JSON.stringify(schema) %>, diff --git a/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/root.ejs b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/root.ejs index 11fe8ac330..2e3f4f230d 100644 --- a/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/root.ejs +++ b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/root.ejs @@ -8,13 +8,13 @@ const extras = { }; %> -const holder = <%= templates.subschema({ +const holder = <%= templates.subschema(Object.assign({ name: name, path: '', required: false, schema: schema, - ...extras, -}) %>; +}, extras)) +%>; holder.set(value); return holder.get(); diff --git a/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/subschema.ejs b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/subschema.ejs index 074f779fff..d1b1d2878d 100644 --- a/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/subschema.ejs +++ b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/subschema.ejs @@ -12,29 +12,27 @@ if (!schema) { %>null<% } else if (schema === true) { %><%= - templates.prop_any({ + templates.prop_any(Object.assign({ name, required, schema: {}, - ...extras, - }) + }, extras)) %><% } else if (!('type' in schema)) { %><%= - templates.prop_any({ + templates.prop_any(Object.assign({ name, required, schema: {}, - ...extras, -}) + }, extras)) %><% } else { %><%= - templates['prop_' + schema.type]({ + templates['prop_' + schema.type](Object.assign({ name: name, required, schema: schema, - ...extras, -}) %><% + }, extras)) +%><% } %> From f7580bfd658242e141b9c1d61bf7d96c6efb8f62 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Thu, 21 Sep 2017 16:00:58 -0700 Subject: [PATCH 11/33] feat(@angular-devkit/schematics): descriptive error Add a better description when the error is "RequiredValueMissing". --- packages/angular_devkit/core/src/json/schema/index.ts | 6 ++++-- .../core/src/json/schema/serializers/javascript.ts | 2 +- packages/angular_devkit/schematics/bin/schematics.ts | 7 ++++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/angular_devkit/core/src/json/schema/index.ts b/packages/angular_devkit/core/src/json/schema/index.ts index 28983353e5..b0120254d0 100644 --- a/packages/angular_devkit/core/src/json/schema/index.ts +++ b/packages/angular_devkit/core/src/json/schema/index.ts @@ -5,12 +5,14 @@ * 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 { JavascriptSerializer } from './serializers/javascript'; +import * as javascript from './serializers/javascript'; export * from './registry'; export * from './schema'; +export { javascript }; + export const serializers = { - JavascriptSerializer, + JavascriptSerializer: javascript.JavascriptSerializer, }; diff --git a/packages/angular_devkit/core/src/json/schema/serializers/javascript.ts b/packages/angular_devkit/core/src/json/schema/serializers/javascript.ts index 60323bee3b..53769cea4d 100644 --- a/packages/angular_devkit/core/src/json/schema/serializers/javascript.ts +++ b/packages/angular_devkit/core/src/json/schema/serializers/javascript.ts @@ -42,7 +42,7 @@ export class RequiredValueMissingException extends BaseException { } -const exceptions = { +export const exceptions = { InvalidRangeException, InvalidSchemaException, InvalidValueException, diff --git a/packages/angular_devkit/schematics/bin/schematics.ts b/packages/angular_devkit/schematics/bin/schematics.ts index db7491cdbe..ab23001d33 100644 --- a/packages/angular_devkit/schematics/bin/schematics.ts +++ b/packages/angular_devkit/schematics/bin/schematics.ts @@ -248,7 +248,12 @@ schematic.call(args, host) }) .subscribe({ error(err: Error) { - logger.fatal(err.toString()); + // Add extra processing to output better error messages. + if (err instanceof schema.javascript.RequiredValueMissingException) { + logger.fatal('Missing argument on the command line: ' + err.path.split('/').pop()); + } else { + logger.fatal(err.message); + } process.exit(1); }, }); From 59b9f5ea02dcc24958e7a9eedaa89be095a902d4 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Thu, 21 Sep 2017 16:08:34 -0700 Subject: [PATCH 12/33] feat(@angular-devkit/schematics): descriptive error Add a better description when the error is "InvalidPropertyNameException". --- packages/angular_devkit/schematics/bin/schematics.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/angular_devkit/schematics/bin/schematics.ts b/packages/angular_devkit/schematics/bin/schematics.ts index ab23001d33..81e840e100 100644 --- a/packages/angular_devkit/schematics/bin/schematics.ts +++ b/packages/angular_devkit/schematics/bin/schematics.ts @@ -251,6 +251,8 @@ schematic.call(args, host) // Add extra processing to output better error messages. if (err instanceof schema.javascript.RequiredValueMissingException) { logger.fatal('Missing argument on the command line: ' + err.path.split('/').pop()); + } else if (err instanceof schema.javascript.InvalidPropertyNameException) { + logger.fatal('A non-supported argument was passed: ' + err.path.split('/').pop()); } else { logger.fatal(err.message); } From 9a0e46e589cd12b06fb42454cfffa90dc70e1642 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Fri, 22 Sep 2017 17:32:40 -0700 Subject: [PATCH 13/33] feat(@angular-devkit/core): add a generic node resolver This is inspired by the resolve package on npm, but with a few modifications: - it is in typescript - it has less options and is more straightforward - it can fallback to checking the global node registry if it doesnt find the package Also added the package angular_devkit/core/node since those utilities are node specific, but the code tries to be platform agnostic. --- packages/angular_devkit/core/BUILD | 19 +- packages/angular_devkit/core/node/index.ts | 9 + packages/angular_devkit/core/node/resolve.ts | 226 ++++++++++++++++++ .../angular_devkit/core/node/resolve_spec.ts | 25 ++ tsconfig.json | 1 + 5 files changed, 278 insertions(+), 2 deletions(-) create mode 100644 packages/angular_devkit/core/node/index.ts create mode 100644 packages/angular_devkit/core/node/resolve.ts create mode 100644 packages/angular_devkit/core/node/resolve_spec.ts diff --git a/packages/angular_devkit/core/BUILD b/packages/angular_devkit/core/BUILD index e388db964b..c1dd19cca8 100644 --- a/packages/angular_devkit/core/BUILD +++ b/packages/angular_devkit/core/BUILD @@ -10,8 +10,8 @@ licenses(["notice"]) # MIT License ts_library( name = "core", srcs = glob( - include = ["**/*.ts"], - exclude = ["**/*_spec.ts", "**/*_benchmark.ts"], + include = ["src/**/*.ts"], + exclude = ["src/**/*_spec.ts", "src/**/*_benchmark.ts"], ), deps = [ # @deps: rxjs @@ -21,6 +21,20 @@ ts_library( module_root = "src" ) +ts_library( + name = "node", + srcs = glob( + include = ["node/**/*.ts"], + exclude = ["node/**/*_spec.ts", "tools/**/*_benchmark.ts"], + ), + deps = [ + "//packages/angular_devkit/core", + ], + tsconfig = "//:tsconfig.json", + module_name = "@angular-devkit/core/node", + module_root = "node" +) + ts_library( name = "spec", srcs = glob( @@ -28,6 +42,7 @@ ts_library( ), deps = [ ":core", + ":node", # @deps: rxjs # @typings: jasmine ], diff --git a/packages/angular_devkit/core/node/index.ts b/packages/angular_devkit/core/node/index.ts new file mode 100644 index 0000000000..40a74cc2be --- /dev/null +++ b/packages/angular_devkit/core/node/index.ts @@ -0,0 +1,9 @@ +/** + * @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 + */ + +export * from './resolve'; diff --git a/packages/angular_devkit/core/node/resolve.ts b/packages/angular_devkit/core/node/resolve.ts new file mode 100644 index 0000000000..78f9dbce92 --- /dev/null +++ b/packages/angular_devkit/core/node/resolve.ts @@ -0,0 +1,226 @@ +/** + * @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 { BaseException } from '@angular-devkit/core'; +import * as fs from 'fs'; +import * as path from 'path'; + + +export class ModuleNotFoundException extends BaseException { + public code: string; + + constructor(public readonly moduleName: string, public readonly basePath: string) { + super(`Could not find module ${JSON.stringify(moduleName)} from ${JSON.stringify(basePath)}.`); + this.code = 'MODULE_NOT_FOUND'; + } +} + + +function _caller() { + // see https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi + const error = Error as {} as { prepareStackTrace: (x: {}, stack: {}) => {} }; + const origPrepareStackTrace = error.prepareStackTrace; + error.prepareStackTrace = (_, stack) => stack; + const stack = (new Error()).stack as {}[] | undefined as { getFileName(): string }[] | undefined; + error.prepareStackTrace = origPrepareStackTrace; + + return stack ? stack[2].getFileName() : ''; +} + + +function _isFile(filePath: string) { + let stat; + try { + stat = fs.statSync(filePath); + } catch (e) { + if (e && (e.code === 'ENOENT' || e.code === 'ENOTDIR')) { + return false; + } + throw e; + } + + return stat.isFile() || stat.isFIFO(); +} + + +/** + * Get the global directory for node_modules. This is based on NPM code itself, and may be subject + * to change, but is relatively stable. + * @returns {string} The path to node_modules itself. + * @private + */ +function _getGlobalNodeModules() { + let globalPrefix; + + if (process.env.PREFIX) { + globalPrefix = process.env.PREFIX; + } else if (process.platform === 'win32') { + // c:\node\node.exe --> prefix=c:\node\ + globalPrefix = path.dirname(process.execPath); + } else { + // /usr/local/bin/node --> prefix=/usr/local + globalPrefix = path.dirname(path.dirname(process.execPath)); + + // destdir only is respected on Unix + if (process.env.DESTDIR) { + globalPrefix = path.join(process.env.DESTDIR, globalPrefix); + } + } + + return (process.platform !== 'win32') + ? path.resolve(globalPrefix, 'lib', 'node_modules') + : path.resolve(globalPrefix, 'node_modules'); +} + + +export interface ResolveOptions { + extensions?: string[]; + basedir?: string; + paths?: string[]; + preserveSymlinks?: boolean; + checkGlobal?: boolean; +} + + +export function resolve(x: string, options: ResolveOptions = {}): string { + const readFileSync = fs.readFileSync; + + const extensions: string[] = options.extensions || Object.keys(require.extensions); + const basePath = options.basedir || path.dirname(_caller()); + + options.paths = options.paths || []; + + if (/^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[/\\])/.test(x)) { + let res = path.resolve(basePath, x); + if (x === '..' || x.slice(-1) === '/') { + res += '/'; + } + + const m = loadAsFileSync(res) || loadAsDirectorySync(res); + if (m) { + return m; + } + } else { + const n = loadNodeModulesSync(x, basePath); + if (n) { + return n; + } + } + + // Fallback to checking the global node modules. + if (options.checkGlobal) { + const globalDir = path.dirname(_getGlobalNodeModules()); + if (globalDir !== options.basedir) { + try { + return resolve(x, { + ...options, + checkGlobal: false, + basedir: globalDir, + }); + } catch (e) { + // Just swap the basePath with the original call one. + if (e instanceof ModuleNotFoundException) { + throw new ModuleNotFoundException(x, basePath); + } + throw e; + } + } + } + + throw new ModuleNotFoundException(x, basePath); + + function loadAsFileSync(x: string): string | null { + if (_isFile(x)) { + return x; + } + + return extensions.map(ex => x + ex).find(f => _isFile(f)) || null; + } + + function loadAsDirectorySync(x: string): string | null { + const pkgfile = path.join(x, 'package.json'); + if (_isFile(pkgfile)) { + try { + const body = readFileSync(pkgfile, 'UTF8'); + const pkg = JSON.parse(body); + + if (pkg['main']) { + if (pkg['main'] === '.' || pkg['main'] === './') { + pkg['main'] = 'index'; + } + + const m = loadAsFileSync(path.resolve(x, pkg['main'])); + if (m) { + return m; + } + const n = loadAsDirectorySync(path.resolve(x, pkg['main'])); + if (n) { + return n; + } + } + } catch (e) {} + } + + return loadAsFileSync(path.join(x, '/index')); + } + + function loadNodeModulesSync(x: string, start: string): string | null { + const dirs = nodeModulesPaths(start, options); + for (const dir of dirs) { + const m = loadAsFileSync(path.join(dir, '/', x)); + if (m) { + return m; + } + const n = loadAsDirectorySync(path.join(dir, '/', x)); + if (n) { + return n; + } + } + + return null; + } + + function nodeModulesPaths(start: string, opts: ResolveOptions) { + const modules = ['node_modules']; + + // ensure that `start` is an absolute path at this point, + // resolving against the process' current working directory + let absoluteStart = path.resolve(start); + + if (opts && opts.preserveSymlinks === false) { + try { + absoluteStart = fs.realpathSync(absoluteStart); + } catch (err) { + if (err.code !== 'ENOENT') { + throw err; + } + } + } + + let prefix = '/'; + if (/^([A-Za-z]:)/.test(absoluteStart)) { + prefix = ''; + } else if (/^\\\\/.test(absoluteStart)) { + prefix = '\\\\'; + } + + const paths = [absoluteStart]; + let parsed = path.parse(absoluteStart); + while (parsed.dir !== paths[paths.length - 1]) { + paths.push(parsed.dir); + parsed = path.parse(parsed.dir); + } + + const dirs = paths.reduce((dirs: string[], aPath: string) => { + return dirs.concat(modules.map(function (moduleDir) { + return path.join(prefix, aPath, moduleDir); + })); + }, []); + + return opts && opts.paths ? dirs.concat(opts.paths) : dirs; + } +} diff --git a/packages/angular_devkit/core/node/resolve_spec.ts b/packages/angular_devkit/core/node/resolve_spec.ts new file mode 100644 index 0000000000..b260aba377 --- /dev/null +++ b/packages/angular_devkit/core/node/resolve_spec.ts @@ -0,0 +1,25 @@ +/** + * @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 +import { resolve } from '@angular-devkit/core/node'; +import * as path from 'path'; + +const devKitRoot = (global as any)._DevKitRoot; + +describe('resolve', () => { + + it('works', () => { + expect(resolve('tslint', { basedir: __dirname })) + .toBe(path.join(devKitRoot, 'node_modules/tslint/lib/index.js')); + + expect(() => resolve('npm', { basedir: '/' })).toThrow(); + + expect(() => resolve('npm', { basedir: '/', checkGlobal: true })).not.toThrow(); + }); + +}); diff --git a/tsconfig.json b/tsconfig.json index 99bbd6a9c9..1d86951523 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -37,6 +37,7 @@ "paths": { "@_/benchmark": [ "./packages/_/benchmark/src/index" ], "@angular-devkit/core": [ "./packages/angular_devkit/core/src/index" ], + "@angular-devkit/core/node": [ "./packages/angular_devkit/core/node/index" ], "@angular-devkit/schematics": [ "./packages/angular_devkit/schematics/src/index" ], "@angular-devkit/schematics/tools": [ "./packages/angular_devkit/schematics/tools/index" ], "@angular-devkit/schematics/test": [ "./packages/angular_devkit/schematics/test/index" ], From ce908303b0ca00518ba93444abca0c5a1f609e6a Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Fri, 22 Sep 2017 17:51:16 -0700 Subject: [PATCH 14/33] feat(@angular-devkit/schematics): add test engine host This adds a few things: - NodeModulesTestEngineHost. This uses the same logic as NodeModulesEngineHost, but allow for overwriting some scheamtics with specific paths. This prevents the EngineHost from using node_modules, which doesnt contain the collection we are trying to test. - Remove RegistryEngineHost which is not used. - Add a new FallbackEngineHost, which can resolve collections and schematics against multiple hosts as a fallback pattenr. - Use the new NodeModulesTestEngineHost for SchematicsTestRunner. - Update all the tests to use the new stuff. - When resolving Url to Sources, knowing the context when creating the source is necessary. Changed that. - The NodeModulesEngineHost now uses the resolve() method from @angular-devkit/core/node. --- packages/angular_devkit/core/node/resolve.ts | 27 +++- packages/angular_devkit/schematics/BUILD | 1 + .../schematics/src/engine/engine.ts | 6 +- .../schematics/src/engine/interface.ts | 10 +- .../schematics/src/rules/url.ts | 2 +- .../schematics/test/schematic-test-runner.ts | 31 ++--- .../schematics/tools/fallback-engine-host.ts | 93 +++++++++++++ .../angular_devkit/schematics/tools/index.ts | 3 +- .../tools/node-module-engine-host.ts | 21 ++- .../tools/node-modules-test-engine-host.ts | 30 +++++ .../schematics/tools/registry-engine-host.ts | 125 ------------------ .../angular/application/index_spec.ts | 6 +- .../schematics/angular/class/index_spec.ts | 6 +- .../angular/component/index_spec.ts | 6 +- .../angular/directive/index_spec.ts | 6 +- .../schematics/angular/enum/index_spec.ts | 6 +- .../schematics/angular/guard/index_spec.ts | 6 +- .../angular/interface/index_spec.ts | 6 +- .../schematics/angular/module/index_spec.ts | 6 +- .../schematics/angular/pipe/index_spec.ts | 6 +- .../schematics/angular/service/index_spec.ts | 6 +- 21 files changed, 244 insertions(+), 165 deletions(-) create mode 100644 packages/angular_devkit/schematics/tools/fallback-engine-host.ts create mode 100644 packages/angular_devkit/schematics/tools/node-modules-test-engine-host.ts delete mode 100644 packages/angular_devkit/schematics/tools/registry-engine-host.ts diff --git a/packages/angular_devkit/core/node/resolve.ts b/packages/angular_devkit/core/node/resolve.ts index 78f9dbce92..82e744a2a5 100644 --- a/packages/angular_devkit/core/node/resolve.ts +++ b/packages/angular_devkit/core/node/resolve.ts @@ -83,6 +83,7 @@ export interface ResolveOptions { paths?: string[]; preserveSymlinks?: boolean; checkGlobal?: boolean; + checkLocal?: boolean; } @@ -111,6 +112,26 @@ export function resolve(x: string, options: ResolveOptions = {}): string { } } + // Fallback to checking the local (callee) node modules. + if (options.checkLocal) { + const localDir = path.dirname(_caller()); + if (localDir !== options.basedir) { + try { + return resolve(x, { + ...options, + checkLocal: false, + checkGlobal: false, + basedir: localDir, + }); + } catch (e) { + // Just swap the basePath with the original call one. + if (!(e instanceof ModuleNotFoundException)) { + throw e; + } + } + } + } + // Fallback to checking the global node modules. if (options.checkGlobal) { const globalDir = path.dirname(_getGlobalNodeModules()); @@ -118,15 +139,15 @@ export function resolve(x: string, options: ResolveOptions = {}): string { try { return resolve(x, { ...options, + checkLocal: false, checkGlobal: false, basedir: globalDir, }); } catch (e) { // Just swap the basePath with the original call one. - if (e instanceof ModuleNotFoundException) { - throw new ModuleNotFoundException(x, basePath); + if (!(e instanceof ModuleNotFoundException)) { + throw e; } - throw e; } } } diff --git a/packages/angular_devkit/schematics/BUILD b/packages/angular_devkit/schematics/BUILD index fd007e59dd..8293f30330 100644 --- a/packages/angular_devkit/schematics/BUILD +++ b/packages/angular_devkit/schematics/BUILD @@ -31,6 +31,7 @@ ts_library( deps = [ ":schematics", "//packages/angular_devkit/core", + "//packages/angular_devkit/core:node", # @deps: rxjs ], tsconfig = "//:tsconfig.json", diff --git a/packages/angular_devkit/schematics/src/engine/engine.ts b/packages/angular_devkit/schematics/src/engine/engine.ts index 1104f9be28..a4e0603491 100644 --- a/packages/angular_devkit/schematics/src/engine/engine.ts +++ b/packages/angular_devkit/schematics/src/engine/engine.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ import { BaseException } from '@angular-devkit/core'; -import { CollectionDescription } from '@angular-devkit/schematics'; +import { CollectionDescription, TypedSchematicContext } from '@angular-devkit/schematics'; import 'rxjs/add/operator/map'; import { Url } from 'url'; import { MergeStrategy } from '../tree/interface'; @@ -103,12 +103,12 @@ export class SchematicEngine): Source { switch (url.protocol) { case 'null:': return () => new NullTree(); case 'empty:': return () => empty(); default: - const hostSource = this._host.createSourceFromUrl(url); + const hostSource = this._host.createSourceFromUrl(url, context); if (!hostSource) { throw new UnknownUrlSourceProtocol(url.toString()); } diff --git a/packages/angular_devkit/schematics/src/engine/interface.ts b/packages/angular_devkit/schematics/src/engine/interface.ts index 33b52ed247..44ee8d8561 100644 --- a/packages/angular_devkit/schematics/src/engine/interface.ts +++ b/packages/angular_devkit/schematics/src/engine/interface.ts @@ -45,7 +45,10 @@ export interface EngineHost( schematic: SchematicDescription, collection: CollectionDescription): RuleFactory; - createSourceFromUrl(url: Url): Source | null; + createSourceFromUrl( + url: Url, + context: TypedSchematicContext, + ): Source | null; transformOptions( schematic: SchematicDescription, options: OptionT, @@ -72,7 +75,10 @@ export interface Engine, ): Schematic; - createSourceFromUrl(url: Url): Source; + createSourceFromUrl( + url: Url, + context: TypedSchematicContext, + ): Source; transformOptions( schematic: Schematic, options: OptionT, diff --git a/packages/angular_devkit/schematics/src/rules/url.ts b/packages/angular_devkit/schematics/src/rules/url.ts index 4c9208f8cd..027bf62a4f 100644 --- a/packages/angular_devkit/schematics/src/rules/url.ts +++ b/packages/angular_devkit/schematics/src/rules/url.ts @@ -12,5 +12,5 @@ import { SchematicContext, Source } from '../engine/interface'; export function url(urlString: string): Source { const url = parse(urlString); - return (context: SchematicContext) => context.engine.createSourceFromUrl(url)(context); + return (context: SchematicContext) => context.engine.createSourceFromUrl(url, context)(context); } diff --git a/packages/angular_devkit/schematics/test/schematic-test-runner.ts b/packages/angular_devkit/schematics/test/schematic-test-runner.ts index 47db389051..2e52d1b631 100644 --- a/packages/angular_devkit/schematics/test/schematic-test-runner.ts +++ b/packages/angular_devkit/schematics/test/schematic-test-runner.ts @@ -13,7 +13,7 @@ import { } from '@angular-devkit/schematics'; import { FileSystemSchematicDesc, - NodeModulesEngineHost, + NodeModulesTestEngineHost, } from '@angular-devkit/schematics/tools'; import { SchemaClassFactory } from '@ngtools/json-schema'; import { Observable } from 'rxjs/Observable'; @@ -22,19 +22,19 @@ import { Observable } from 'rxjs/Observable'; export interface SchematicSchemaT {} export class SchematicTestRunner { - private engineHost: NodeModulesEngineHost; - private engine: SchematicEngine<{}, {}>; - private collection: Collection<{}, {}>; + private _engineHost = new NodeModulesTestEngineHost(); + private _engine: SchematicEngine<{}, {}> = new SchematicEngine(this._engineHost); + private _collection: Collection<{}, {}>; - constructor(private collectionName: string) { - this.prepareCollection(); - } + constructor(private _collectionName: string, collectionPath: string) { + this._engineHost.registerCollection(_collectionName, collectionPath); + + this._engineHost.registerOptionsTransform(( + schematicDescription: {}, + opts: SchematicSchemaT, + ) => { + const schematic: FileSystemSchematicDesc = schematicDescription as FileSystemSchematicDesc; - private prepareCollection() { - this.engineHost = new NodeModulesEngineHost(); - this.engine = new SchematicEngine(this.engineHost); - this.engineHost.registerOptionsTransform(( - schematic: FileSystemSchematicDesc, opts: SchematicSchemaT) => { if (schematic.schema && schematic.schemaJson) { const SchemaMetaClass = SchemaClassFactory(schematic.schemaJson); const schemaClass = new SchemaMetaClass(opts); @@ -44,18 +44,19 @@ export class SchematicTestRunner { return opts; }); - this.collection = this.engine.createCollection(this.collectionName); + + this._collection = this._engine.createCollection(this._collectionName); } runSchematicAsync(schematicName: string, opts?: SchematicSchemaT, tree?: Tree): Observable { - const schematic = this.collection.createSchematic(schematicName); + const schematic = this._collection.createSchematic(schematicName); const host = Observable.of(tree || new VirtualTree); return schematic.call(opts || {}, host); } runSchematic(schematicName: string, opts?: SchematicSchemaT, tree?: Tree): Tree { - const schematic = this.collection.createSchematic(schematicName); + const schematic = this._collection.createSchematic(schematicName); let result: Tree | null = null; const host = Observable.of(tree || new VirtualTree); diff --git a/packages/angular_devkit/schematics/tools/fallback-engine-host.ts b/packages/angular_devkit/schematics/tools/fallback-engine-host.ts new file mode 100644 index 0000000000..95d5a9ff0c --- /dev/null +++ b/packages/angular_devkit/schematics/tools/fallback-engine-host.ts @@ -0,0 +1,93 @@ +/** + * @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 { + CollectionDescription, + EngineHost, + RuleFactory, + SchematicDescription, + Source, TypedSchematicContext, + UnknownCollectionException, +} from '@angular-devkit/schematics'; +import { Url } from 'url'; + + +export type FallbackCollectionDescription = { + host: EngineHost<{}, {}>; + description: CollectionDescription<{}>; +}; +export type FallbackSchematicDescription = { + description: SchematicDescription<{}, {}>; +}; +export declare type OptionTransform = ( + schematic: SchematicDescription, + options: T, +) => R; + + +/** + * An EngineHost that support multiple hosts in a fallback configuration. If a host does not + * have a collection/schematics, use the following host before giving up. + */ +export class FallbackEngineHost implements EngineHost<{}, {}> { + private _hosts: EngineHost<{}, {}>[] = []; + private _transforms: OptionTransform[] = []; + + constructor() {} + + addHost( + host: EngineHost, + ) { + this._hosts.push(host); + } + + registerOptionsTransform(t: OptionTransform) { + this._transforms.push(t); + } + + createCollectionDescription(name: string): CollectionDescription { + for (const host of this._hosts) { + try { + const description = host.createCollectionDescription(name); + + return { name, host, description }; + } catch (_) { + } + } + + throw new UnknownCollectionException(name); + } + + createSchematicDescription( + name: string, + collection: CollectionDescription, + ): SchematicDescription { + const description = collection.host.createSchematicDescription(name, collection.description); + + return { name, collection, description }; + } + + getSchematicRuleFactory( + schematic: SchematicDescription, + collection: CollectionDescription): RuleFactory { + return collection.host.getSchematicRuleFactory(schematic.description, collection.description); + } + + createSourceFromUrl( + url: Url, + context: TypedSchematicContext, + ): Source | null { + return context.schematic.collection.description.host.createSourceFromUrl(url, context); + } + + transformOptions( + schematic: SchematicDescription, + options: OptionT, + ): ResultT { + return this._transforms.reduce((acc: ResultT, t) => t(schematic, acc), options) as ResultT; + } +} diff --git a/packages/angular_devkit/schematics/tools/index.ts b/packages/angular_devkit/schematics/tools/index.ts index 1b5ec377d8..ec461d17e2 100644 --- a/packages/angular_devkit/schematics/tools/index.ts +++ b/packages/angular_devkit/schematics/tools/index.ts @@ -9,6 +9,7 @@ export * from './description'; export * from './file-system-host'; export * from './file-system-engine-host-base'; +export { FallbackEngineHost } from './fallback-engine-host'; export {FileSystemEngineHost} from './file-system-engine-host'; export {NodeModulesEngineHost} from './node-module-engine-host'; -export {RegistryEngineHost} from './registry-engine-host'; +export { NodeModulesTestEngineHost } from './node-modules-test-engine-host'; diff --git a/packages/angular_devkit/schematics/tools/node-module-engine-host.ts b/packages/angular_devkit/schematics/tools/node-module-engine-host.ts index 0fa58c6dba..8361f3c5db 100644 --- a/packages/angular_devkit/schematics/tools/node-module-engine-host.ts +++ b/packages/angular_devkit/schematics/tools/node-module-engine-host.ts @@ -5,13 +5,14 @@ * 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 { resolve } from '@angular-devkit/core/node'; import { RuleFactory } from '@angular-devkit/schematics'; import { CollectionCannotBeResolvedException, CollectionMissingSchematicsMapException, SchematicMissingFieldsException, } from '@angular-devkit/schematics/tools'; -import { join } from 'path'; +import { dirname, join } from 'path'; import { FileSystemCollectionDesc, FileSystemSchematicDesc, @@ -24,13 +25,25 @@ import { FileSystemEngineHostBase } from './file-system-engine-host-base'; * A simple EngineHost that uses NodeModules to resolve collections. */ export class NodeModulesEngineHost extends FileSystemEngineHostBase { + constructor() { super(); } + protected _resolveCollectionPath(name: string): string { - const pkgJsonSchematics = require(join(name, 'package.json'))['schematics']; + const packageJsonPath = resolve(join(name, 'package.json'), { + basedir: process.cwd(), + checkLocal: true, + checkGlobal: true, + }); + + const pkgJsonSchematics = require(packageJsonPath)['schematics']; if (!pkgJsonSchematics) { throw new CollectionCannotBeResolvedException(name); } - return require.resolve(join(name, pkgJsonSchematics)); + return resolve(join(dirname(packageJsonPath), pkgJsonSchematics), { + basedir: process.cwd(), + checkLocal: true, + checkGlobal: true, + }); } protected _resolveReferenceString(refString: string, parentPath: string) { @@ -49,12 +62,10 @@ export class NodeModulesEngineHost extends FileSystemEngineHostBase { if (!desc.schematics || typeof desc.schematics != 'object') { throw new CollectionMissingSchematicsMapException(name); } - const version = require(join(name, 'package.json'))['version']; return { ...desc, name, - version, } as FileSystemCollectionDesc; } diff --git a/packages/angular_devkit/schematics/tools/node-modules-test-engine-host.ts b/packages/angular_devkit/schematics/tools/node-modules-test-engine-host.ts new file mode 100644 index 0000000000..37f567467f --- /dev/null +++ b/packages/angular_devkit/schematics/tools/node-modules-test-engine-host.ts @@ -0,0 +1,30 @@ +/** + * @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 { NodeModulesEngineHost } from '@angular-devkit/schematics/tools'; + + +/** + * An EngineHost that uses a registry to super seed locations of collection.json files, but + * revert back to using node modules resolution. This is done for testing. + */ +export class NodeModulesTestEngineHost extends NodeModulesEngineHost { + private _collections = new Map(); + + registerCollection(name: string, path: string) { + this._collections.set(name, path); + } + + protected _resolveCollectionPath(name: string): string { + const maybePath = this._collections.get(name); + if (maybePath) { + return maybePath; + } + + return super._resolveCollectionPath(name); + } +} diff --git a/packages/angular_devkit/schematics/tools/registry-engine-host.ts b/packages/angular_devkit/schematics/tools/registry-engine-host.ts deleted file mode 100644 index a4bc4de5af..0000000000 --- a/packages/angular_devkit/schematics/tools/registry-engine-host.ts +++ /dev/null @@ -1,125 +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 { - CollectionDescription, - RuleFactory, - SchematicDescription, - UnknownCollectionException, -} from '@angular-devkit/schematics'; -import { - CollectionMissingSchematicsMapException, - SchematicMissingFieldsException, -} from '@angular-devkit/schematics/tools'; -import { existsSync, statSync } from 'fs'; -import { join } from 'path'; -import { FileSystemCollectionDescription, FileSystemSchematicDescription } from './description'; -import { ExportStringRef } from './export-ref'; -import { FileSystemEngineHostBase } from './file-system-engine-host-base'; -import { readJsonFile } from './file-system-utility'; - - -/** - * Used to simplify typings. - */ -export declare type FileSystemCollectionDesc - = CollectionDescription; -export declare type FileSystemSchematicDesc - = SchematicDescription; - - -/** - * A simple EngineHost that uses a registry of {name => path} to find collections. This can be - * useful for tooling that want to load generic collections from random places. - */ -export class RegistryEngineHost extends FileSystemEngineHostBase { - protected _registry = new Map(); - - registerPath(path: string) { - // Read the collection from the path. - - if (existsSync(path) && statSync(path).isFile()) { - // Allow path to be fully qualified to a JSON file. - } else if (existsSync(join(path, 'collection.json')) && statSync(path).isFile()) { - // Allow path to point to a directory containing a `collection.json`. - path = join(path, 'collection.json'); - } else { - throw new Error(`Invalid path: "${path}".`); - } - - const json: FileSystemCollectionDesc = readJsonFile(path) as FileSystemCollectionDesc; - if (!json) { - throw new Error(`Invalid path for collection: "${path}".`); - } - - // Validate that the name is not in the registry already (and that the registry does not - // contain this path under another name. - const name = json.name; - const maybePath = this._registry.get(name); - if (maybePath && maybePath != path) { - throw new Error(`Collection name "${name}" already registered.`); - } - for (const registryPath of this._registry.values()) { - if (registryPath == path) { - throw new Error(`Collection path "${path}" already registered under another name.`); - } - } - - this._registry.set(name, path); - } - - removePath(path: string) { - for (const [key, p] of this._registry.entries()) { - if (p == path) { - this._registry.delete(key); - } - } - } - - removeName(name: string) { - this._registry.delete(name); - } - - - protected _resolveCollectionPath(name: string): string { - const maybePath = this._registry.get(name); - if (!maybePath) { - throw new UnknownCollectionException(name); - } - - return maybePath; - } - - protected _resolveReferenceString(refString: string, parentPath: string) { - // Use the same kind of export strings as NodeModule. - const ref = new ExportStringRef>(refString, parentPath); - if (!ref.ref) { - return null; - } - - return { ref: ref.ref, path: ref.module }; - } - - protected _transformCollectionDescription(name: string, - desc: Partial) { - if (!desc.schematics || typeof desc.schematics != 'object') { - throw new CollectionMissingSchematicsMapException(name); - } - - return desc as FileSystemCollectionDesc; - } - - protected _transformSchematicDescription(name: string, - _collection: FileSystemCollectionDesc, - desc: Partial) { - if (!desc.factoryFn || !desc.path || !desc.description) { - throw new SchematicMissingFieldsException(name); - } - - return desc as FileSystemSchematicDesc; - } -} diff --git a/packages/schematics/angular/application/index_spec.ts b/packages/schematics/angular/application/index_spec.ts index 30db255c72..8700de2adf 100644 --- a/packages/schematics/angular/application/index_spec.ts +++ b/packages/schematics/angular/application/index_spec.ts @@ -7,12 +7,16 @@ */ import { Tree } from '@angular-devkit/schematics'; import { SchematicTestRunner } from '@angular-devkit/schematics/test'; +import * as path from 'path'; import { getFileContent } from '../utility/test'; import { Schema as AppSchema } from './schema'; describe('Application Schematic', () => { - const schematicRunner = new SchematicTestRunner('@schematics/angular'); + const schematicRunner = new SchematicTestRunner( + '@schematics/angular', + path.join(__dirname, '../collection.json'), + ); const defaultOptions: AppSchema = { directory: 'foo', name: 'foo', diff --git a/packages/schematics/angular/class/index_spec.ts b/packages/schematics/angular/class/index_spec.ts index f94b125b15..8474a9bfd2 100644 --- a/packages/schematics/angular/class/index_spec.ts +++ b/packages/schematics/angular/class/index_spec.ts @@ -6,12 +6,16 @@ * found in the LICENSE file at https://angular.io/license */ import { SchematicTestRunner } from '@angular-devkit/schematics/test'; +import * as path from 'path'; import { getFileContent } from '../utility/test'; import { Schema as ClassSchema } from './schema'; describe('Class Schematic', () => { - const schematicRunner = new SchematicTestRunner('@schematics/angular'); + const schematicRunner = new SchematicTestRunner( + '@schematics/angular', + path.join(__dirname, '../collection.json'), + ); const defaultOptions: ClassSchema = { name: 'foo', path: 'app', diff --git a/packages/schematics/angular/component/index_spec.ts b/packages/schematics/angular/component/index_spec.ts index ab3ad3d506..b305c7f0e0 100644 --- a/packages/schematics/angular/component/index_spec.ts +++ b/packages/schematics/angular/component/index_spec.ts @@ -7,12 +7,16 @@ */ import { Tree, VirtualTree } from '@angular-devkit/schematics'; import { SchematicTestRunner } from '@angular-devkit/schematics/test'; +import * as path from 'path'; import { createAppModule, getFileContent } from '../utility/test'; import { Schema as ComponentSchema } from './schema'; describe('Component Schematic', () => { - const schematicRunner = new SchematicTestRunner('@schematics/angular'); + const schematicRunner = new SchematicTestRunner( + '@schematics/angular', + path.join(__dirname, '../collection.json'), + ); const defaultOptions: ComponentSchema = { name: 'foo', path: 'app', diff --git a/packages/schematics/angular/directive/index_spec.ts b/packages/schematics/angular/directive/index_spec.ts index 5ca6e90eaa..743e9161ac 100644 --- a/packages/schematics/angular/directive/index_spec.ts +++ b/packages/schematics/angular/directive/index_spec.ts @@ -7,12 +7,16 @@ */ import { Tree, VirtualTree } from '@angular-devkit/schematics'; import { SchematicTestRunner } from '@angular-devkit/schematics/test'; +import * as path from 'path'; import { createAppModule, getFileContent } from '../utility/test'; import { Schema as DirectiveSchema } from './schema'; describe('Directive Schematic', () => { - const schematicRunner = new SchematicTestRunner('@schematics/angular'); + const schematicRunner = new SchematicTestRunner( + '@schematics/angular', + path.join(__dirname, '../collection.json'), + ); const defaultOptions: DirectiveSchema = { name: 'foo', path: 'app', diff --git a/packages/schematics/angular/enum/index_spec.ts b/packages/schematics/angular/enum/index_spec.ts index 9ff6721f16..07768b6dbc 100644 --- a/packages/schematics/angular/enum/index_spec.ts +++ b/packages/schematics/angular/enum/index_spec.ts @@ -6,11 +6,15 @@ * found in the LICENSE file at https://angular.io/license */ import { SchematicTestRunner } from '@angular-devkit/schematics/test'; +import * as path from 'path'; import { Schema as EnumSchematic } from './schema'; describe('Enum Schematic', () => { - const schematicRunner = new SchematicTestRunner('@schematics/angular'); + const schematicRunner = new SchematicTestRunner( + '@schematics/angular', + path.join(__dirname, '../collection.json'), + ); const defaultOptions: EnumSchematic = { name: 'foo', path: 'app', diff --git a/packages/schematics/angular/guard/index_spec.ts b/packages/schematics/angular/guard/index_spec.ts index a935ec9707..3bc4daf168 100644 --- a/packages/schematics/angular/guard/index_spec.ts +++ b/packages/schematics/angular/guard/index_spec.ts @@ -7,12 +7,16 @@ */ import { Tree, VirtualTree } from '@angular-devkit/schematics'; import { SchematicTestRunner } from '@angular-devkit/schematics/test'; +import * as path from 'path'; import { createAppModule, getFileContent } from '../utility/test'; import { Schema as GuardSchema } from './schema'; describe('Guard Schematic', () => { - const schematicRunner = new SchematicTestRunner('@schematics/angular'); + const schematicRunner = new SchematicTestRunner( + '@schematics/angular', + path.join(__dirname, '../collection.json'), + ); const defaultOptions: GuardSchema = { name: 'foo', path: 'app', diff --git a/packages/schematics/angular/interface/index_spec.ts b/packages/schematics/angular/interface/index_spec.ts index 55cae40358..de39362785 100644 --- a/packages/schematics/angular/interface/index_spec.ts +++ b/packages/schematics/angular/interface/index_spec.ts @@ -6,12 +6,16 @@ * found in the LICENSE file at https://angular.io/license */ import { SchematicTestRunner } from '@angular-devkit/schematics/test'; +import * as path from 'path'; import { getFileContent } from '../utility/test'; import { Schema as InterfaceSchema } from './schema'; describe('Interface Schematic', () => { - const schematicRunner = new SchematicTestRunner('@schematics/angular'); + const schematicRunner = new SchematicTestRunner( + '@schematics/angular', + path.join(__dirname, '../collection.json'), + ); const defaultOptions: InterfaceSchema = { name: 'foo', path: 'app', diff --git a/packages/schematics/angular/module/index_spec.ts b/packages/schematics/angular/module/index_spec.ts index 38864d7718..19c30368ba 100644 --- a/packages/schematics/angular/module/index_spec.ts +++ b/packages/schematics/angular/module/index_spec.ts @@ -7,12 +7,16 @@ */ import { Tree, VirtualTree } from '@angular-devkit/schematics'; import { SchematicTestRunner } from '@angular-devkit/schematics/test'; +import * as path from 'path'; import { createAppModule, getFileContent } from '../utility/test'; import { Schema as ModuleSchema } from './schema'; describe('Module Schematic', () => { - const schematicRunner = new SchematicTestRunner('@schematics/angular'); + const schematicRunner = new SchematicTestRunner( + '@schematics/angular', + path.join(__dirname, '../collection.json'), + ); const defaultOptions: ModuleSchema = { name: 'foo', path: 'app', diff --git a/packages/schematics/angular/pipe/index_spec.ts b/packages/schematics/angular/pipe/index_spec.ts index 3acb7b68f2..f4cbdedaba 100644 --- a/packages/schematics/angular/pipe/index_spec.ts +++ b/packages/schematics/angular/pipe/index_spec.ts @@ -7,12 +7,16 @@ */ import { Tree, VirtualTree } from '@angular-devkit/schematics'; import { SchematicTestRunner } from '@angular-devkit/schematics/test'; +import * as path from 'path'; import { createAppModule, getFileContent } from '../utility/test'; import { Schema as PipeSchemna } from './schema'; describe('Pipe Schematic', () => { - const schematicRunner = new SchematicTestRunner('@schematics/angular'); + const schematicRunner = new SchematicTestRunner( + '@schematics/angular', + path.join(__dirname, '../collection.json'), + ); const defaultOptions: PipeSchemna = { name: 'foo', path: 'app', diff --git a/packages/schematics/angular/service/index_spec.ts b/packages/schematics/angular/service/index_spec.ts index 1d08af4876..bb184f387f 100644 --- a/packages/schematics/angular/service/index_spec.ts +++ b/packages/schematics/angular/service/index_spec.ts @@ -7,12 +7,16 @@ */ import { Tree, VirtualTree } from '@angular-devkit/schematics'; import { SchematicTestRunner } from '@angular-devkit/schematics/test'; +import * as path from 'path'; import { createAppModule, getFileContent } from '../utility/test'; import { Schema as ServiceSchema } from './schema'; describe('Pipe Schematic', () => { - const schematicRunner = new SchematicTestRunner('@schematics/angular'); + const schematicRunner = new SchematicTestRunner( + '@schematics/angular', + path.join(__dirname, '../collection.json'), + ); const defaultOptions: ServiceSchema = { name: 'foo', path: 'app', From 557783de7a4a8d310a908abb8924c79abe5aaae3 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Fri, 22 Sep 2017 21:09:36 -0700 Subject: [PATCH 15/33] test: fix slow test Turns out the regex is really slow because it makes a lot of captures. This new one is instant. --- packages/schematics/angular/module/index_spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/schematics/angular/module/index_spec.ts b/packages/schematics/angular/module/index_spec.ts index 19c30368ba..23daae5012 100644 --- a/packages/schematics/angular/module/index_spec.ts +++ b/packages/schematics/angular/module/index_spec.ts @@ -48,7 +48,7 @@ describe('Module Schematic', () => { const tree = schematicRunner.runSchematic('module', options, appTree); const content = getFileContent(tree, '/src/app/app.module.ts'); expect(content).toMatch(/import { FooModule } from '.\/foo\/foo.module'/); - expect(content).toMatch(/imports: \[(.|\s)*FooModule(.|\s)*\]/m); + expect(content).toMatch(/imports: \[[^\]]*FooModule[^\]]*\]/m); }); it('should import into another module (deep)', () => { From 081ad5118a264a81a50dd1cefa33d5a47fcf00b6 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Sun, 24 Sep 2017 14:27:00 -0700 Subject: [PATCH 16/33] feat(@angular-devkit/schematics): add support for relative collection path Collections can now understand relative paths to a package.json or paths to a directory that contains a package.json. Also added the capacity for the "schematics" key in package.json to point to a node module which will be resolved relative to the package.json containing the key. --- packages/angular_devkit/core/node/fs.ts | 23 ++++++++++ packages/angular_devkit/core/node/index.ts | 3 ++ packages/angular_devkit/core/node/resolve.ts | 22 ++------- .../tools/node-module-engine-host.ts | 46 ++++++++++++------- 4 files changed, 60 insertions(+), 34 deletions(-) create mode 100644 packages/angular_devkit/core/node/fs.ts diff --git a/packages/angular_devkit/core/node/fs.ts b/packages/angular_devkit/core/node/fs.ts new file mode 100644 index 0000000000..11a175e0bd --- /dev/null +++ b/packages/angular_devkit/core/node/fs.ts @@ -0,0 +1,23 @@ +/** + * @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 * as fs from 'fs'; + + +export function isFile(filePath: string): boolean { + let stat; + try { + stat = fs.statSync(filePath); + } catch (e) { + if (e && (e.code === 'ENOENT' || e.code === 'ENOTDIR')) { + return false; + } + throw e; + } + + return stat.isFile() || stat.isFIFO(); +} diff --git a/packages/angular_devkit/core/node/index.ts b/packages/angular_devkit/core/node/index.ts index 40a74cc2be..1eeb86e461 100644 --- a/packages/angular_devkit/core/node/index.ts +++ b/packages/angular_devkit/core/node/index.ts @@ -6,4 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import * as fs from './fs'; export * from './resolve'; + +export { fs }; diff --git a/packages/angular_devkit/core/node/resolve.ts b/packages/angular_devkit/core/node/resolve.ts index 82e744a2a5..f2bf40c55c 100644 --- a/packages/angular_devkit/core/node/resolve.ts +++ b/packages/angular_devkit/core/node/resolve.ts @@ -8,6 +8,7 @@ import { BaseException } from '@angular-devkit/core'; import * as fs from 'fs'; import * as path from 'path'; +import { isFile } from './fs'; export class ModuleNotFoundException extends BaseException { @@ -32,21 +33,6 @@ function _caller() { } -function _isFile(filePath: string) { - let stat; - try { - stat = fs.statSync(filePath); - } catch (e) { - if (e && (e.code === 'ENOENT' || e.code === 'ENOTDIR')) { - return false; - } - throw e; - } - - return stat.isFile() || stat.isFIFO(); -} - - /** * Get the global directory for node_modules. This is based on NPM code itself, and may be subject * to change, but is relatively stable. @@ -155,16 +141,16 @@ export function resolve(x: string, options: ResolveOptions = {}): string { throw new ModuleNotFoundException(x, basePath); function loadAsFileSync(x: string): string | null { - if (_isFile(x)) { + if (isFile(x)) { return x; } - return extensions.map(ex => x + ex).find(f => _isFile(f)) || null; + return extensions.map(ex => x + ex).find(f => isFile(f)) || null; } function loadAsDirectorySync(x: string): string | null { const pkgfile = path.join(x, 'package.json'); - if (_isFile(pkgfile)) { + if (isFile(pkgfile)) { try { const body = readFileSync(pkgfile, 'UTF8'); const pkg = JSON.parse(body); diff --git a/packages/angular_devkit/schematics/tools/node-module-engine-host.ts b/packages/angular_devkit/schematics/tools/node-module-engine-host.ts index 8361f3c5db..a69d4b507e 100644 --- a/packages/angular_devkit/schematics/tools/node-module-engine-host.ts +++ b/packages/angular_devkit/schematics/tools/node-module-engine-host.ts @@ -5,14 +5,14 @@ * 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 { resolve } from '@angular-devkit/core/node'; +import * as core from '@angular-devkit/core/node'; import { RuleFactory } from '@angular-devkit/schematics'; import { CollectionCannotBeResolvedException, CollectionMissingSchematicsMapException, SchematicMissingFieldsException, } from '@angular-devkit/schematics/tools'; -import { dirname, join } from 'path'; +import { dirname, join, resolve as resolvePath } from 'path'; import { FileSystemCollectionDesc, FileSystemSchematicDesc, @@ -27,23 +27,37 @@ import { FileSystemEngineHostBase } from './file-system-engine-host-base'; export class NodeModulesEngineHost extends FileSystemEngineHostBase { constructor() { super(); } - protected _resolveCollectionPath(name: string): string { - const packageJsonPath = resolve(join(name, 'package.json'), { - basedir: process.cwd(), - checkLocal: true, - checkGlobal: true, - }); + protected _resolvePath(name: string, basedir = process.cwd(), extraChecks = true): string { + // Allow relative / absolute paths. + if (name.startsWith('.') || name.startsWith('/')) { + return resolvePath(process.cwd(), name); + } else { + return core.resolve(name, { + basedir, + checkLocal: extraChecks, + checkGlobal: extraChecks, + }); + } + } - const pkgJsonSchematics = require(packageJsonPath)['schematics']; - if (!pkgJsonSchematics) { - throw new CollectionCannotBeResolvedException(name); + protected _resolveCollectionPath(name: string): string { + let packageJsonPath = this._resolvePath(name); + // If it's a file, use it as is. Otherwise append package.json to it. + if (!core.fs.isFile(packageJsonPath)) { + packageJsonPath = join(packageJsonPath, 'package.json'); } - return resolve(join(dirname(packageJsonPath), pkgJsonSchematics), { - basedir: process.cwd(), - checkLocal: true, - checkGlobal: true, - }); + try { + const pkgJsonSchematics = require(packageJsonPath)['schematics']; + if (pkgJsonSchematics) { + const resolvedPath = this._resolvePath(pkgJsonSchematics, dirname(packageJsonPath), false); + require(resolvedPath); + + return resolvedPath; + } + } catch (e) { + } + throw new CollectionCannotBeResolvedException(name); } protected _resolveReferenceString(refString: string, parentPath: string) { From 81a42a38d7b3ef4b37e69bf0eb135fd3a92fe2ad Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Sun, 24 Sep 2017 15:14:28 -0700 Subject: [PATCH 17/33] refactor(@angular-devkit/schematics): create context in engine Prior to this, contexts were created inline where they were needed. Adding the creation in the Engine will allow us to refer to the host when creating a context. Also added the ability to pass in a parent context for transfering information (such as merge strategy). --- .../schematics/src/engine/engine.ts | 21 +++++++++++++++++++ .../schematics/src/engine/interface.ts | 10 ++++++++- .../schematics/src/engine/schematic.ts | 14 ++++++------- .../schematics/src/engine/schematic_spec.ts | 11 ++++++++-- .../schematics/src/rules/schematic.ts | 2 +- 5 files changed, 47 insertions(+), 11 deletions(-) diff --git a/packages/angular_devkit/schematics/src/engine/engine.ts b/packages/angular_devkit/schematics/src/engine/engine.ts index a4e0603491..f95fa7c86a 100644 --- a/packages/angular_devkit/schematics/src/engine/engine.ts +++ b/packages/angular_devkit/schematics/src/engine/engine.ts @@ -36,6 +36,10 @@ export class UnknownSchematicException extends BaseException { } } +export class SchematicEngineConflictingException extends BaseException { + constructor() { super(`A schematic was called from a different engine as its parent.`); } +} + export class SchematicEngine implements Engine { @@ -67,6 +71,23 @@ export class SchematicEngine, + parent?: Partial>, + ): TypedSchematicContext { + // Check for inconsistencies. + if (parent && parent.engine && parent.engine !== this) { + throw new SchematicEngineConflictingException(); + } + + return { + schematic, + engine: this, + strategy: (parent && parent.strategy !== undefined) + ? parent.strategy : this.defaultMergeStrategy, + }; + } + createSchematic( name: string, collection: Collection): Schematic { diff --git a/packages/angular_devkit/schematics/src/engine/interface.ts b/packages/angular_devkit/schematics/src/engine/interface.ts index 44ee8d8561..bf90295de3 100644 --- a/packages/angular_devkit/schematics/src/engine/interface.ts +++ b/packages/angular_devkit/schematics/src/engine/interface.ts @@ -71,6 +71,10 @@ export interface EngineHost { createCollection(name: string): Collection; + createContext( + schematic: Schematic, + parent?: Partial>, + ): TypedSchematicContext; createSchematic( name: string, collection: Collection, @@ -107,7 +111,11 @@ export interface Schematic; readonly collection: Collection; - call(options: OptionT, host: Observable): Observable; + call( + options: OptionT, + host: Observable, + parentContext?: Partial>, + ): Observable; } diff --git a/packages/angular_devkit/schematics/src/engine/schematic.ts b/packages/angular_devkit/schematics/src/engine/schematic.ts index 432bb8a1c5..f8645af385 100644 --- a/packages/angular_devkit/schematics/src/engine/schematic.ts +++ b/packages/angular_devkit/schematics/src/engine/schematic.ts @@ -31,7 +31,7 @@ export class SchematicImpl { constructor(private _description: SchematicDescription, - private _factory: RuleFactory, // tslint:disable-line:no-any + private _factory: RuleFactory<{}>, private _collection: Collection, private _engine: Engine) { if (!_description.name.match(/^[-_.a-zA-Z0-9]+$/)) { @@ -42,12 +42,12 @@ export class SchematicImpl(options: OptionT, host: Observable): Observable { - const context: TypedSchematicContext = { - engine: this._engine, - schematic: this, - strategy: this._engine.defaultMergeStrategy, - }; + call( + options: OptionT, + host: Observable, + parentContext?: Partial>, + ): Observable { + const context = this._engine.createContext(this, parentContext); return host.concatMap(tree => { const transformedOptions = this._engine.transformOptions(this, options); diff --git a/packages/angular_devkit/schematics/src/engine/schematic_spec.ts b/packages/angular_devkit/schematics/src/engine/schematic_spec.ts index af246235d9..b33df8c1af 100644 --- a/packages/angular_devkit/schematics/src/engine/schematic_spec.ts +++ b/packages/angular_devkit/schematics/src/engine/schematic_spec.ts @@ -6,12 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ // tslint:disable:non-null-operator +import { NullLogger } from '@angular-devkit/core'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/toArray'; import 'rxjs/add/operator/toPromise'; import { MergeStrategy, Tree } from '../tree/interface'; import { branch, empty } from '../tree/static'; -import { CollectionDescription, Engine, Rule, SchematicDescription } from './interface'; +import { CollectionDescription, Engine, Rule, Schematic, SchematicDescription } from './interface'; import { SchematicImpl } from './schematic'; @@ -25,7 +26,13 @@ type SchematicT = { factory: (options: T) => Rule; }; -const engine = { +const context = { + debug: false, + logger: new NullLogger(), + strategy: MergeStrategy.Default, +}; +const engine: Engine = { + createContext: (schematic: Schematic<{}, {}>) => ({ engine, schematic, ...context }), transformOptions: (_: {}, options: {}) => options, defaultMergeStrategy: MergeStrategy.Default, } as Engine; diff --git a/packages/angular_devkit/schematics/src/rules/schematic.ts b/packages/angular_devkit/schematics/src/rules/schematic.ts index 2dd39a28af..693448b721 100644 --- a/packages/angular_devkit/schematics/src/rules/schematic.ts +++ b/packages/angular_devkit/schematics/src/rules/schematic.ts @@ -25,7 +25,7 @@ export function externalSchematic(collectionName: string const collection = context.engine.createCollection(collectionName); const schematic = collection.createSchematic(schematicName); - return schematic.call(options, Observable.of(branch(input))); + return schematic.call(options, Observable.of(branch(input)), context); }; } From 0cc5ad2d2ecc5725c81f7f392d599ec6dc23e4f6 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Sun, 24 Sep 2017 15:15:20 -0700 Subject: [PATCH 18/33] feat(@angular-devkit/schematics): add a --debug flag to schematics tool This will allow us to add more details (and log events) related to schematics. --- .../angular_devkit/core/src/utils/index.ts | 4 +++- .../angular_devkit/core/src/utils/literals.ts | 6 +++++ .../schematics/bin/schematics.ts | 24 +++++++++++++------ .../schematics/src/engine/engine.ts | 1 + .../schematics/src/engine/interface.ts | 1 + 5 files changed, 28 insertions(+), 8 deletions(-) diff --git a/packages/angular_devkit/core/src/utils/index.ts b/packages/angular_devkit/core/src/utils/index.ts index 6b0a8b58ba..511ae85bcc 100644 --- a/packages/angular_devkit/core/src/utils/index.ts +++ b/packages/angular_devkit/core/src/utils/index.ts @@ -5,7 +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 */ -export * from './literals'; +import * as tags from './literals'; export * from './object'; export * from './strings'; export * from './template'; + +export { tags }; diff --git a/packages/angular_devkit/core/src/utils/literals.ts b/packages/angular_devkit/core/src/utils/literals.ts index 7ca3b42293..9980fa0f06 100644 --- a/packages/angular_devkit/core/src/utils/literals.ts +++ b/packages/angular_devkit/core/src/utils/literals.ts @@ -6,6 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ +export function oneLine(strings: TemplateStringsArray, ...values: string[]) { + const endResult = strings.map((s, i) => s + (i < values.length ? values[i] : '')).join(''); + + return endResult.replace(/\n\s*/gm, ' '); +} + export function stripIndents(strings: TemplateStringsArray, ...values: string[]) { const endResult = strings.map((s, i) => s + (i < values.length ? values[i] : '')).join(''); diff --git a/packages/angular_devkit/schematics/bin/schematics.ts b/packages/angular_devkit/schematics/bin/schematics.ts index 81e840e100..047bd4afaa 100644 --- a/packages/angular_devkit/schematics/bin/schematics.ts +++ b/packages/angular_devkit/schematics/bin/schematics.ts @@ -6,7 +6,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 { createLogger, schema } from '@angular-devkit/core'; +import { createLogger, schema, tags } from '@angular-devkit/core'; import { DryRunEvent, DryRunSink, @@ -29,21 +29,25 @@ import 'rxjs/add/operator/ignoreElements'; * Show usage of the CLI tool, and exit the process. */ function usage(exitCode = 0): never { - logger.info(` + logger.info(tags.stripIndents` schematics [CollectionName:]SchematicName [options, ...] By default, if the collection name is not specified, use the internal collection provided by the Schematics CLI. Options: + --debug Debug mode. This is true by default if the collection is a relative + path (in that case, turn off with --debug=false). --dry-run Do not output anything, but instead just show what actions would be - performed. + performed. Default to true if debug is also true. --force Force overwriting files that would otherwise be an error. --list-schematics List all schematics from the collection, by name. + --verbose Show more information. + --help Show this message. Any additional option is passed to the Schematics depending on - `.replace(/^\s\s\s\s/g, '')); // To remove the indentation. + `); process.exit(exitCode); throw 0; // The node typing sometimes don't have a never type for process.exit(). @@ -85,9 +89,13 @@ function parseSchematicName(str: string | null): { collection: string, schematic /** Parse the command line. */ -const booleanArgs = [ 'dry-run', 'force', 'help', 'list-schematics', 'verbose' ]; +const booleanArgs = [ 'debug', 'dry-run', 'force', 'help', 'list-schematics', 'verbose' ]; const argv = minimist(process.argv.slice(2), { boolean: booleanArgs, + default: { + 'debug': null, + 'dry-run': null, + }, '--': true, }); @@ -103,6 +111,7 @@ const { collection: collectionName, schematic: schematicName, } = parseSchematicName(argv._.shift() || null); +const isLocalCollection = collectionName.startsWith('.') || collectionName.startsWith('/'); /** @@ -154,8 +163,9 @@ if (argv['list-schematics']) { const schematic = collection.createSchematic(schematicName); /** Gather the arguments for later use. */ +const debug: boolean = argv.debug === null ? isLocalCollection : argv.debug; +const dryRun: boolean = argv['dry-run'] === null ? debug : argv['dry-run']; const force = argv['force']; -const dryRun = argv['dry-run']; /** This host is the original Tree created from the current directory. */ const host = Observable.of(new FileSystemTree(new FileSystemHost(process.cwd()))); @@ -229,7 +239,7 @@ delete args._; * Then we proceed to run the dryRun commit. We run this before we then commit to the filesystem * (if --dry-run was not passed or an error was detected by dryRun). */ -schematic.call(args, host) +schematic.call(args, host, { debug }) .map((tree: Tree) => Tree.optimize(tree)) .concatMap((tree: Tree) => { return dryRunSink.commit(tree).ignoreElements().concat(Observable.of(tree)); diff --git a/packages/angular_devkit/schematics/src/engine/engine.ts b/packages/angular_devkit/schematics/src/engine/engine.ts index f95fa7c86a..9f52aad1e6 100644 --- a/packages/angular_devkit/schematics/src/engine/engine.ts +++ b/packages/angular_devkit/schematics/src/engine/engine.ts @@ -81,6 +81,7 @@ export class SchematicEngine { + readonly debug: boolean; readonly engine: Engine; readonly schematic: Schematic; readonly strategy: MergeStrategy; From d069e72ec5b4c1ff2cdbb2093fd7fadb4b415528 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Sun, 24 Sep 2017 15:36:37 -0700 Subject: [PATCH 19/33] feat(@angular-devkit/schematics): add a logger to the context --- packages/angular_devkit/schematics/bin/schematics.ts | 2 +- packages/angular_devkit/schematics/src/engine/engine.ts | 5 +++-- packages/angular_devkit/schematics/src/engine/interface.ts | 2 ++ packages/angular_devkit/schematics/src/rules/schematic.ts | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/angular_devkit/schematics/bin/schematics.ts b/packages/angular_devkit/schematics/bin/schematics.ts index 047bd4afaa..2512f8c403 100644 --- a/packages/angular_devkit/schematics/bin/schematics.ts +++ b/packages/angular_devkit/schematics/bin/schematics.ts @@ -239,7 +239,7 @@ delete args._; * Then we proceed to run the dryRun commit. We run this before we then commit to the filesystem * (if --dry-run was not passed or an error was detected by dryRun). */ -schematic.call(args, host, { debug }) +schematic.call(args, host, { debug, logger }) .map((tree: Tree) => Tree.optimize(tree)) .concatMap((tree: Tree) => { return dryRunSink.commit(tree).ignoreElements().concat(Observable.of(tree)); diff --git a/packages/angular_devkit/schematics/src/engine/engine.ts b/packages/angular_devkit/schematics/src/engine/engine.ts index 9f52aad1e6..fda1e4ec69 100644 --- a/packages/angular_devkit/schematics/src/engine/engine.ts +++ b/packages/angular_devkit/schematics/src/engine/engine.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 { BaseException } from '@angular-devkit/core'; +import { BaseException, NullLogger } from '@angular-devkit/core'; import { CollectionDescription, TypedSchematicContext } from '@angular-devkit/schematics'; import 'rxjs/add/operator/map'; import { Url } from 'url'; @@ -82,8 +82,9 @@ export class SchematicEngine { readonly debug: boolean; readonly engine: Engine; + readonly logger: Logger; readonly schematic: Schematic; readonly strategy: MergeStrategy; } diff --git a/packages/angular_devkit/schematics/src/rules/schematic.ts b/packages/angular_devkit/schematics/src/rules/schematic.ts index 693448b721..3e76bbec26 100644 --- a/packages/angular_devkit/schematics/src/rules/schematic.ts +++ b/packages/angular_devkit/schematics/src/rules/schematic.ts @@ -41,6 +41,6 @@ export function schematic(schematicName: string, options const collection = context.schematic.collection; const schematic = collection.createSchematic(schematicName); - return schematic.call(options, Observable.of(branch(input))); + return schematic.call(options, Observable.of(branch(input)), context); }; } From d26acfd6ae19313f3bb3d6b2a6f9dd10f59247c2 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Sun, 24 Sep 2017 15:55:05 -0700 Subject: [PATCH 20/33] refactor: add a literal, move color functions to its own namespace Core is getting crowded, so more stuff will be put in its own namespace. --- packages/angular_devkit/core/src/index.ts | 5 +++- .../angular_devkit/core/src/utils/literals.ts | 16 ++++++++++-- scripts/benchmark.ts | 26 +++++++++++-------- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/packages/angular_devkit/core/src/index.ts b/packages/angular_devkit/core/src/index.ts index 2b1236c336..29b2abf854 100644 --- a/packages/angular_devkit/core/src/index.ts +++ b/packages/angular_devkit/core/src/index.ts @@ -8,6 +8,9 @@ export * from './exception/exception'; export * from './json'; export * from './logger'; -export * from './terminal'; +import * as terminal from './terminal'; export * from './utils'; export * from './virtual-fs'; + + +export { terminal }; diff --git a/packages/angular_devkit/core/src/utils/literals.ts b/packages/angular_devkit/core/src/utils/literals.ts index 9980fa0f06..48299212dd 100644 --- a/packages/angular_devkit/core/src/utils/literals.ts +++ b/packages/angular_devkit/core/src/utils/literals.ts @@ -9,11 +9,23 @@ export function oneLine(strings: TemplateStringsArray, ...values: string[]) { const endResult = strings.map((s, i) => s + (i < values.length ? values[i] : '')).join(''); - return endResult.replace(/\n\s*/gm, ' '); + return endResult.trim().replace(/\n\s*/gm, ' '); +} + +export function indentBy(indentations: number) { + let i = ''; + while (indentations--) { + i += ' '; + } + + return (strings: TemplateStringsArray, ...values: string[]) => { + return stripIndents(strings, ...values) + .replace(/\n/g, '\n' + i); + }; } export function stripIndents(strings: TemplateStringsArray, ...values: string[]) { - const endResult = strings.map((s, i) => s + (i < values.length ? values[i] : '')).join(''); + const endResult = strings.map((s, i) => s + (i < values.length ? values[i] : '')).join('').trim(); // Remove the shortest leading indentation from each line. const match = endResult.match(/^[ \t]*(?=\S)/gm); diff --git a/scripts/benchmark.ts b/scripts/benchmark.ts index 2f7fb1281f..819bb055a7 100644 --- a/scripts/benchmark.ts +++ b/scripts/benchmark.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 { yellow } from '@angular-devkit/core'; +import { tags, terminal } from '@angular-devkit/core'; import * as glob from 'glob'; import 'jasmine'; import {SpecReporter as JasmineSpecReporter } from 'jasmine-spec-reporter'; @@ -84,17 +84,21 @@ class BenchmarkReporter extends JasmineSpecReporter implements jasmine.CustomRep const baseAverage = pad(Math.floor(stat.base.average)); const baseAverageMult = pad(precision(stat.average / stat.base.average), multPad); - console.log(yellow(` fastest: ${fastest}`)); - console.log(yellow(` (base) ${baseFastest}`)); - console.log(yellow(` slowest: ${slowest}`)); - console.log(yellow(` (base) ${baseSlowest}`)); - console.log(yellow(` mean: ${mean} (${baseMean}) (${baseMeanMult}x)`)); - console.log(yellow(` average: ${average} (${baseAverage}) (${baseAverageMult}x)`)); + console.log(terminal.yellow(tags.indentBy(6)` + fastest: ${fastest} + (base) ${baseFastest} + slowest: ${slowest} + (base) ${baseSlowest} + mean: ${mean} (${baseMean}) (${baseMeanMult}x) + average: ${average} (${baseAverage}) (${baseAverageMult}x) + `)); } else { - console.log(yellow(` fastest: ${fastest}`)); - console.log(yellow(` slowest: ${slowest}`)); - console.log(yellow(` mean: ${mean}`)); - console.log(yellow(` average: ${average}`)); + console.log(terminal.yellow(tags.indentBy(6)` + fastest: ${fastest} + slowest: ${slowest} + mean: ${mean} + average: ${average} + `)); } } } From 26fe09da05502d76b864f9bcb82f0783a5384f36 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Sun, 24 Sep 2017 15:55:24 -0700 Subject: [PATCH 21/33] feat(@angular-devkit/schematics): add pretty colors to output Similar to what we do in the CLI --- .../angular_devkit/schematics/bin/schematics.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/angular_devkit/schematics/bin/schematics.ts b/packages/angular_devkit/schematics/bin/schematics.ts index 2512f8c403..1408da2d85 100644 --- a/packages/angular_devkit/schematics/bin/schematics.ts +++ b/packages/angular_devkit/schematics/bin/schematics.ts @@ -6,7 +6,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 { createLogger, schema, tags } from '@angular-devkit/core'; +import { createLogger, schema, tags, terminal } from '@angular-devkit/core'; import { DryRunEvent, DryRunSink, @@ -193,16 +193,20 @@ dryRunSink.reporter.subscribe((event: DryRunEvent) => { error = true; break; case 'update': - loggingQueue.push(`UPDATE ${event.path} (${event.content.length} bytes)`); + loggingQueue.push(tags.oneLine` + ${terminal.white('UPDATE')} ${event.path} (${event.content.length} bytes) + `); break; case 'create': - loggingQueue.push(`CREATE ${event.path} (${event.content.length} bytes)`); + loggingQueue.push(tags.oneLine` + ${terminal.green('CREATE')} ${event.path} (${event.content.length} bytes) + `); break; case 'delete': - loggingQueue.push(`DELETE ${event.path}`); + loggingQueue.push(`${terminal.yellow('DELETE')} ${event.path}`); break; case 'rename': - loggingQueue.push(`RENAME ${event.path} => ${event.to}`); + loggingQueue.push(`${terminal.blue('RENAME')} ${event.path} => ${event.to}`); break; } }); From 22ba1bfb0c6e4ea66d3d737b2b8cc3b1a8169d70 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Sun, 24 Sep 2017 15:59:37 -0700 Subject: [PATCH 22/33] refactor: fix the literals a bit Also remove the usage of common-tags everywhere. --- package-lock.json | 1300 ++++++++--------- package.json | 2 - .../build-optimizer/build-optimizer_spec.ts | 26 +- .../build_optimizer/src/purify/purify_spec.ts | 32 +- .../src/transforms/class-fold_spec.ts | 14 +- .../src/transforms/import-tslib_spec.ts | 32 +- .../src/transforms/prefix-classes_spec.ts | 14 +- .../src/transforms/prefix-functions_spec.ts | 40 +- .../src/transforms/scrub-file_spec.ts | 76 +- .../src/transforms/wrap-enums_spec.ts | 14 +- .../angular_devkit/core/src/utils/literals.ts | 38 +- .../core/src/utils/literals_spec.ts | 28 +- .../schematics/bin/schematics.ts | 2 +- .../angular/utility/ast-utils_spec.ts | 4 +- 14 files changed, 796 insertions(+), 826 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3e50ff71bf..24ad9f558c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,26 +29,13 @@ "resolved": "https://registry.npmjs.org/@ngtools/json-schema/-/json-schema-1.1.0.tgz", "integrity": "sha1-w6DFRNYjkqzCgTpCyKDcb1j4aSI=" }, - "@ngtools/logger": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ngtools/logger/-/logger-1.1.2.tgz", - "integrity": "sha512-mMcToFtz5S76IN8viZOdw5+Bx93LY4qu1axEc2wafVDJyABHmsYak7EjcspjSE/AmocMOMitQrwp1Zhpe/fHxg==", - "requires": { - "rxjs": "5.4.2" - } - }, - "@types/common-tags": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@types/common-tags/-/common-tags-1.2.5.tgz", - "integrity": "sha1-FPKYk5kusyVZS4PXOa8C8rZSD0Y=" - }, "@types/glob": { - "version": "5.0.30", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-5.0.30.tgz", - "integrity": "sha1-ECZAnFYlqGiQdGAoCNCCsoZ7ilE=", + "version": "5.0.32", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-5.0.32.tgz", + "integrity": "sha512-DMcj5b67Alb/e4KhpzyvphC5nVDHn1oCOGZao3oBddZVMH5vgI/cvdp+O/kcxZGZaPqs0ZLAsK4YrjbtZHO05g==", "requires": { - "@types/minimatch": "2.0.29", - "@types/node": "6.0.85" + "@types/minimatch": "3.0.1", + "@types/node": "6.0.88" } }, "@types/istanbul": { @@ -57,14 +44,14 @@ "integrity": "sha1-KcjLt0esVygJZVRdxYUUug27ma8=" }, "@types/jasmine": { - "version": "2.5.53", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.5.53.tgz", - "integrity": "sha512-2YNL0jXYuN7w07mb1sMZQ6T6zOvGi83v8UbjhBZ8mhvI1VkQ2STU9XOrTFyvWswMyh5rW1evS+e7qltYJvTqPA==" + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.6.0.tgz", + "integrity": "sha512-1ZZdFvYA5zARDXPj5+VF0bwDZWH/o0QQWJVDc5srdC/DngcCZXskR33eR/4PielGvBjLQpQOd6KiQbmtqVkeZA==" }, "@types/minimatch": { - "version": "2.0.29", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-2.0.29.tgz", - "integrity": "sha1-UALhT3Xi1x5WQoHfBDHIwbSio2o=" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.1.tgz", + "integrity": "sha512-rUO/jz10KRSyA9SHoCWQ8WX9BICyj5jZYu1/ucKEJKb4KzLZCKMURdYbadP157Q6Zl1x0vHsrU+Z/O0XlhYQDw==" }, "@types/minimist": { "version": "1.2.0", @@ -72,14 +59,14 @@ "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=" }, "@types/node": { - "version": "6.0.85", - "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.85.tgz", - "integrity": "sha512-6qLZpfQFO/g5Ns2e7RsW6brk0Q6Xzwiw7kVVU/XiQNOiJXSojhX76GP457PBYIsNMH2WfcGgcnZB4awFDHrwpA==" + "version": "6.0.88", + "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.88.tgz", + "integrity": "sha512-bYDPZTX0/s1aihdjLuAgogUAT5M+TpoWChEMea2p0yOcfn5bu3k6cJb9cp6nw268XeSNIGGr+4+/8V5K6BGzLQ==" }, "@types/semver": { - "version": "5.3.33", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.3.33.tgz", - "integrity": "sha512-UwrBgjsRS8BSsckIEdrAhIAmdh0MJidtKTvD3S6tpMq6qHLY3uGaNYcRDUjPxpF4hOAOEbMNSXhhfxmNHB1QNQ==" + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.4.0.tgz", + "integrity": "sha512-PBHCvO98hNec9A491vBbh0ZNDOVxccwKL1u2pm6fs9oDgm7SEnw0lEHqHfjsYryDxnE3zaf7LvERWEXjOp1hig==" }, "@types/source-list-map": { "version": "0.1.2", @@ -92,9 +79,9 @@ "integrity": "sha512-/GVAjL1Y8puvZab63n8tsuBiYwZt1bApMdx58/msQ9ID5T05ov+wm/ZV1DvYC/DKKEygpTJViqQvkh5Rhrl4CA==" }, "@types/tapable": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-0.2.3.tgz", - "integrity": "sha1-CIiw8gzH5Y4cIqGIi06WPu+qgQo=" + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-0.2.4.tgz", + "integrity": "sha512-pclMAvhPnXJcJu1ZZ8bQthuUcdDWzDuxDdbSf6l1U6s4fP6EBiZpPsOZYqFOrbqDV97sXGFSsb6AUpiLfv4xIA==" }, "@types/uglify-js": { "version": "2.6.29", @@ -105,12 +92,12 @@ } }, "@types/webpack": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-3.0.6.tgz", - "integrity": "sha512-5XYNKoWQ7jvDgFHglCUJVALcDzYiEQOhRaLocJOKl6GIf91LX6sgjd7vDAYdOvDzWSIYCL3RTwXMJarSwRVing==", + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-3.0.11.tgz", + "integrity": "sha512-zd9NK+SLVONhuD2iGVfE8KcpTHIwIcIZqyisRyGurjC2iXzRu1rkJmAWq4agBOw7rU3GocH29JX/gwrDLSA+cw==", "requires": { - "@types/node": "6.0.85", - "@types/tapable": "0.2.3", + "@types/node": "6.0.88", + "@types/tapable": "0.2.4", "@types/uglify-js": "2.6.29" } }, @@ -119,11 +106,20 @@ "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.3.tgz", "integrity": "sha512-yS052yVjjyIjwcUqIEe2+JxbWsw27OM8UFb1fLUGacGYtqMRwgAx2qk41VTE/nPMjw/xfD0JiHPD0Q99dlrInA==", "requires": { - "@types/node": "6.0.85", + "@types/node": "6.0.88", "@types/source-list-map": "0.1.2", "@types/source-map": "0.5.1" } }, + "JSONStream": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz", + "integrity": "sha1-cH92HgHa6eFvG8+TcDt4xwlmV5o=", + "requires": { + "jsonparse": "1.3.1", + "through": "2.3.8" + } + }, "abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", @@ -159,14 +155,17 @@ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "requires": { + "color-convert": "1.9.0" + } }, "aproba": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.2.tgz", - "integrity": "sha512-ZpYajIfO0j2cOFTO955KUMIKNmj6zhX8kVztMAxFsDaMwz+9Z9SV0uou2pC9HJqcfpffOsjnbrDMvkNy+9RXPw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, "are-we-there-yet": { "version": "1.1.4", @@ -256,22 +255,37 @@ "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" }, "babel-code-frame": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz", - "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ=", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", "requires": { "chalk": "1.1.3", "esutils": "2.0.2", "js-tokens": "3.0.2" - } - }, - "babel-runtime": { - "version": "6.25.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.25.0.tgz", - "integrity": "sha1-M7mOql1IK7AajRqmtDetKwGuxBw=", - "requires": { - "core-js": "2.5.0", - "regenerator-runtime": "0.10.5" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } } }, "balanced-match": { @@ -289,9 +303,9 @@ } }, "big.js": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.1.3.tgz", - "integrity": "sha1-TK2iGTZS6zyp7I5VyQFWacmAaXg=" + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==" }, "block-stream": { "version": "0.0.9", @@ -361,24 +375,35 @@ } }, "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", + "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", "requires": { - "ansi-styles": "2.2.1", + "ansi-styles": "3.2.0", "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "supports-color": "4.4.0" }, "dependencies": { - "supports-color": { + "has-flag": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" + }, + "supports-color": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "requires": { + "has-flag": "2.0.0" + } } } }, + "chownr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", + "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=" + }, "cliui": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", @@ -406,6 +431,19 @@ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, + "color-convert": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", + "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, "colors": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", @@ -430,14 +468,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==" }, - "common-tags": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.4.0.tgz", - "integrity": "sha1-EYe+Tz1M8MBCfUP3Tu8fc1AWFMA=", - "requires": { - "babel-runtime": "6.25.0" - } - }, "compare-func": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-1.3.2.tgz", @@ -458,31 +488,29 @@ "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "conventional-changelog": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-1.1.4.tgz", - "integrity": "sha1-EIvHUMKjF+IA4vm0E8qqH4x++js=", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-1.1.5.tgz", + "integrity": "sha512-DUM0iXhgE11uRCoEQnFuYQA5zJcxfvKn940ZFyXziFl0y05yBCoy5ci2fcQYFWQ+OyyxgE2H02EdgYpuz9XG4w==", "requires": { - "conventional-changelog-angular": "1.4.0", + "conventional-changelog-angular": "1.5.0", "conventional-changelog-atom": "0.1.1", - "conventional-changelog-codemirror": "0.1.0", - "conventional-changelog-core": "1.9.0", - "conventional-changelog-ember": "0.2.6", - "conventional-changelog-eslint": "0.1.0", - "conventional-changelog-express": "0.1.0", + "conventional-changelog-codemirror": "0.2.0", + "conventional-changelog-core": "1.9.1", + "conventional-changelog-ember": "0.2.7", + "conventional-changelog-eslint": "0.2.0", + "conventional-changelog-express": "0.2.0", "conventional-changelog-jquery": "0.1.0", "conventional-changelog-jscs": "0.1.0", - "conventional-changelog-jshint": "0.1.0" + "conventional-changelog-jshint": "0.2.0" } }, "conventional-changelog-angular": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-1.4.0.tgz", - "integrity": "sha512-ukKX22lJl9ewogze1hKbBuff/dGMG2uyGpOhhw0ehhlv6GtdeCxj51YfGOZ5qC89WwsHT7SDXFzBKidwH3pwmQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-1.5.0.tgz", + "integrity": "sha512-Gt0qSf5wdFmLabgdSlqjguDAmPyYTXtUl4WH5W3SlpElHhnU/UiCY3M7xcIkZxrNQfVA1UxUBgu65eBbaJnZVA==", "requires": { "compare-func": "1.3.2", - "github-url-from-git": "1.5.0", - "q": "1.5.0", - "read-pkg-up": "2.0.0" + "q": "1.5.0" } }, "conventional-changelog-atom": { @@ -494,20 +522,20 @@ } }, "conventional-changelog-codemirror": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-0.1.0.tgz", - "integrity": "sha1-dXelkdv5tTjnoVCn7mL2WihyszQ=", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-0.2.0.tgz", + "integrity": "sha512-jUbY98JoKdAOR5k3pOBiKZ+Iz9t2F84hL7x4WjSRW6x7FdeCEUOjyfml+YClE2h/h62Tf3mwur5jSO8upxxc1g==", "requires": { "q": "1.5.0" } }, "conventional-changelog-core": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-1.9.0.tgz", - "integrity": "sha1-3l37wJGEdlZQjUo4njXJobxJ5/Q=", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-1.9.1.tgz", + "integrity": "sha512-Fo0bSeO+NsO6GtuQXts0xQeRpLrxaABTPU8NK4Zij9sJB3zFkU4BObSefJS4F4+EkKujaKCWtfS6Uih+9NpXrQ==", "requires": { - "conventional-changelog-writer": "1.4.1", - "conventional-commits-parser": "1.3.0", + "conventional-changelog-writer": "2.0.1", + "conventional-commits-parser": "2.0.0", "dateformat": "1.0.12", "get-pkg-repo": "1.4.0", "git-raw-commits": "1.2.0", @@ -519,96 +547,28 @@ "read-pkg": "1.1.0", "read-pkg-up": "1.0.1", "through2": "2.0.3" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "strip-bom": "2.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "requires": { - "pinkie-promise": "2.0.1" - } - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "requires": { - "graceful-fs": "4.1.11", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - } - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "requires": { - "load-json-file": "1.1.0", - "normalize-package-data": "2.4.0", - "path-type": "1.1.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "requires": { - "find-up": "1.1.2", - "read-pkg": "1.1.0" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "requires": { - "is-utf8": "0.2.1" - } - } } }, "conventional-changelog-ember": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-0.2.6.tgz", - "integrity": "sha1-i3NVQZ9RJ0k8TFYkc6svx5LxwrY=", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-0.2.7.tgz", + "integrity": "sha512-Xj1v9uVcKM8N798hMr7e6yiw8IFyvI6Ik+TdjdmG54uGupqvEEWW37xAbPPbdKvgoitbyZkqUTancj055actEg==", "requires": { "q": "1.5.0" } }, "conventional-changelog-eslint": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-0.1.0.tgz", - "integrity": "sha1-pSQR6ZngUBzlALhWsKZD0DMJB+I=", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-0.2.0.tgz", + "integrity": "sha512-WGKnC0bGPD6BHGiRBfYqNGfy6DZDn2jGs1yxPRT8I2796wYdGqsbDF4477o4fdsxUJvckoW2OFPqkmRMQaCHSA==", "requires": { "q": "1.5.0" } }, "conventional-changelog-express": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-0.1.0.tgz", - "integrity": "sha1-VcbIQcgRliA2wDe9vZZKVK4xD84=", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-0.2.0.tgz", + "integrity": "sha512-ujSEmbWfozC1iIjH5Pl7AKtREowvAl10whs1q6c7nZLnoNZK5CmdB2PQ/V42O6rCgUzaLX+ACRW2+g0A/Htqvw==", "requires": { "q": "1.5.0" } @@ -630,18 +590,18 @@ } }, "conventional-changelog-jshint": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-0.1.0.tgz", - "integrity": "sha1-AMq46aMxdIer2UxNhGcTQpGNKgc=", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-0.2.0.tgz", + "integrity": "sha512-uUP4c0et6F2teapl+YY2JHFAHD401U5CkgI+P8PyU0y1zS8BdBy6EnhqgZEXhFOp9fPzUdic+Wv/9alOqw3agQ==", "requires": { "compare-func": "1.3.2", "q": "1.5.0" } }, "conventional-changelog-writer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-1.4.1.tgz", - "integrity": "sha1-P0y00APrtWmJ0w00WJO1KkNjnI4=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-2.0.1.tgz", + "integrity": "sha512-X4qC758celQOKw0iUPAsH5sJX6fH6N5dboFc3elXb1/SIKhsYMukhhaxWmxRdtVUSqGt9rZg8giwBQG5B2GeKg==", "requires": { "compare-func": "1.3.2", "conventional-commits-filter": "1.0.0", @@ -665,12 +625,12 @@ } }, "conventional-commits-parser": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-1.3.0.tgz", - "integrity": "sha1-4ye1MZThp61dxjR57pCZpSsCSGU=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-2.0.0.tgz", + "integrity": "sha512-8od6g684Fhi5Vpp4ABRv/RBsW1AY6wSHbJHEK6FGTv+8jvAAnlABniZu/FVmX9TcirkHepaEsa1QGkRvbg0CKw==", "requires": { - "is-text-path": "1.0.1", "JSONStream": "1.3.1", + "is-text-path": "1.0.1", "lodash": "4.17.4", "meow": "3.7.0", "split2": "2.1.1", @@ -678,11 +638,6 @@ "trim-off-newlines": "1.0.1" } }, - "core-js": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.0.tgz", - "integrity": "sha1-VpwFCRi+ZIazg3VSAorgRmtxcIY=" - }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -747,9 +702,9 @@ } }, "debug": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", - "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "requires": { "ms": "2.0.0" } @@ -789,9 +744,9 @@ "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, "diff": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.0.tgz", - "integrity": "sha512-w0XZubFWn0Adlsapj9EAWX0FqWdO4tz8kc3RiYdWLh4k/V8PTb6i0SMgXt0vRM3zyKnT8tKO7mUlieRQHIjMNg==" + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", + "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==" }, "dot-prop": { "version": "3.0.0", @@ -829,12 +784,12 @@ } }, "es-abstract": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.8.0.tgz", - "integrity": "sha512-Cf9/h5MrXtExM20gSS55YFrGKCyPrRBjIVBtVyy8vmlsDfe0NPKMWj65tPLgzyfPuapWxh5whpXCtW4+AW5mRg==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.8.2.tgz", + "integrity": "sha512-dvhwFL3yjQxNNsOWx6exMlaDrRHCRGMQlnx5lsXDCZ/J7G/frgIIl94zhZSp/galVAYp7VzPi1OrAHta89/yGQ==", "requires": { "es-to-primitive": "1.1.1", - "function-bind": "1.1.0", + "function-bind": "1.1.1", "has": "1.0.1", "is-callable": "1.1.3", "is-regex": "1.0.4" @@ -938,11 +893,12 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "requires": { - "locate-path": "2.0.0" + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" } }, "foreach": { @@ -962,7 +918,7 @@ "requires": { "asynckit": "0.4.0", "combined-stream": "1.0.5", - "mime-types": "2.1.16" + "mime-types": "2.1.17" } }, "from": { @@ -997,16 +953,16 @@ } }, "function-bind": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz", - "integrity": "sha1-FhdnFMgBeY5Ojyz391KUZ7tKV3E=" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "requires": { - "aproba": "1.1.2", + "aproba": "1.2.0", "console-control-strings": "1.1.0", "has-unicode": "2.0.1", "object-assign": "4.1.1", @@ -1024,7 +980,7 @@ "hosted-git-info": "2.5.0", "meow": "3.7.0", "normalize-package-data": "2.4.0", - "parse-github-repo-url": "1.4.0", + "parse-github-repo-url": "1.4.1", "through2": "2.0.3" } }, @@ -1086,11 +1042,6 @@ "ini": "1.3.4" } }, - "github-url-from-git": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/github-url-from-git/-/github-url-from-git-1.5.0.tgz", - "integrity": "sha1-+YX+3MCpqledyI16/waNVcxiUaA=" - }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", @@ -1149,7 +1100,7 @@ "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", "requires": { - "function-bind": "1.1.0" + "function-bind": "1.1.1" } }, "has-ansi": { @@ -1300,7 +1251,7 @@ "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", "requires": { - "text-extensions": "1.5.0" + "text-extensions": "1.6.0" } }, "is-typedarray": { @@ -1339,7 +1290,7 @@ "esprima": "2.7.3", "glob": "5.0.15", "handlebars": "4.0.10", - "js-yaml": "3.9.1", + "js-yaml": "3.10.0", "mkdirp": "0.5.1", "nopt": "3.0.6", "once": "1.4.0", @@ -1369,19 +1320,19 @@ } }, "jasmine": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.7.0.tgz", - "integrity": "sha1-XPC7TllLRgC7QjVWA2YhKsWuobI=", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", + "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", "requires": { "exit": "0.1.2", "glob": "7.1.2", - "jasmine-core": "2.7.0" + "jasmine-core": "2.8.0" } }, "jasmine-core": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.7.0.tgz", - "integrity": "sha1-UP+MT5LY71wLLBuEbdJj7YUVIJE=" + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", + "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=" }, "jasmine-spec-reporter": { "version": "3.3.0", @@ -1397,9 +1348,9 @@ "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" }, "js-yaml": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.9.1.tgz", - "integrity": "sha512-CbcG379L1e+mWBnLvHWWeLs8GyV/EMw862uLI3c+GxVyDHWZcjZinwuBd3iW2pgxgIlksW/1vNJa4to+RvDOww==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", + "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", "requires": { "argparse": "1.0.9", "esprima": "4.0.0" @@ -1451,15 +1402,6 @@ "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" }, - "JSONStream": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz", - "integrity": "sha1-cH92HgHa6eFvG8+TcDt4xwlmV5o=", - "requires": { - "jsonparse": "1.3.1", - "through": "2.3.8" - } - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -1501,14 +1443,15 @@ } }, "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "requires": { "graceful-fs": "4.1.11", "parse-json": "2.2.0", "pify": "2.3.0", - "strip-bom": "3.0.0" + "pinkie-promise": "2.0.1", + "strip-bom": "2.0.0" } }, "loader-utils": { @@ -1516,20 +1459,11 @@ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "requires": { - "big.js": "3.1.3", + "big.js": "3.2.0", "emojis-list": "2.1.0", "json5": "0.5.1" } }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "requires": { - "p-locate": "2.0.0", - "path-exists": "3.0.0" - } - }, "lodash": { "version": "4.17.4", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", @@ -1601,6 +1535,37 @@ "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=" }, + "memory-streams": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/memory-streams/-/memory-streams-0.1.2.tgz", + "integrity": "sha1-Jz/3d6tg/sWZsRY1UlUoLMosUMI=", + "requires": { + "readable-stream": "1.0.34" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, "meow": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", @@ -1616,87 +1581,19 @@ "read-pkg-up": "1.0.1", "redent": "1.0.0", "trim-newlines": "1.0.0" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "strip-bom": "2.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "requires": { - "pinkie-promise": "2.0.1" - } - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "requires": { - "graceful-fs": "4.1.11", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - } - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "requires": { - "load-json-file": "1.1.0", - "normalize-package-data": "2.4.0", - "path-type": "1.1.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "requires": { - "find-up": "1.1.2", - "read-pkg": "1.1.0" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "requires": { - "is-utf8": "0.2.1" - } - } } }, "mime-db": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.29.0.tgz", - "integrity": "sha1-SNJtI1WJZRcErFkWygYAGRQmaHg=" + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" }, "mime-types": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.16.tgz", - "integrity": "sha1-K4WKUuXs1RbbiXrCvodIeDBpjiM=", + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", "requires": { - "mime-db": "1.29.0" + "mime-db": "1.30.0" } }, "minimatch": { @@ -1761,21 +1658,22 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "nan": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.6.2.tgz", - "integrity": "sha1-5P805slf37WuzAjeZZb0NgWn20U=" + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", + "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY=" }, "node-pre-gyp": { - "version": "0.6.36", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz", - "integrity": "sha1-22BBEst04NR3VU6bUFsXq936t4Y=", + "version": "0.6.38", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.38.tgz", + "integrity": "sha1-6Sog+DQWQVu0CG9tH7eLPac9ET0=", "requires": { + "hawk": "3.1.3", "mkdirp": "0.5.1", "nopt": "4.0.1", "npmlog": "4.1.2", "rc": "1.2.1", "request": "2.81.0", - "rimraf": "2.6.1", + "rimraf": "2.6.2", "semver": "5.4.1", "tar": "2.2.1", "tar-pack": "3.4.0" @@ -1791,9 +1689,9 @@ } }, "rimraf": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", - "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "requires": { "glob": "7.1.2" } @@ -1830,10 +1728,11 @@ } }, "npm": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm/-/npm-5.3.0.tgz", - "integrity": "sha512-ZJsOWVJ25E2C5Qedf4w9ePIv5hrPCdDIsHhq89tRxSJCqyIfDAMh0KoU9xeTu7yHT9ZrxPF7mopq1TCWxtMfkw==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/npm/-/npm-5.4.2.tgz", + "integrity": "sha512-F6LLCAHriKyKQ9Ff03UKCjkXZoRBp281I42K42+VeHfjAXZ3TJdg3RccinzoCFV1kDxCedVm7AstIpb1Uf5UkQ==", "requires": { + "JSONStream": "1.3.1", "abbrev": "1.1.0", "ansi-regex": "3.0.0", "ansicolors": "0.3.2", @@ -1853,8 +1752,6 @@ "editor": "1.0.0", "fs-vacuum": "1.2.10", "fs-write-stream-atomic": "1.0.10", - "fstream": "1.0.11", - "fstream-npm": "1.2.1", "glob": "7.1.2", "graceful-fs": "4.1.11", "has-unicode": "2.0.1", @@ -1865,9 +1762,8 @@ "inherits": "2.0.3", "ini": "1.3.4", "init-package-json": "1.10.1", - "JSONStream": "1.3.1", "lazy-property": "1.0.0", - "libnpx": "9.2.0", + "libnpx": "9.6.0", "lockfile": "1.0.3", "lodash._baseindexof": "3.1.0", "lodash._baseuniq": "4.6.0", @@ -1881,6 +1777,7 @@ "lodash.uniq": "4.5.0", "lodash.without": "4.4.0", "lru-cache": "4.1.1", + "meant": "1.0.0", "mississippi": "1.3.0", "mkdirp": "0.5.1", "move-concurrently": "1.0.1", @@ -1889,20 +1786,22 @@ "normalize-package-data": "2.4.0", "npm-cache-filename": "1.0.2", "npm-install-checks": "3.0.0", + "npm-lifecycle": "1.0.2", "npm-package-arg": "5.1.2", + "npm-packlist": "1.1.8", "npm-registry-client": "8.4.0", "npm-user-validate": "1.0.0", "npmlog": "4.1.2", "once": "1.4.0", "opener": "1.4.3", "osenv": "0.1.4", - "pacote": "2.7.38", + "pacote": "6.0.2", "path-is-inside": "1.0.2", "promise-inflight": "1.0.1", "read": "1.0.7", "read-cmd-shim": "1.0.1", "read-installed": "4.0.3", - "read-package-json": "2.0.10", + "read-package-json": "2.0.12", "read-package-tree": "5.1.6", "readable-stream": "2.3.3", "readdir-scoped-modules": "1.0.2", @@ -1910,14 +1809,14 @@ "retry": "0.10.1", "rimraf": "2.6.1", "safe-buffer": "5.1.1", - "semver": "5.3.0", + "semver": "5.4.1", "sha": "2.0.1", "slide": "1.1.6", "sorted-object": "2.0.1", "sorted-union-stream": "2.1.3", "ssri": "4.1.6", "strip-ansi": "4.0.0", - "tar": "2.2.1", + "tar": "4.0.1", "text-table": "0.2.0", "uid-number": "0.0.6", "umask": "1.1.0", @@ -1927,12 +1826,30 @@ "uuid": "3.1.0", "validate-npm-package-license": "3.0.1", "validate-npm-package-name": "3.0.0", - "which": "1.2.14", - "worker-farm": "1.4.1", + "which": "1.3.0", + "worker-farm": "1.5.0", "wrappy": "1.0.2", "write-file-atomic": "2.1.0" }, "dependencies": { + "JSONStream": { + "version": "1.3.1", + "bundled": true, + "requires": { + "jsonparse": "1.3.1", + "through": "2.3.8" + }, + "dependencies": { + "jsonparse": { + "version": "1.3.1", + "bundled": true + }, + "through": { + "version": "2.3.8", + "bundled": true + } + } + }, "abbrev": { "version": "1.1.0", "bundled": true @@ -2124,64 +2041,6 @@ "readable-stream": "2.3.3" } }, - "fstream": { - "version": "1.0.11", - "bundled": true, - "requires": { - "graceful-fs": "4.1.11", - "inherits": "2.0.3", - "mkdirp": "0.5.1", - "rimraf": "2.6.1" - } - }, - "fstream-npm": { - "version": "1.2.1", - "bundled": true, - "requires": { - "fstream-ignore": "1.0.5", - "inherits": "2.0.3" - }, - "dependencies": { - "fstream-ignore": { - "version": "1.0.5", - "bundled": true, - "requires": { - "fstream": "1.0.11", - "inherits": "2.0.3", - "minimatch": "3.0.4" - }, - "dependencies": { - "minimatch": { - "version": "3.0.4", - "bundled": true, - "requires": { - "brace-expansion": "1.1.8" - }, - "dependencies": { - "brace-expansion": { - "version": "1.1.8", - "bundled": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - }, - "dependencies": { - "balanced-match": { - "version": "1.0.0", - "bundled": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true - } - } - } - } - } - } - } - } - }, "glob": { "version": "7.1.2", "bundled": true, @@ -2275,8 +2134,8 @@ "npm-package-arg": "5.1.2", "promzard": "0.3.0", "read": "1.0.7", - "read-package-json": "2.0.10", - "semver": "5.3.0", + "read-package-json": "2.0.12", + "semver": "5.4.1", "validate-npm-package-license": "3.0.1", "validate-npm-package-name": "3.0.0" }, @@ -2290,30 +2149,12 @@ } } }, - "JSONStream": { - "version": "1.3.1", - "bundled": true, - "requires": { - "jsonparse": "1.3.1", - "through": "2.3.8" - }, - "dependencies": { - "jsonparse": { - "version": "1.3.1", - "bundled": true - }, - "through": { - "version": "2.3.8", - "bundled": true - } - } - }, "lazy-property": { "version": "1.0.0", "bundled": true }, "libnpx": { - "version": "9.2.0", + "version": "9.6.0", "bundled": true, "requires": { "dotenv": "4.0.0", @@ -2321,7 +2162,7 @@ "rimraf": "2.6.1", "safe-buffer": "5.1.1", "update-notifier": "2.2.0", - "which": "1.2.14", + "which": "1.3.0", "y18n": "3.2.1", "yargs": "8.0.2" }, @@ -2342,12 +2183,12 @@ "cliui": "3.2.0", "decamelize": "1.2.0", "get-caller-file": "1.0.2", - "os-locale": "2.0.0", + "os-locale": "2.1.0", "read-pkg-up": "2.0.0", "require-directory": "2.1.1", "require-main-filename": "1.0.1", "set-blocking": "2.0.0", - "string-width": "2.1.0", + "string-width": "2.1.1", "which-module": "2.0.0", "y18n": "3.2.1", "yargs-parser": "7.0.0" @@ -2426,20 +2267,20 @@ "bundled": true }, "os-locale": { - "version": "2.0.0", + "version": "2.1.0", "bundled": true, "requires": { - "execa": "0.5.1", + "execa": "0.7.0", "lcid": "1.0.0", "mem": "1.1.0" }, "dependencies": { "execa": { - "version": "0.5.1", + "version": "0.7.0", "bundled": true, "requires": { - "cross-spawn": "4.0.2", - "get-stream": "2.3.1", + "cross-spawn": "5.1.0", + "get-stream": "3.0.0", "is-stream": "1.1.0", "npm-run-path": "2.0.2", "p-finally": "1.0.0", @@ -2448,40 +2289,33 @@ }, "dependencies": { "cross-spawn": { - "version": "4.0.2", + "version": "5.1.0", "bundled": true, "requires": { "lru-cache": "4.1.1", - "which": "1.2.14" - } - }, - "get-stream": { - "version": "2.3.1", - "bundled": true, - "requires": { - "object-assign": "4.1.1", - "pinkie-promise": "2.0.1" + "shebang-command": "1.2.0", + "which": "1.3.0" }, "dependencies": { - "object-assign": { - "version": "4.1.1", - "bundled": true - }, - "pinkie-promise": { - "version": "2.0.1", + "shebang-command": { + "version": "1.2.0", "bundled": true, "requires": { - "pinkie": "2.0.4" + "shebang-regex": "1.0.0" }, "dependencies": { - "pinkie": { - "version": "2.0.4", + "shebang-regex": { + "version": "1.0.0", "bundled": true } } } } }, + "get-stream": { + "version": "3.0.0", + "bundled": true + }, "is-stream": { "version": "1.1.0", "bundled": true @@ -2666,7 +2500,7 @@ "bundled": true }, "string-width": { - "version": "2.1.0", + "version": "2.1.1", "bundled": true, "requires": { "is-fullwidth-code-point": "2.0.0", @@ -2777,6 +2611,10 @@ } } }, + "meant": { + "version": "1.0.0", + "bundled": true + }, "mississippi": { "version": "1.3.0", "bundled": true, @@ -2987,9 +2825,19 @@ "rimraf": "2.6.1", "semver": "5.3.0", "tar": "2.2.1", - "which": "1.2.14" + "which": "1.3.0" }, "dependencies": { + "fstream": { + "version": "1.0.11", + "bundled": true, + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.1" + } + }, "minimatch": { "version": "3.0.4", "bundled": true, @@ -3023,6 +2871,28 @@ "requires": { "abbrev": "1.1.0" } + }, + "semver": { + "version": "5.3.0", + "bundled": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + }, + "dependencies": { + "block-stream": { + "version": "0.0.9", + "bundled": true, + "requires": { + "inherits": "2.0.3" + } + } + } } } }, @@ -3040,7 +2910,7 @@ "requires": { "hosted-git-info": "2.5.0", "is-builtin-module": "1.0.0", - "semver": "5.3.0", + "semver": "5.4.1", "validate-npm-package-license": "3.0.1" }, "dependencies": { @@ -3067,7 +2937,18 @@ "version": "3.0.0", "bundled": true, "requires": { - "semver": "5.3.0" + "semver": "5.4.1" + } + }, + "npm-lifecycle": { + "version": "1.0.2", + "bundled": true, + "requires": { + "graceful-fs": "4.1.11", + "slide": "1.1.6", + "uid-number": "0.0.6", + "umask": "1.1.0", + "which": "1.3.0" } }, "npm-package-arg": { @@ -3076,10 +2957,60 @@ "requires": { "hosted-git-info": "2.5.0", "osenv": "0.1.4", - "semver": "5.3.0", + "semver": "5.4.1", "validate-npm-package-name": "3.0.0" } }, + "npm-packlist": { + "version": "1.1.8", + "bundled": true, + "requires": { + "ignore-walk": "3.0.0", + "npm-bundled": "1.0.3" + }, + "dependencies": { + "ignore-walk": { + "version": "3.0.0", + "bundled": true, + "requires": { + "minimatch": "3.0.4" + }, + "dependencies": { + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "1.1.8" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.8", + "bundled": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + } + } + } + } + } + } + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true + } + } + }, "npm-registry-client": { "version": "8.4.0", "bundled": true, @@ -3092,7 +3023,7 @@ "once": "1.4.0", "request": "2.81.0", "retry": "0.10.1", - "semver": "5.3.0", + "semver": "5.4.1", "slide": "1.1.6", "ssri": "4.1.6" }, @@ -3254,44 +3185,44 @@ } }, "pacote": { - "version": "2.7.38", + "version": "6.0.2", "bundled": true, "requires": { "bluebird": "3.5.0", "cacache": "9.2.9", "glob": "7.1.2", "lru-cache": "4.1.1", - "make-fetch-happen": "2.4.13", + "make-fetch-happen": "2.5.0", "minimatch": "3.0.4", "mississippi": "1.3.0", "normalize-package-data": "2.4.0", "npm-package-arg": "5.1.2", + "npm-packlist": "1.1.8", "npm-pick-manifest": "1.0.4", "osenv": "0.1.4", "promise-inflight": "1.0.1", "promise-retry": "1.1.1", "protoduck": "4.0.0", "safe-buffer": "5.1.1", - "semver": "5.3.0", + "semver": "5.4.1", "ssri": "4.1.6", - "tar-fs": "1.15.3", - "tar-stream": "1.5.4", + "tar": "4.0.1", "unique-filename": "1.1.0", - "which": "1.2.14" + "which": "1.3.0" }, "dependencies": { "make-fetch-happen": { - "version": "2.4.13", + "version": "2.5.0", "bundled": true, "requires": { "agentkeepalive": "3.3.0", "cacache": "9.2.9", "http-cache-semantics": "3.7.3", "http-proxy-agent": "2.0.0", - "https-proxy-agent": "2.0.0", + "https-proxy-agent": "2.1.0", "lru-cache": "4.1.1", "mississippi": "1.3.0", - "node-fetch-npm": "2.0.1", + "node-fetch-npm": "2.0.2", "promise-retry": "1.1.1", "socks-proxy-agent": "3.0.0", "ssri": "4.1.6" @@ -3327,12 +3258,12 @@ "version": "2.0.0", "bundled": true, "requires": { - "agent-base": "4.1.0", + "agent-base": "4.1.1", "debug": "2.6.8" }, "dependencies": { "agent-base": { - "version": "4.1.0", + "version": "4.1.1", "bundled": true, "requires": { "es6-promisify": "5.0.0" @@ -3369,15 +3300,15 @@ } }, "https-proxy-agent": { - "version": "2.0.0", + "version": "2.1.0", "bundled": true, "requires": { - "agent-base": "4.1.0", + "agent-base": "4.1.1", "debug": "2.6.8" }, "dependencies": { "agent-base": { - "version": "4.1.0", + "version": "4.1.1", "bundled": true, "requires": { "es6-promisify": "5.0.0" @@ -3414,11 +3345,11 @@ } }, "node-fetch-npm": { - "version": "2.0.1", + "version": "2.0.2", "bundled": true, "requires": { "encoding": "0.1.12", - "json-parse-helpfulerror": "1.0.3", + "json-parse-better-errors": "1.0.1", "safe-buffer": "5.1.1" }, "dependencies": { @@ -3435,18 +3366,9 @@ } } }, - "json-parse-helpfulerror": { - "version": "1.0.3", - "bundled": true, - "requires": { - "jju": "1.3.0" - }, - "dependencies": { - "jju": { - "version": "1.3.0", - "bundled": true - } - } + "json-parse-better-errors": { + "version": "1.0.1", + "bundled": true } } }, @@ -3454,12 +3376,12 @@ "version": "3.0.0", "bundled": true, "requires": { - "agent-base": "4.1.0", + "agent-base": "4.1.1", "socks": "1.1.10" }, "dependencies": { "agent-base": { - "version": "4.1.0", + "version": "4.1.1", "bundled": true, "requires": { "es6-promisify": "5.0.0" @@ -3534,7 +3456,7 @@ "bundled": true, "requires": { "npm-package-arg": "5.1.2", - "semver": "5.3.0" + "semver": "5.4.1" } }, "promise-retry": { @@ -3563,65 +3485,6 @@ "bundled": true } } - }, - "tar-fs": { - "version": "1.15.3", - "bundled": true, - "requires": { - "chownr": "1.0.1", - "mkdirp": "0.5.1", - "pump": "1.0.2", - "tar-stream": "1.5.4" - }, - "dependencies": { - "pump": { - "version": "1.0.2", - "bundled": true, - "requires": { - "end-of-stream": "1.4.0", - "once": "1.4.0" - }, - "dependencies": { - "end-of-stream": { - "version": "1.4.0", - "bundled": true, - "requires": { - "once": "1.4.0" - } - } - } - } - } - }, - "tar-stream": { - "version": "1.5.4", - "bundled": true, - "requires": { - "bl": "1.2.1", - "end-of-stream": "1.4.0", - "readable-stream": "2.3.3", - "xtend": "4.0.1" - }, - "dependencies": { - "bl": { - "version": "1.2.1", - "bundled": true, - "requires": { - "readable-stream": "2.3.3" - } - }, - "end-of-stream": { - "version": "1.4.0", - "bundled": true, - "requires": { - "once": "1.4.0" - } - }, - "xtend": { - "version": "4.0.1", - "bundled": true - } - } } } }, @@ -3659,9 +3522,9 @@ "requires": { "debuglog": "1.0.1", "graceful-fs": "4.1.11", - "read-package-json": "2.0.10", + "read-package-json": "2.0.12", "readdir-scoped-modules": "1.0.2", - "semver": "5.3.0", + "semver": "5.4.1", "slide": "1.1.6", "util-extend": "1.0.3" }, @@ -3673,27 +3536,23 @@ } }, "read-package-json": { - "version": "2.0.10", + "version": "2.0.12", "bundled": true, "requires": { "glob": "7.1.2", "graceful-fs": "4.1.11", - "json-parse-helpfulerror": "1.0.3", - "normalize-package-data": "2.4.0" + "json-parse-better-errors": "1.0.1", + "normalize-package-data": "2.4.0", + "slash": "1.0.0" }, "dependencies": { - "json-parse-helpfulerror": { - "version": "1.0.3", - "bundled": true, - "requires": { - "jju": "1.3.0" - }, - "dependencies": { - "jju": { - "version": "1.3.0", - "bundled": true - } - } + "json-parse-better-errors": { + "version": "1.0.1", + "bundled": true + }, + "slash": { + "version": "1.0.0", + "bundled": true } } }, @@ -3704,7 +3563,7 @@ "debuglog": "1.0.1", "dezalgo": "1.0.3", "once": "1.4.0", - "read-package-json": "2.0.10", + "read-package-json": "2.0.12", "readdir-scoped-modules": "1.0.2" } }, @@ -4099,7 +3958,7 @@ "bundled": true }, "semver": { - "version": "5.3.0", + "version": "5.4.1", "bundled": true }, "sha": { @@ -4197,20 +4056,33 @@ } }, "tar": { - "version": "2.2.1", + "version": "4.0.1", "bundled": true, "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.11", - "inherits": "2.0.3" + "chownr": "1.0.1", + "minipass": "2.2.1", + "minizlib": "1.0.3", + "mkdirp": "0.5.1", + "yallist": "3.0.2" }, "dependencies": { - "block-stream": { - "version": "0.0.9", + "minipass": { + "version": "2.2.1", "bundled": true, "requires": { - "inherits": "2.0.3" + "yallist": "3.0.2" } + }, + "minizlib": { + "version": "1.0.3", + "bundled": true, + "requires": { + "minipass": "2.2.1" + } + }, + "yallist": { + "version": "3.0.2", + "bundled": true } } }, @@ -4333,7 +4205,7 @@ "bundled": true, "requires": { "lru-cache": "4.1.1", - "which": "1.2.14" + "which": "1.3.0" } }, "is-stream": { @@ -4541,7 +4413,7 @@ "got": "6.7.1", "registry-auth-token": "3.3.1", "registry-url": "3.1.0", - "semver": "5.3.0" + "semver": "5.4.1" }, "dependencies": { "got": { @@ -4696,7 +4568,7 @@ "version": "2.1.0", "bundled": true, "requires": { - "semver": "5.3.0" + "semver": "5.4.1" } }, "xdg-basedir": { @@ -4750,7 +4622,7 @@ } }, "which": { - "version": "1.2.14", + "version": "1.3.0", "bundled": true, "requires": { "isexe": "2.0.0" @@ -4763,7 +4635,7 @@ } }, "worker-farm": { - "version": "1.4.1", + "version": "1.5.0", "bundled": true, "requires": { "errno": "0.1.4", @@ -4826,17 +4698,55 @@ } }, "npm-run-all": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.0.2.tgz", - "integrity": "sha1-qEZpNI5ttsy+BSIAtM22v+A0pP4=", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.1.tgz", + "integrity": "sha512-qrmqqaJa+REbzUTIL/mHfTdgwz+gL1xUezY/ueyLa7GISZ4T3h0CH8D2r6AaZdCYN2unu7PzspP0ofpXla1ftg==", "requires": { - "chalk": "1.1.3", + "ansi-styles": "3.2.0", + "chalk": "2.1.0", "cross-spawn": "5.1.0", + "memory-streams": "0.1.2", "minimatch": "3.0.4", "ps-tree": "1.1.0", "read-pkg": "2.0.0", "shell-quote": "1.6.1", "string.prototype.padend": "3.0.0" + }, + "dependencies": { + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "strip-bom": "3.0.0" + } + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "requires": { + "pify": "2.3.0" + } + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "requires": { + "load-json-file": "2.0.0", + "normalize-package-data": "2.4.0", + "path-type": "2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + } } }, "npm-which": { @@ -4949,23 +4859,10 @@ "os-tmpdir": "1.0.2" } }, - "p-limit": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.1.0.tgz", - "integrity": "sha1-sH/y2aXYi+yAYDWJWiurZqJ5iLw=" - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "requires": { - "p-limit": "1.1.0" - } - }, "parse-github-repo-url": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/parse-github-repo-url/-/parse-github-repo-url-1.4.0.tgz", - "integrity": "sha1-KGxT4smWLgZBZJ7jrJUI/KTdlZw=" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz", + "integrity": "sha1-nn2LslKmy2ukJZUGC3v23z28H1A=" }, "parse-json": { "version": "2.2.0", @@ -4976,9 +4873,12 @@ } }, "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "requires": { + "pinkie-promise": "2.0.1" + } }, "path-is-absolute": { "version": "1.0.1", @@ -4991,11 +4891,13 @@ "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=" }, "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "requires": { - "pify": "2.3.0" + "graceful-fs": "4.1.11", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" } }, "pause-stream": { @@ -5111,22 +5013,22 @@ } }, "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", "requires": { - "load-json-file": "2.0.0", + "load-json-file": "1.1.0", "normalize-package-data": "2.4.0", - "path-type": "2.0.0" + "path-type": "1.1.0" } }, "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", "requires": { - "find-up": "2.1.0", - "read-pkg": "2.0.0" + "find-up": "1.1.2", + "read-pkg": "1.1.0" } }, "readable-stream": { @@ -5152,11 +5054,6 @@ "strip-indent": "1.0.1" } }, - "regenerator-runtime": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=" - }, "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", @@ -5188,13 +5085,13 @@ "is-typedarray": "1.0.0", "isstream": "0.1.2", "json-stringify-safe": "5.0.1", - "mime-types": "2.1.16", + "mime-types": "2.1.17", "oauth-sign": "0.8.2", "performance-now": "0.2.0", "qs": "6.4.0", "safe-buffer": "5.1.1", "stringstream": "0.0.5", - "tough-cookie": "2.3.2", + "tough-cookie": "2.3.3", "tunnel-agent": "0.6.0", "uuid": "3.1.0" } @@ -5218,9 +5115,9 @@ "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=" }, "rxjs": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.4.2.tgz", - "integrity": "sha1-KjI2/L8D31e64G/Wly/ZnlwI/Pc=", + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.4.3.tgz", + "integrity": "sha512-fSNi+y+P9ss+EZuV0GcIIqPUK07DEaMRUtLJvdcvMyFjc9dizuDjere+A4V7JrLGnm9iCc+nagV/4QdMTkqC4A==", "requires": { "symbol-observable": "1.0.4" } @@ -5291,16 +5188,16 @@ "integrity": "sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==" }, "source-map": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", - "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=" + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, "source-map-support": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.15.tgz", - "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E=", + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", "requires": { - "source-map": "0.5.6" + "source-map": "0.5.7" } }, "spdx-correct": { @@ -5372,14 +5269,6 @@ "duplexer": "0.1.1" } }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "requires": { - "safe-buffer": "5.1.1" - } - }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", @@ -5396,8 +5285,16 @@ "integrity": "sha1-86rvfBcZ8XDF6rHDK/eA2W4h8vA=", "requires": { "define-properties": "1.1.2", - "es-abstract": "1.8.0", - "function-bind": "1.1.0" + "es-abstract": "1.8.2", + "function-bind": "1.1.1" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "requires": { + "safe-buffer": "5.1.1" } }, "stringstream": { @@ -5414,9 +5311,12 @@ } }, "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "requires": { + "is-utf8": "0.2.1" + } }, "strip-indent": { "version": "1.0.1", @@ -5450,10 +5350,11 @@ "integrity": "sha1-cX0izFPwzh3vVZQ2LzqJouu5EQU=" }, "tar": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/tar/-/tar-3.1.9.tgz", - "integrity": "sha512-/Aiui1ynEeSz6rr8RhLk3Vp9BEbtYnlIauKHco1ILlP5U6W6SSUWnDtycqTPCovdnnoutKHzzCOchZqEfCcvVw==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-3.2.1.tgz", + "integrity": "sha512-ZSzds1E0IqutvMU8HxjMaU8eB7urw2fGwTq88ukDOVuUIh0656l7/P7LiVPxhO5kS4flcRJQk8USG+cghQbTUQ==", "requires": { + "chownr": "1.0.1", "minipass": "2.2.1", "minizlib": "1.0.3", "mkdirp": "0.5.1", @@ -5472,20 +5373,20 @@ "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.0.tgz", "integrity": "sha1-I74tf2cagzk3bL2wuP4/3r8xeYQ=", "requires": { - "debug": "2.6.8", + "debug": "2.6.9", "fstream": "1.0.11", "fstream-ignore": "1.0.5", "once": "1.4.0", "readable-stream": "2.3.3", - "rimraf": "2.6.1", + "rimraf": "2.6.2", "tar": "2.2.1", "uid-number": "0.0.6" }, "dependencies": { "rimraf": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", - "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "requires": { "glob": "7.1.2" } @@ -5512,9 +5413,9 @@ } }, "text-extensions": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.5.0.tgz", - "integrity": "sha1-0cstFLXQvEW/3Kigikc/aMfrDLw=" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.6.0.tgz", + "integrity": "sha512-U2M04F2rbuYYCNioiTD14cImLTae4ys1rC57tllzKg3dt5DPR2JXs5yFdC017yOBrW6wM6s5gtAlFJ7yye04rA==" }, "through": { "version": "2.3.8", @@ -5531,9 +5432,9 @@ } }, "tough-cookie": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", - "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", + "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", "requires": { "punycode": "1.4.1" } @@ -5555,16 +5456,40 @@ "requires": { "arrify": "1.0.1", "chalk": "1.1.3", - "diff": "3.3.0", + "diff": "3.3.1", "make-error": "1.3.0", "minimist": "1.2.0", "mkdirp": "0.5.1", "pinkie": "2.0.4", - "source-map-support": "0.4.15", + "source-map-support": "0.4.18", "tsconfig": "6.0.0", "v8flags": "2.1.1", "xtend": "4.0.1", "yn": "1.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } } }, "tsconfig": { @@ -5574,6 +5499,13 @@ "requires": { "strip-bom": "3.0.0", "strip-json-comments": "2.0.1" + }, + "dependencies": { + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + } } }, "tsickle": { @@ -5584,8 +5516,8 @@ "requires": { "minimist": "1.2.0", "mkdirp": "0.5.1", - "source-map": "0.5.6", - "source-map-support": "0.4.15" + "source-map": "0.5.7", + "source-map-support": "0.4.18" } }, "tslib": { @@ -5594,20 +5526,20 @@ "integrity": "sha1-vIAEFkaRkjp5/oN4u+s9ogF1OOw=" }, "tslint": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.6.0.tgz", - "integrity": "sha1-CIqmxgJmIzOGULKQCCirPt9Z9s8=", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.7.0.tgz", + "integrity": "sha1-wl4NDJL6EgHCvDDoROCOaCtPNVI=", "requires": { - "babel-code-frame": "6.22.0", + "babel-code-frame": "6.26.0", "colors": "1.1.2", "commander": "2.11.0", - "diff": "3.3.0", + "diff": "3.3.1", "glob": "7.1.2", "minimatch": "3.0.4", "resolve": "1.4.0", "semver": "5.4.1", "tslib": "1.7.1", - "tsutils": "2.8.0" + "tsutils": "2.8.2" }, "dependencies": { "resolve": { @@ -5621,9 +5553,9 @@ } }, "tsutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.8.0.tgz", - "integrity": "sha1-AWAXNymzvxOGKN0UoVN+AIUdgUo=", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.8.2.tgz", + "integrity": "sha1-LBSGukMSYIRbCsb5Aq/Z1wio6mo=", "requires": { "tslib": "1.7.1" } @@ -5661,7 +5593,7 @@ "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", "optional": true, "requires": { - "source-map": "0.5.6", + "source-map": "0.5.7", "uglify-to-browserify": "1.0.2", "yargs": "3.10.0" } @@ -5697,8 +5629,8 @@ "resolved": "https://registry.npmjs.org/v8-profiler/-/v8-profiler-5.7.0.tgz", "integrity": "sha1-6DgcvrtbX9DKjSsJ9qAYGhWNs00=", "requires": { - "nan": "2.6.2", - "node-pre-gyp": "0.6.36" + "nan": "2.7.0", + "node-pre-gyp": "0.6.38" } }, "v8flags": { @@ -5741,7 +5673,7 @@ "integrity": "sha512-05tMxipUCwHqYaVS8xc7sYPTly8PzXayRCB4dTxLhWTqlKUiwH6ezmEe0OSreL1c30LAuA3Zqmc+uEBUGFJDjw==", "requires": { "source-list-map": "2.0.0", - "source-map": "0.5.6" + "source-map": "0.5.7" } }, "which": { diff --git a/package.json b/package.json index ba73fe1dab..2d1035a2ce 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,6 @@ "homepage": "https://github.com/angular/devkit", "dependencies": { "@ngtools/json-schema": "^1.0.9", - "@types/common-tags": "^1.2.4", "@types/glob": "^5.0.29", "@types/istanbul": "^0.4.29", "@types/jasmine": "^2.5.47", @@ -52,7 +51,6 @@ "@types/source-map": "^0.5.0", "@types/webpack": "^3.0.2", "@types/webpack-sources": "^0.1.3", - "common-tags": "^1.3.1", "conventional-changelog": "^1.1.0", "glob": "^7.0.3", "istanbul": "^0.4.5", diff --git a/packages/angular_devkit/build_optimizer/src/build-optimizer/build-optimizer_spec.ts b/packages/angular_devkit/build_optimizer/src/build-optimizer/build-optimizer_spec.ts index 0e76451396..9be981fcc8 100644 --- a/packages/angular_devkit/build_optimizer/src/build-optimizer/build-optimizer_spec.ts +++ b/packages/angular_devkit/build_optimizer/src/build-optimizer/build-optimizer_spec.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 { oneLine, stripIndent } from 'common-tags'; +import { tags } from '@angular-devkit/core'; import { RawSourceMap } from 'source-map'; import { buildOptimizer } from './build-optimizer'; @@ -18,7 +18,7 @@ describe('build-optimizer', () => { describe('basic functionality', () => { it('applies class-fold, scrub-file and prefix-functions', () => { - const input = stripIndent` + const input = tags.stripIndent` ${imports} var __extends = (this && this.__extends) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; @@ -37,7 +37,7 @@ describe('build-optimizer', () => { Clazz.ctorParameters = function () { return [{type: Injector}]; }; `; // tslint:disable:max-line-length - const output = oneLine` + const output = tags.oneLine` /** PURE_IMPORTS_START _angular_core,tslib PURE_IMPORTS_END */ ${imports} import { __extends } from "tslib"; @@ -52,12 +52,12 @@ describe('build-optimizer', () => { const inputFilePath = '/node_modules/@angular/core/@angular/core.es5.js'; const boOutput = buildOptimizer({ content: input, inputFilePath }); - expect(oneLine`${boOutput.content}`).toEqual(output); + expect(tags.oneLine`${boOutput.content}`).toEqual(output); expect(boOutput.emitSkipped).toEqual(false); }); it('doesn\'t process files without decorators/ctorParameters/outside Angular', () => { - const input = oneLine` + const input = tags.oneLine` var Clazz = (function () { function Clazz() { } return Clazz; }()); ${staticProperty} `; @@ -70,18 +70,18 @@ describe('build-optimizer', () => { it('supports es2015 modules', () => { // prefix-functions would add PURE_IMPORTS_START and PURE to the super call. // This test ensures it isn't applied to es2015 modules. - const output = oneLine` + const output = tags.oneLine` import { Injectable } from '@angular/core'; class Clazz extends BaseClazz { constructor(e) { super(e); } } `; - const input = stripIndent` + const input = tags.stripIndent` ${output} Clazz.ctorParameters = () => [ { type: Injectable } ]; `; const inputFilePath = '/node_modules/@angular/core/@angular/core.js'; const boOutput = buildOptimizer({ content: input, inputFilePath }); - expect(oneLine`${boOutput.content}`).toEqual(output); + expect(tags.oneLine`${boOutput.content}`).toEqual(output); expect(boOutput.emitSkipped).toEqual(false); }); }); @@ -89,7 +89,7 @@ describe('build-optimizer', () => { describe('resilience', () => { it('doesn\'t process files with invalid syntax by default', () => { - const input = oneLine` + const input = tags.oneLine` ))))invalid syntax ${clazz} Clazz.decorators = [ { type: Injectable } ]; @@ -101,7 +101,7 @@ describe('build-optimizer', () => { }); it('throws on files with invalid syntax in strict mode', () => { - const input = oneLine` + const input = tags.oneLine` ))))invalid syntax ${clazz} Clazz.decorators = [ { type: Injectable } ]; @@ -137,7 +137,7 @@ describe('build-optimizer', () => { }); describe('sourcemaps', () => { - const transformableInput = oneLine` + const transformableInput = tags.oneLine` ${imports} ${clazz} ${decorators} @@ -154,11 +154,11 @@ describe('build-optimizer', () => { }); it('doesn\'t produce sourcemaps when emitting was skipped', () => { - const ignoredInput = oneLine` + const ignoredInput = tags.oneLine` var Clazz = (function () { function Clazz() { } return Clazz; }()); ${staticProperty} `; - const invalidInput = oneLine` + const invalidInput = tags.oneLine` ))))invalid syntax ${clazz} Clazz.decorators = [ { type: Injectable } ]; diff --git a/packages/angular_devkit/build_optimizer/src/purify/purify_spec.ts b/packages/angular_devkit/build_optimizer/src/purify/purify_spec.ts index e3e489c894..255da328ad 100644 --- a/packages/angular_devkit/build_optimizer/src/purify/purify_spec.ts +++ b/packages/angular_devkit/build_optimizer/src/purify/purify_spec.ts @@ -5,77 +5,77 @@ * 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 { oneLine, stripIndent } from 'common-tags'; +import { tags } from '@angular-devkit/core'; import { purify } from './purify'; // tslint:disable:max-line-length describe('purify', () => { it('prefixes safe imports with /*@__PURE__*/', () => { - const input = stripIndent` + const input = tags.stripIndent` /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_rxjs_Subject__ = __webpack_require__("EEr4"); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__angular_http__ = __webpack_require__(72); /** PURE_IMPORTS_START rxjs_Subject,_angular_http PURE_IMPORTS_END */ `; - const output = stripIndent` + const output = tags.stripIndent` /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_rxjs_Subject__ = /*@__PURE__*/__webpack_require__("EEr4"); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__angular_http__ = /*@__PURE__*/__webpack_require__(72); /** PURE_IMPORTS_START rxjs_Subject,_angular_http PURE_IMPORTS_END */ `; - expect(oneLine`${purify(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${purify(input)}`).toEqual(tags.oneLine`${output}`); }); it('prefixes safe default imports with /*@__PURE__*/', () => { - const input = stripIndent` + const input = tags.stripIndent` /* harmony import */ var __WEBPACK_IMPORTED_MODULE_4_rxjs_Subject__ = __webpack_require__("rlar"); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_4_rxjs_Subject___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_4_rxjs_Subject__); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_zone_js___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_zone_js__); /** PURE_IMPORTS_START rxjs_Subject,zone_js PURE_IMPORTS_END */ `; - const output = stripIndent` + const output = tags.stripIndent` /* harmony import */ var __WEBPACK_IMPORTED_MODULE_4_rxjs_Subject__ = /*@__PURE__*/__webpack_require__("rlar"); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_4_rxjs_Subject___default = /*@__PURE__*/__webpack_require__.n(__WEBPACK_IMPORTED_MODULE_4_rxjs_Subject__); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_zone_js___default = /*@__PURE__*/__webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_zone_js__); /** PURE_IMPORTS_START rxjs_Subject,zone_js PURE_IMPORTS_END */ `; - expect(oneLine`${purify(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${purify(input)}`).toEqual(tags.oneLine`${output}`); }); it('prefix createComponentFactory and createNgModuleFactory function calls', () => { - const input = stripIndent` + const input = tags.stripIndent` var AppComponentNgFactory = __WEBPACK_IMPORTED_MODULE_1__angular_core__["I" /* ɵccf */]('app-root'); var AppModuleNgFactory = __WEBPACK_IMPORTED_MODULE_1__angular_core__["I" /* ɵcmf */]('app-root'); var SelectComponentNgFactory = select_component_ngfactory___WEBPACK_IMPORTED_MODULE_0__angular_core__["U" /* ɵccf */]('aio-select'); `; - const output = stripIndent` + const output = tags.stripIndent` var AppComponentNgFactory = /*@__PURE__*/__WEBPACK_IMPORTED_MODULE_1__angular_core__["I" /* ɵccf */]('app-root'); var AppModuleNgFactory = /*@__PURE__*/__WEBPACK_IMPORTED_MODULE_1__angular_core__["I" /* ɵcmf */]('app-root'); var SelectComponentNgFactory = /*@__PURE__*/select_component_ngfactory___WEBPACK_IMPORTED_MODULE_0__angular_core__["U" /* ɵccf */]('aio-select'); `; - expect(oneLine`${purify(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${purify(input)}`).toEqual(tags.oneLine`${output}`); }); it('should prefix createRendererType2 function calls', () => { - const input = stripIndent` + const input = tags.stripIndent` var RenderType_MdOption = index_ngfactory___WEBPACK_IMPORTED_MODULE_0__angular_core__["W" /* ɵcrt */]({ encapsulation: 2, styles: styles_MdOption}); `; - const output = stripIndent` + const output = tags.stripIndent` var RenderType_MdOption = /*@__PURE__*/index_ngfactory___WEBPACK_IMPORTED_MODULE_0__angular_core__["W" /* ɵcrt */]({ encapsulation: 2, styles: styles_MdOption}); `; - expect(oneLine`${purify(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${purify(input)}`).toEqual(tags.oneLine`${output}`); }); it('prefix module statements', () => { - const input = stripIndent` + const input = tags.stripIndent` var AppModuleNgFactory = new __WEBPACK_IMPORTED_MODULE_1__angular_core__["I" /* NgModuleFactory */]; `; - const output = stripIndent` + const output = tags.stripIndent` var AppModuleNgFactory = /*@__PURE__*/new __WEBPACK_IMPORTED_MODULE_1__angular_core__["I" /* NgModuleFactory */]; `; - expect(oneLine`${purify(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${purify(input)}`).toEqual(tags.oneLine`${output}`); }); }); diff --git a/packages/angular_devkit/build_optimizer/src/transforms/class-fold_spec.ts b/packages/angular_devkit/build_optimizer/src/transforms/class-fold_spec.ts index acf0be775b..237c857ce9 100644 --- a/packages/angular_devkit/build_optimizer/src/transforms/class-fold_spec.ts +++ b/packages/angular_devkit/build_optimizer/src/transforms/class-fold_spec.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 { oneLine, stripIndent } from 'common-tags'; +import { tags } from '@angular-devkit/core'; import { transformJavascript } from '../helpers/transform-javascript'; import { getFoldFileTransformer } from './class-fold'; @@ -16,31 +16,31 @@ const transform = (content: string) => transformJavascript( describe('class-fold', () => { it('folds static properties into class', () => { const staticProperty = 'Clazz.prop = 1;'; - const input = stripIndent` + const input = tags.stripIndent` var Clazz = (function () { function Clazz() { } return Clazz; }()); ${staticProperty} `; - const output = stripIndent` + const output = tags.stripIndent` var Clazz = (function () { function Clazz() { } ${staticProperty} return Clazz; }()); `; - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); it('folds multiple static properties into class', () => { const staticProperty = 'Clazz.prop = 1;'; const anotherStaticProperty = 'Clazz.anotherProp = 2;'; - const input = stripIndent` + const input = tags.stripIndent` var Clazz = (function () { function Clazz() { } return Clazz; }()); ${staticProperty} ${anotherStaticProperty} `; - const output = stripIndent` + const output = tags.stripIndent` var Clazz = (function () { function Clazz() { } ${staticProperty} ${anotherStaticProperty} return Clazz; }()); `; - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); }); diff --git a/packages/angular_devkit/build_optimizer/src/transforms/import-tslib_spec.ts b/packages/angular_devkit/build_optimizer/src/transforms/import-tslib_spec.ts index 05ec7499f4..d78b467af8 100644 --- a/packages/angular_devkit/build_optimizer/src/transforms/import-tslib_spec.ts +++ b/packages/angular_devkit/build_optimizer/src/transforms/import-tslib_spec.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 { oneLine, stripIndent } from 'common-tags'; +import { tags } from '@angular-devkit/core'; import { transformJavascript } from '../helpers/transform-javascript'; import { getImportTslibTransformer, testImportTslib } from './import-tslib'; @@ -15,24 +15,24 @@ const transform = (content: string) => transformJavascript( describe('import-tslib', () => { it('replaces __extends', () => { - const input = stripIndent` + const input = tags.stripIndent` var __extends = (this && this.__extends) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; `; - const output = stripIndent` + const output = tags.stripIndent` import { __extends } from "tslib"; `; expect(testImportTslib(input)).toBeTruthy(); - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); it('replaces __decorate', () => { // tslint:disable:max-line-length - const input = stripIndent` + const input = tags.stripIndent` var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); @@ -41,47 +41,47 @@ describe('import-tslib', () => { }; `; // tslint:enable:max-line-length - const output = stripIndent` + const output = tags.stripIndent` import { __decorate } from "tslib"; `; expect(testImportTslib(input)).toBeTruthy(); - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); it('replaces __metadata', () => { // tslint:disable:max-line-length - const input = stripIndent` + const input = tags.stripIndent` var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; `; // tslint:enable:max-line-length - const output = stripIndent` + const output = tags.stripIndent` import { __metadata } from "tslib"; `; expect(testImportTslib(input)).toBeTruthy(); - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); it('replaces __param', () => { - const input = stripIndent` + const input = tags.stripIndent` var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; `; - const output = stripIndent` + const output = tags.stripIndent` import { __param } from "tslib"; `; expect(testImportTslib(input)).toBeTruthy(); - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); it('replaces uses "require" instead of "import" on CJS modules', () => { - const input = stripIndent` + const input = tags.stripIndent` var __extends = (this && this.__extends) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; function __() { this.constructor = d; } @@ -89,11 +89,11 @@ describe('import-tslib', () => { }; exports.meaning = 42; `; - const output = stripIndent` + const output = tags.stripIndent` var __extends = /*@__PURE__*/ require("tslib").__extends; exports.meaning = 42; `; - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); }); diff --git a/packages/angular_devkit/build_optimizer/src/transforms/prefix-classes_spec.ts b/packages/angular_devkit/build_optimizer/src/transforms/prefix-classes_spec.ts index e214697868..d5a6ee4206 100644 --- a/packages/angular_devkit/build_optimizer/src/transforms/prefix-classes_spec.ts +++ b/packages/angular_devkit/build_optimizer/src/transforms/prefix-classes_spec.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 { oneLine, stripIndent } from 'common-tags'; +import { tags } from '@angular-devkit/core'; import { transformJavascript } from '../helpers/transform-javascript'; import { getPrefixClassesTransformer, testPrefixClasses } from './prefix-classes'; @@ -15,7 +15,7 @@ const transform = (content: string) => transformJavascript( describe('prefix-classes', () => { it('prefix downleveled classes with /*@__PURE__*/', () => { - const input = stripIndent` + const input = tags.stripIndent` var ReplayEvent = (function () { function ReplayEvent(time, value) { this.time = time; @@ -24,7 +24,7 @@ describe('prefix-classes', () => { return ReplayEvent; }()); `; - const output = stripIndent` + const output = tags.stripIndent` var ReplayEvent = /*@__PURE__*/ (function () { function ReplayEvent(time, value) { this.time = time; @@ -35,12 +35,12 @@ describe('prefix-classes', () => { `; expect(testPrefixClasses(input)).toBeTruthy(); - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); // tslint:disable:max-line-length it('prefix downleveled classes that extend another class with /*@__PURE__*/', () => { - const input = stripIndent` + const input = tags.stripIndent` var TakeUntilSubscriber = (function (_super) { __extends(TakeUntilSubscriber, _super); function TakeUntilSubscriber(destination, notifier) { @@ -57,7 +57,7 @@ describe('prefix-classes', () => { return TakeUntilSubscriber; }(OuterSubscriber_1.OuterSubscriber)); `; - const output = stripIndent` + const output = tags.stripIndent` var TakeUntilSubscriber = /*@__PURE__*/ (function (_super) { __extends(TakeUntilSubscriber, _super); function TakeUntilSubscriber(destination, notifier) { @@ -76,7 +76,7 @@ describe('prefix-classes', () => { `; expect(testPrefixClasses(input)).toBeTruthy(); - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); // tslint:enable:max-line-length }); diff --git a/packages/angular_devkit/build_optimizer/src/transforms/prefix-functions_spec.ts b/packages/angular_devkit/build_optimizer/src/transforms/prefix-functions_spec.ts index 031936e7ea..37a973b180 100644 --- a/packages/angular_devkit/build_optimizer/src/transforms/prefix-functions_spec.ts +++ b/packages/angular_devkit/build_optimizer/src/transforms/prefix-functions_spec.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 { oneLine, stripIndent } from 'common-tags'; +import { tags } from '@angular-devkit/core'; import { transformJavascript } from '../helpers/transform-javascript'; import { getPrefixFunctionsTransformer } from './prefix-functions'; @@ -14,77 +14,77 @@ const transform = (content: string) => transformJavascript( { content, getTransforms: [getPrefixFunctionsTransformer] }).content; describe('prefix-functions', () => { - const emptyImportsComment = '/** PURE_IMPORTS_START PURE_IMPORTS_END */'; + const emptyImportsComment = '/** PURE_IMPORTS_START PURE_IMPORTS_END */'; const clazz = 'var Clazz = (function () { function Clazz() { } return Clazz; }());'; describe('pure imports', () => { it('adds import list', () => { - const input = stripIndent` + const input = tags.stripIndent` import { Injectable } from '@angular/core'; var foo = Injectable; `; - const output = stripIndent` + const output = tags.stripIndent` /** PURE_IMPORTS_START _angular_core PURE_IMPORTS_END */ ${input} `; - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); it('adds import list even with no imports', () => { - const input = stripIndent` + const input = tags.stripIndent` var foo = 42; `; - const output = stripIndent` + const output = tags.stripIndent` ${emptyImportsComment} ${input} `; - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); }); describe('pure functions', () => { it('adds comment to new calls', () => { - const input = stripIndent` + const input = tags.stripIndent` var newClazz = new Clazz(); `; - const output = stripIndent` + const output = tags.stripIndent` ${emptyImportsComment} var newClazz = /*@__PURE__*/ new Clazz(); `; - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); it('adds comment to function calls', () => { - const input = stripIndent` + const input = tags.stripIndent` var newClazz = Clazz(); `; - const output = stripIndent` + const output = tags.stripIndent` ${emptyImportsComment} var newClazz = /*@__PURE__*/ Clazz(); `; - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); it('adds comment outside of IIFEs', () => { - const input = stripIndent` + const input = tags.stripIndent` ${clazz} var ClazzTwo = (function () { function Clazz() { } return Clazz; })(); `; - const output = stripIndent` + const output = tags.stripIndent` ${emptyImportsComment} var Clazz = /*@__PURE__*/ (function () { function Clazz() { } return Clazz; }()); var ClazzTwo = /*@__PURE__*/ (function () { function Clazz() { } return Clazz; })(); `; - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); it('doesn\'t adds comment when inside function declarations or expressions', () => { - const input = stripIndent` + const input = tags.stripIndent` function funcDecl() { var newClazz = Clazz(); var newClazzTwo = new Clazz(); @@ -95,12 +95,12 @@ describe('prefix-functions', () => { var newClazzTwo = new Clazz(); }; `; - const output = stripIndent` + const output = tags.stripIndent` ${emptyImportsComment} ${input} `; - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); }); }); diff --git a/packages/angular_devkit/build_optimizer/src/transforms/scrub-file_spec.ts b/packages/angular_devkit/build_optimizer/src/transforms/scrub-file_spec.ts index d5102e432b..41a2c1009b 100644 --- a/packages/angular_devkit/build_optimizer/src/transforms/scrub-file_spec.ts +++ b/packages/angular_devkit/build_optimizer/src/transforms/scrub-file_spec.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 { oneLine, stripIndent } from 'common-tags'; +import { tags } from '@angular-devkit/core'; import { transformJavascript } from '../helpers/transform-javascript'; import { getScrubFileTransformer, testScrubFile } from './scrub-file'; @@ -18,28 +18,28 @@ describe('scrub-file', () => { describe('decorators', () => { it('removes top-level Angular decorators', () => { - const output = stripIndent` + const output = tags.stripIndent` import { Injectable } from '@angular/core'; ${clazz} `; - const input = stripIndent` + const input = tags.stripIndent` ${output} Clazz.decorators = [ { type: Injectable } ]; `; expect(testScrubFile(input)).toBeTruthy(); - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); it('removes nested Angular decorators', () => { - const output = stripIndent` + const output = tags.stripIndent` import { Injectable } from '@angular/core'; var Clazz = (function () { function Clazz() { } return Clazz; }()); `; - const input = stripIndent` + const input = tags.stripIndent` import { Injectable } from '@angular/core'; var Clazz = (function () { function Clazz() {} @@ -48,61 +48,61 @@ describe('scrub-file', () => { }()); `; - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); it('doesn\'t remove non Angular decorators', () => { - const input = stripIndent` + const input = tags.stripIndent` import { Injectable } from 'another-lib'; ${clazz} Clazz.decorators = [{ type: Injectable }]; `; - expect(oneLine`${transform(input)}`).toEqual(oneLine`${input}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${input}`); }); it('leaves non-Angular decorators in mixed arrays', () => { - const input = stripIndent` + const input = tags.stripIndent` import { Injectable } from '@angular/core'; import { NotInjectable } from 'another-lib'; ${clazz} Clazz.decorators = [{ type: Injectable }, { type: NotInjectable }]; `; - const output = stripIndent` + const output = tags.stripIndent` import { Injectable } from '@angular/core'; import { NotInjectable } from 'another-lib'; ${clazz} Clazz.decorators = [{ type: NotInjectable }]; `; - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); }); describe('propDecorators', () => { it('removes top-level Angular propDecorators', () => { - const output = stripIndent` + const output = tags.stripIndent` import { Input } from '@angular/core'; ${clazz} `; - const input = stripIndent` + const input = tags.stripIndent` ${output} Clazz.propDecorators = { 'ngIf': [{ type: Input }] } `; expect(testScrubFile(input)).toBeTruthy(); - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); it('removes nested Angular propDecorators', () => { - const output = stripIndent` + const output = tags.stripIndent` import { Input } from '@angular/core'; var Clazz = (function () { function Clazz() { } return Clazz; }()); `; - const input = stripIndent` + const input = tags.stripIndent` import { Input } from '@angular/core'; var Clazz = (function () { function Clazz() {} @@ -111,21 +111,21 @@ describe('scrub-file', () => { }()); `; - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); it('doesn\'t remove non Angular propDecorators', () => { - const input = stripIndent` + const input = tags.stripIndent` import { Input } from 'another-lib'; ${clazz} - Clazz.propDecorators = { \'ngIf\': [{ type: Input }] }; + Clazz.propDecorators = { 'ngIf': [{ type: Input }] }; `; - expect(oneLine`${transform(input)}`).toEqual(oneLine`${input}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${input}`); }); it('leaves non-Angular propDecorators in mixed arrays', () => { - const output = stripIndent` + const output = tags.stripIndent` import { Input } from '@angular/core'; import { NotInput } from 'another-lib'; ${clazz} @@ -133,7 +133,7 @@ describe('scrub-file', () => { 'notNgIf': [{ type: NotInput }] }; `; - const input = stripIndent` + const input = tags.stripIndent` import { Input } from '@angular/core'; import { NotInput } from 'another-lib'; ${clazz} @@ -143,59 +143,59 @@ describe('scrub-file', () => { }; `; - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); }); describe('ctorParameters', () => { it('removes empty constructor parameters', () => { - const output = stripIndent` + const output = tags.stripIndent` ${clazz} `; - const input = stripIndent` + const input = tags.stripIndent` ${output} Clazz.ctorParameters = function () { return []; }; `; expect(testScrubFile(input)).toBeTruthy(); - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); it('removes non-empty top-level style constructor parameters', () => { - const output = stripIndent` + const output = tags.stripIndent` ${clazz} `; - const input = stripIndent` + const input = tags.stripIndent` ${clazz} Clazz.ctorParameters = function () { return [{type: Injector}]; }; `; - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); it('removes top-level Angular constructor parameters in es2015', () => { - const output = stripIndent` + const output = tags.stripIndent` class Clazz extends BaseClazz { constructor(e) { super(e); } } `; - const input = stripIndent` + const input = tags.stripIndent` ${output} Clazz.ctorParameters = () => [ { type: Injectable } ]; `; expect(testScrubFile(input)).toBeTruthy(); - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); it('removes nested constructor parameters', () => { - const output = stripIndent` + const output = tags.stripIndent` import { Injector } from '@angular/core'; var Clazz = (function () { function Clazz() { } return Clazz; }()); `; - const input = stripIndent` + const input = tags.stripIndent` import { Injector } from '@angular/core'; var Clazz = (function () { function Clazz() {} @@ -204,16 +204,16 @@ describe('scrub-file', () => { }()); `; - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); it('doesn\'t remove constructor parameters from whitelisted classes', () => { - const input = stripIndent` + const input = tags.stripIndent` ${clazz.replace('Clazz', 'PlatformRef_')} PlatformRef_.ctorParameters = function () { return []; }; `; - expect(oneLine`${transform(input)}`).toEqual(oneLine`${input}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${input}`); }); }); }); diff --git a/packages/angular_devkit/build_optimizer/src/transforms/wrap-enums_spec.ts b/packages/angular_devkit/build_optimizer/src/transforms/wrap-enums_spec.ts index 8e97555cf7..04b3e5b510 100644 --- a/packages/angular_devkit/build_optimizer/src/transforms/wrap-enums_spec.ts +++ b/packages/angular_devkit/build_optimizer/src/transforms/wrap-enums_spec.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 { oneLine, stripIndent } from 'common-tags'; +import { tags } from '@angular-devkit/core'; import { transformJavascript } from '../helpers/transform-javascript'; import { getWrapEnumsTransformer, testWrapEnums } from './wrap-enums'; @@ -15,14 +15,14 @@ const transform = (content: string) => transformJavascript( describe('wrap-enums', () => { it('wraps ts 2.2 enums in IIFE', () => { - const input = stripIndent` + const input = tags.stripIndent` export var ChangeDetectionStrategy = {}; ChangeDetectionStrategy.OnPush = 0; ChangeDetectionStrategy.Default = 1; ChangeDetectionStrategy[ChangeDetectionStrategy.OnPush] = "OnPush"; ChangeDetectionStrategy[ChangeDetectionStrategy.Default] = "Default"; `; - const output = stripIndent` + const output = tags.stripIndent` export var ChangeDetectionStrategy = /*@__PURE__*/ (function () { var ChangeDetectionStrategy = {}; ChangeDetectionStrategy.OnPush = 0; @@ -34,18 +34,18 @@ describe('wrap-enums', () => { `; expect(testWrapEnums(input)).toBeTruthy(); - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); it('wraps ts 2.3 enums in IIFE', () => { - const input = stripIndent` + const input = tags.stripIndent` export var ChangeDetectionStrategy; (function (ChangeDetectionStrategy) { ChangeDetectionStrategy[ChangeDetectionStrategy["OnPush"] = 0] = "OnPush"; ChangeDetectionStrategy[ChangeDetectionStrategy["Default"] = 1] = "Default"; })(ChangeDetectionStrategy || (ChangeDetectionStrategy = {})); `; - const output = stripIndent` + const output = tags.stripIndent` export var ChangeDetectionStrategy = /*@__PURE__*/ (function () { var ChangeDetectionStrategy = {}; ChangeDetectionStrategy[ChangeDetectionStrategy["OnPush"] = 0] = "OnPush"; @@ -55,6 +55,6 @@ describe('wrap-enums', () => { `; expect(testWrapEnums(input)).toBeTruthy(); - expect(oneLine`${transform(input)}`).toEqual(oneLine`${output}`); + expect(tags.oneLine`${transform(input)}`).toEqual(tags.oneLine`${output}`); }); }); diff --git a/packages/angular_devkit/core/src/utils/literals.ts b/packages/angular_devkit/core/src/utils/literals.ts index 48299212dd..1dab7f2e1a 100644 --- a/packages/angular_devkit/core/src/utils/literals.ts +++ b/packages/angular_devkit/core/src/utils/literals.ts @@ -5,32 +5,38 @@ * 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-next-line:no-any +export type TemplateTag = (template: TemplateStringsArray, ...substitutions: any[]) => string; -export function oneLine(strings: TemplateStringsArray, ...values: string[]) { - const endResult = strings.map((s, i) => s + (i < values.length ? values[i] : '')).join(''); - return endResult.trim().replace(/\n\s*/gm, ' '); +// tslint:disable-next-line:no-any +export function oneLine(strings: TemplateStringsArray, ...values: any[]) { + const endResult = String.raw(strings, ...values); + + return endResult.replace(/(?:\n(?:\s*))+/gm, ' ').trim(); } -export function indentBy(indentations: number) { +export function indentBy(indentations: number): TemplateTag { let i = ''; while (indentations--) { i += ' '; } - return (strings: TemplateStringsArray, ...values: string[]) => { - return stripIndents(strings, ...values) + return (strings, ...values) => { + return stripIndent(strings, ...values) .replace(/\n/g, '\n' + i); }; } -export function stripIndents(strings: TemplateStringsArray, ...values: string[]) { - const endResult = strings.map((s, i) => s + (i < values.length ? values[i] : '')).join('').trim(); - // Remove the shortest leading indentation from each line. +// tslint:disable-next-line:no-any +export function stripIndent(strings: TemplateStringsArray, ...values: any[]) { + const endResult = String.raw(strings, ...values); + + // remove the shortest leading indentation from each line const match = endResult.match(/^[ \t]*(?=\S)/gm); - // Return early if there's nothing to strip. + // return early if there's nothing to strip if (match === null) { return endResult; } @@ -38,5 +44,15 @@ export function stripIndents(strings: TemplateStringsArray, ...values: string[]) const indent = Math.min(...match.map(el => el.length)); const regexp = new RegExp('^[ \\t]{' + indent + '}', 'gm'); - return (indent > 0 ? endResult.replace(regexp, '') : endResult).replace(/[ \t]*$/, ''); + return (indent > 0 ? endResult.replace(regexp, '') : endResult).trim(); +} + + +// tslint:disable-next-line:no-any +export function stripIndents(strings: TemplateStringsArray, ...values: any[]) { + return String.raw(strings, ...values) + .split('\n') + .map(line => line.trim()) + .join('\n') + .trim(); } diff --git a/packages/angular_devkit/core/src/utils/literals_spec.ts b/packages/angular_devkit/core/src/utils/literals_spec.ts index de528a3779..3b28d52332 100644 --- a/packages/angular_devkit/core/src/utils/literals_spec.ts +++ b/packages/angular_devkit/core/src/utils/literals_spec.ts @@ -5,9 +5,21 @@ * 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 { stripIndents } from './literals'; +import { oneLine, stripIndent, stripIndents } from './literals'; describe('literals', () => { + describe('stripIndent', () => { + it('works', () => { + const test = stripIndent` + hello world + how are you? + test + `; + + expect(test).toBe('hello world\n how are you?\ntest'); + }); + }); + describe('stripIndents', () => { it('works', () => { const test = stripIndents` @@ -16,7 +28,19 @@ describe('literals', () => { test `; - expect(test).toBe('\nhello world\n how are you?\ntest\n'); + expect(test).toBe('hello world\nhow are you?\ntest'); + }); + }); + + describe('oneLine', () => { + it('works', () => { + const test = oneLine` + hello world + how are you? blue red + test + `; + + expect(test).toBe('hello world how are you? blue red test'); }); }); }); diff --git a/packages/angular_devkit/schematics/bin/schematics.ts b/packages/angular_devkit/schematics/bin/schematics.ts index 1408da2d85..1dfa4f4347 100644 --- a/packages/angular_devkit/schematics/bin/schematics.ts +++ b/packages/angular_devkit/schematics/bin/schematics.ts @@ -29,7 +29,7 @@ import 'rxjs/add/operator/ignoreElements'; * Show usage of the CLI tool, and exit the process. */ function usage(exitCode = 0): never { - logger.info(tags.stripIndents` + logger.info(tags.stripIndent` schematics [CollectionName:]SchematicName [options, ...] By default, if the collection name is not specified, use the internal collection provided diff --git a/packages/schematics/angular/utility/ast-utils_spec.ts b/packages/schematics/angular/utility/ast-utils_spec.ts index 1b953ac5bb..9220d9af52 100644 --- a/packages/schematics/angular/utility/ast-utils_spec.ts +++ b/packages/schematics/angular/utility/ast-utils_spec.ts @@ -6,8 +6,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 { tags } from '@angular-devkit/core'; import { VirtualTree } from '@angular-devkit/schematics'; -import { stripIndent } from 'common-tags'; import * as ts from 'typescript'; import { Change, InsertChange } from '../utility/change'; import { getFileContent } from '../utility/test'; @@ -65,7 +65,7 @@ describe('ast utils', () => { }); it('should add export to module if not indented', () => { - moduleContent = stripIndent`${moduleContent}`; + moduleContent = tags.stripIndent`${moduleContent}`; const source = getTsSource(modulePath, moduleContent); const changes = addExportToModule(source, modulePath, 'FooComponent', './foo.component'); const output = applyChanges(modulePath, moduleContent, changes); From 18f1649e153ab288cc1676dc933bcc39f4ffb1a3 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Sun, 24 Sep 2017 16:19:23 -0700 Subject: [PATCH 23/33] refactor: require the rxjs operators necessary for devkit-admin --- bin/devkit-admin | 1 + packages/angular_devkit/core/{src/logger => node}/cli-logger.ts | 0 2 files changed, 1 insertion(+) rename packages/angular_devkit/core/{src/logger => node}/cli-logger.ts (100%) diff --git a/bin/devkit-admin b/bin/devkit-admin index 3e97882dde..df1cdf4033 100755 --- a/bin/devkit-admin +++ b/bin/devkit-admin @@ -34,6 +34,7 @@ process.chdir(path.join(__dirname, '..')); let logger = null; try { logger = new (require('@angular-devkit/core').IndentLogger)('root'); + require('rxjs/add/operator/filter'); logger .filter(entry => (entry.level !== 'debug' || args.verbose)) diff --git a/packages/angular_devkit/core/src/logger/cli-logger.ts b/packages/angular_devkit/core/node/cli-logger.ts similarity index 100% rename from packages/angular_devkit/core/src/logger/cli-logger.ts rename to packages/angular_devkit/core/node/cli-logger.ts From 2968fd0a090f5f34f79be554f3d38e1fda848915 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Sun, 24 Sep 2017 16:20:00 -0700 Subject: [PATCH 24/33] refactor: move cli-logger to node and rename the factory function --- packages/angular_devkit/core/node/cli-logger.ts | 16 +++++++--------- packages/angular_devkit/core/node/index.ts | 3 ++- packages/angular_devkit/core/src/logger/index.ts | 1 - .../angular_devkit/schematics/bin/schematics.ts | 5 +++-- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/angular_devkit/core/node/cli-logger.ts b/packages/angular_devkit/core/node/cli-logger.ts index 340249badb..bb25c719db 100644 --- a/packages/angular_devkit/core/node/cli-logger.ts +++ b/packages/angular_devkit/core/node/cli-logger.ts @@ -5,36 +5,34 @@ * 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 { IndentLogger, LogEntry, Logger, terminal } from '@angular-devkit/core'; import 'rxjs/add/operator/filter'; -import { bold, dim, red, white, yellow } from '../terminal'; -import { IndentLogger } from './indent'; -import { LogEntry, Logger } from './logger'; /** * A Logger that sends information to STDOUT and STDERR. */ -export function createLogger(verbose = false): Logger { +export function createConsoleLogger(verbose = false): Logger { const logger = new IndentLogger('cling'); logger .filter((entry: LogEntry) => (entry.level != 'debug' || verbose)) .subscribe((entry: LogEntry) => { - let color: (s: string) => string = x => dim(white(x)); + let color: (s: string) => string = x => terminal.dim(terminal.white(x)); let output = process.stdout; switch (entry.level) { case 'info': - color = white; + color = terminal.white; break; case 'warn': - color = yellow; + color = terminal.yellow; break; case 'error': - color = red; + color = terminal.red; output = process.stderr; break; case 'fatal': - color = (x: string) => bold(red(x)); + color = (x: string) => terminal.bold(terminal.red(x)); output = process.stderr; break; } diff --git a/packages/angular_devkit/core/node/index.ts b/packages/angular_devkit/core/node/index.ts index 1eeb86e461..d52441cfad 100644 --- a/packages/angular_devkit/core/node/index.ts +++ b/packages/angular_devkit/core/node/index.ts @@ -5,8 +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 * as fs from './fs'; + +export * from './cli-logger'; export * from './resolve'; export { fs }; diff --git a/packages/angular_devkit/core/src/logger/index.ts b/packages/angular_devkit/core/src/logger/index.ts index 71005eb72e..50fafbe700 100644 --- a/packages/angular_devkit/core/src/logger/index.ts +++ b/packages/angular_devkit/core/src/logger/index.ts @@ -5,7 +5,6 @@ * 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 */ -export * from './cli-logger'; export * from './indent'; export * from './logger'; export * from './null-logger'; diff --git a/packages/angular_devkit/schematics/bin/schematics.ts b/packages/angular_devkit/schematics/bin/schematics.ts index 1dfa4f4347..4fa0412c35 100644 --- a/packages/angular_devkit/schematics/bin/schematics.ts +++ b/packages/angular_devkit/schematics/bin/schematics.ts @@ -6,7 +6,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 { createLogger, schema, tags, terminal } from '@angular-devkit/core'; +import { schema, tags, terminal } from '@angular-devkit/core'; +import { createConsoleLogger } from '@angular-devkit/core/node'; import { DryRunEvent, DryRunSink, @@ -100,7 +101,7 @@ const argv = minimist(process.argv.slice(2), { }); /** Create the DevKit Logger used through the CLI. */ -const logger = createLogger(argv['verbose']); +const logger = createConsoleLogger(argv['verbose']); if (argv.help) { usage(); From cfcffa7bf541f14a0e07d497c4001c46209c19fa Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Mon, 25 Sep 2017 15:27:19 -0700 Subject: [PATCH 25/33] fix(@angular-devkit/core): fix an indentation issue with literals --- packages/angular_devkit/core/src/utils/literals.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/angular_devkit/core/src/utils/literals.ts b/packages/angular_devkit/core/src/utils/literals.ts index 1dab7f2e1a..13683c18a3 100644 --- a/packages/angular_devkit/core/src/utils/literals.ts +++ b/packages/angular_devkit/core/src/utils/literals.ts @@ -23,8 +23,7 @@ export function indentBy(indentations: number): TemplateTag { } return (strings, ...values) => { - return stripIndent(strings, ...values) - .replace(/\n/g, '\n' + i); + return i + stripIndent(strings, ...values).replace(/\n/g, '\n' + i); }; } From 390d75434ccf6f4bc7e41ddb4aed4f8650a5c542 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Mon, 25 Sep 2017 15:29:58 -0700 Subject: [PATCH 26/33] feat(@angular-devkit/core): add support for source map in templates Using the source-map library from mozilla. Its pretty neat. --- lib/bootstrap-local.js | 4 +- package-lock.json | 74 ++-- packages/angular_devkit/core/package.json | 1 + .../angular_devkit/core/src/utils/template.ts | 369 ++++++++++++++---- scripts/build.ts | 17 +- 5 files changed, 349 insertions(+), 116 deletions(-) diff --git a/lib/bootstrap-local.js b/lib/bootstrap-local.js index 774be40be7..c7c90323dd 100644 --- a/lib/bootstrap-local.js +++ b/lib/bootstrap-local.js @@ -88,9 +88,9 @@ require.extensions['.ts'] = function (m, filename) { require.extensions['.ejs'] = function (m, filename) { const source = fs.readFileSync(filename).toString(); const template = require('@angular-devkit/core').template; - const result = template(source, { sourceURL: filename, module: true }); + const result = template(source, { sourceURL: filename, sourceMap: true }); - return m._compile('module.exports.default = ' + result.toString(), filename); + return m._compile(result.source.replace(/return/, 'module.exports.default = '), filename); }; diff --git a/package-lock.json b/package-lock.json index 24ad9f558c..6941fe7af3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -111,15 +111,6 @@ "@types/source-map": "0.5.1" } }, - "JSONStream": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz", - "integrity": "sha1-cH92HgHa6eFvG8+TcDt4xwlmV5o=", - "requires": { - "jsonparse": "1.3.1", - "through": "2.3.8" - } - }, "abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", @@ -629,8 +620,8 @@ "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-2.0.0.tgz", "integrity": "sha512-8od6g684Fhi5Vpp4ABRv/RBsW1AY6wSHbJHEK6FGTv+8jvAAnlABniZu/FVmX9TcirkHepaEsa1QGkRvbg0CKw==", "requires": { - "JSONStream": "1.3.1", "is-text-path": "1.0.1", + "JSONStream": "1.3.1", "lodash": "4.17.4", "meow": "3.7.0", "split2": "2.1.1", @@ -1402,6 +1393,15 @@ "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" }, + "JSONStream": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz", + "integrity": "sha1-cH92HgHa6eFvG8+TcDt4xwlmV5o=", + "requires": { + "jsonparse": "1.3.1", + "through": "2.3.8" + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -1732,7 +1732,6 @@ "resolved": "https://registry.npmjs.org/npm/-/npm-5.4.2.tgz", "integrity": "sha512-F6LLCAHriKyKQ9Ff03UKCjkXZoRBp281I42K42+VeHfjAXZ3TJdg3RccinzoCFV1kDxCedVm7AstIpb1Uf5UkQ==", "requires": { - "JSONStream": "1.3.1", "abbrev": "1.1.0", "ansi-regex": "3.0.0", "ansicolors": "0.3.2", @@ -1762,6 +1761,7 @@ "inherits": "2.0.3", "ini": "1.3.4", "init-package-json": "1.10.1", + "JSONStream": "1.3.1", "lazy-property": "1.0.0", "libnpx": "9.6.0", "lockfile": "1.0.3", @@ -1832,24 +1832,6 @@ "write-file-atomic": "2.1.0" }, "dependencies": { - "JSONStream": { - "version": "1.3.1", - "bundled": true, - "requires": { - "jsonparse": "1.3.1", - "through": "2.3.8" - }, - "dependencies": { - "jsonparse": { - "version": "1.3.1", - "bundled": true - }, - "through": { - "version": "2.3.8", - "bundled": true - } - } - }, "abbrev": { "version": "1.1.0", "bundled": true @@ -2149,6 +2131,24 @@ } } }, + "JSONStream": { + "version": "1.3.1", + "bundled": true, + "requires": { + "jsonparse": "1.3.1", + "through": "2.3.8" + }, + "dependencies": { + "jsonparse": { + "version": "1.3.1", + "bundled": true + }, + "through": { + "version": "2.3.8", + "bundled": true + } + } + }, "lazy-property": { "version": "1.0.0", "bundled": true @@ -5269,6 +5269,14 @@ "duplexer": "0.1.1" } }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "requires": { + "safe-buffer": "5.1.1" + } + }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", @@ -5289,14 +5297,6 @@ "function-bind": "1.1.1" } }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "requires": { - "safe-buffer": "5.1.1" - } - }, "stringstream": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", diff --git a/packages/angular_devkit/core/package.json b/packages/angular_devkit/core/package.json index 185fe95f78..3ce547a395 100644 --- a/packages/angular_devkit/core/package.json +++ b/packages/angular_devkit/core/package.json @@ -8,5 +8,6 @@ "core" ], "dependencies": { + "source-map": "^0.5.6" } } diff --git a/packages/angular_devkit/core/src/utils/template.ts b/packages/angular_devkit/core/src/utils/template.ts index 92200a2ccd..21125cee58 100644 --- a/packages/angular_devkit/core/src/utils/template.ts +++ b/packages/angular_devkit/core/src/utils/template.ts @@ -5,6 +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 { Position, SourceNode } from 'source-map'; // Matches <%= expr %>. This does not support structural JavaScript (for/if/...). const kInterpolateRe = /<%=([\s\S]+?)%>/g; @@ -31,105 +32,329 @@ const reUnescapedHtml = new RegExp(`[${Object.keys(kHtmlEscapes).join('')}]`, 'g // Options to pass to template. export interface TemplateOptions { sourceURL?: string; + sourceMap?: boolean; + module?: boolean | { exports: {} }; + sourceRoot?: string; + fileName?: string; } -// Used to match empty string literals in compiled template source. -const reEmptyStringLeading = /\b__p \+= '';/g; -const reEmptyStringMiddle = /\b(__p \+=) '' \+/g; -const reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g; +function _positionFor(content: string, offset: number): Position { + let line = 1; + let column = 0; + for (let i = 0; i < offset - 1; i++) { + if (content[i] == '\n') { + line++; + column = 0; + } else { + column++; + } + } + return { + line, + column, + }; +} -// Used to escape characters for inclusion in compiled string literals. -const stringEscapes: {[char: string]: string} = { - '\\': '\\\\', - "'": "\\'", - '\n': '\\n', - '\r': '\\r', - '\u2028': '\\u2028', - '\u2029': '\\u2029', -}; +/** + * A simple AST for templates. There's only one level of AST nodes, but it's still useful + * to have the information you're looking for. + */ +export interface TemplateAst { + fileName: string; + content: string; + children: TemplateAstNode[]; +} -// Used to match unescaped characters in compiled string literals. -const reUnescapedString = /['\n\r\u2028\u2029\\]/g; +/** + * The base, which contains positions. + */ +export interface TemplateAstBase { + start: Position; + end: Position; +} +/** + * A static content node. + */ +export interface TemplateAstContent extends TemplateAstBase { + kind: 'content'; + content: string; +} /** - * An equivalent of lodash templates, which is based on John Resig's `tmpl` implementation - * (http://ejohn.org/blog/javascript-micro-templating/) and Laura Doktorova's doT.js - * (https://github.com/olado/doT). - * - * This version differs from lodash by removing support from ES6 quasi-literals, and making the - * code slightly simpler to follow. It also does not depend on any third party, which is nice. - * - * @param content - * @param options - * @return {any} + * An evaluate node, which is the code between `<% ... %>`. */ -export function template(content: string, options?: TemplateOptions): (input: T) => string { - const interpolate = kInterpolateRe; - let isEvaluating; - let index = 0; - let source = `__p += '`; +export interface TemplateAstEvaluate extends TemplateAstBase { + kind: 'evaluate'; + expression: string; +} - options = options || {}; +/** + * An escape node, which is the code between `<%- ... %>`. + */ +export interface TemplateAstEscape extends TemplateAstBase { + kind: 'escape'; + expression: string; +} + +/** + * An interpolation node, which is the code between `<%= ... %>`. + */ +export interface TemplateAstInterpolate extends TemplateAstBase { + kind: 'interpolate'; + expression: string; +} + +export type TemplateAstNode = TemplateAstContent + | TemplateAstEvaluate + | TemplateAstEscape + | TemplateAstInterpolate; + +/** + * Given a source text (and a fileName), returns a TemplateAst. + */ +export function templateParser(sourceText: string, fileName: string): TemplateAst { + const children = []; // Compile the regexp to match each delimiter. const reDelimiters = RegExp( - `${kEscapeRe.source}|${interpolate.source}|${kEvaluateRe.source}|$`, 'g'); + `${kEscapeRe.source}|${kInterpolateRe.source}|${kEvaluateRe.source}|$`, 'g'); - // Use a sourceURL for easier debugging. - const sourceURL = options.sourceURL ? '//# sourceURL=' + options.sourceURL + '\n' : ''; + const parsed = sourceText.split(reDelimiters); + let offset = 0; + // Optimization that uses the fact that the end of a node is always the beginning of the next + // node, so we keep the positioning of the nodes in memory. + let start = _positionFor(sourceText, offset); + let end = null as Position | null; - content.replace(reDelimiters, (match, escapeValue, interpolateValue, evaluateValue, offset) => { - // Escape characters that can't be included in string literals. - source += content.slice(index, offset).replace(reUnescapedString, chr => stringEscapes[chr]); - - // Replace delimiters with snippets. - if (escapeValue) { - source += `' +\n__e(${escapeValue}) +\n '`; + for (let i = 0; i < parsed.length; i += 4) { + const [content, escape, interpolate, evaluate] = parsed.slice(i, i + 4); + if (content) { + end = _positionFor(sourceText, offset + content.length); + offset += content.length; + children.push({ kind: 'content', content, start, end } as TemplateAstContent); + start = end; } - if (evaluateValue) { - isEvaluating = true; - source += `';\n${evaluateValue};\n__p += '`; + if (escape) { + end = _positionFor(sourceText, offset + escape.length + 5); + offset += escape.length + 5; + children.push({ kind: 'escape', expression: escape, start, end } as TemplateAstEscape); + start = end; } - if (interpolateValue) { - source += `' +\n((__t = (${interpolateValue})) == null ? '' : __t) +\n '`; + if (interpolate) { + end = _positionFor(sourceText, offset + interpolate.length + 5); + offset += interpolate.length + 5; + children.push({ + kind: 'interpolate', + expression: interpolate, + start, + end, + } as TemplateAstInterpolate); + start = end; } - index = offset + match.length; - - return match; - }); + if (evaluate) { + end = _positionFor(sourceText, offset + evaluate.length + 5); + offset += evaluate.length + 5; + children.push({ kind: 'evaluate', expression: evaluate, start, end } as TemplateAstEvaluate); + start = end; + } + } - source += "';\n"; + return { + fileName, + content: sourceText, + children, + }; +} - // Cleanup code by stripping empty strings. - source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source) - .replace(reEmptyStringMiddle, '$1') - .replace(reEmptyStringTrailing, '$1;'); +/** + * Fastest implementation of the templating algorithm. It only add strings and does not bother + * with source maps. + */ +function templateFast(ast: TemplateAst, options?: TemplateOptions): string { + const module = options && options.module ? 'module.exports.default =' : ''; + const reHtmlEscape = reUnescapedHtml.source.replace(/[']/g, '\\\\\\\''); - // Frame code as the function body. - source = ` - return function(obj) { - obj || (obj = {}); - let __t; - let __p = ''; + return ` + return ${module} function(obj) { + obj || (obj = {}); + let __t; + let __p = ''; + const __escapes = ${JSON.stringify(kHtmlEscapes)}; + const __escapesre = new RegExp('${reHtmlEscape}', 'g'); - const __escapes = ${JSON.stringify(kHtmlEscapes)}; - const __escapesre = new RegExp('${reUnescapedHtml.source.replace(/'/g, '\\\'')}', 'g'); + const __e = function(s) { + return s ? s.replace(__escapesre, function(key) { return __escapes[key]; }) : ''; + }; + with (obj) { + ${ast.children.map(node => { + switch (node.kind) { + case 'content': + return `__p += ${JSON.stringify(node.content)};`; + case 'interpolate': + return `__p += ((__t = (${node.expression})) == null) ? '' : __t;`; + case 'escape': + return `__p += __e(${node.expression});`; + case 'evaluate': + return node.expression; + } + }).join('\n') + } + } - const __e = function(s) { - return s ? s.replace(__escapesre, key => __escapes[key]) : ''; + return __p; }; - with (obj) { - ${source.replace(/\n/g, '\n ')} - } - return __p; - }; `; +} + +/** + * Templating algorithm with source map support. The map is outputted as //# sourceMapUrl=... + */ +function templateWithSourceMap(ast: TemplateAst, options?: TemplateOptions): string { + const sourceUrl = ast.fileName; + const module = options && options.module ? 'module.exports.default =' : ''; + const reHtmlEscape = reUnescapedHtml.source.replace(/[']/g, '\\\\\\\''); + + const preamble = (new SourceNode(1, 0, sourceUrl, '')) + .add(new SourceNode(1, 0, sourceUrl, [ + `return ${module} function(obj) {\n`, + ' obj || (obj = {});\n', + ' let __t;\n', + ' let __p = "";\n', + ` const __escapes = ${JSON.stringify(kHtmlEscapes)};\n`, + ` const __escapesre = new RegExp('${reHtmlEscape}', 'g');\n`, + `\n`, + ` const __e = function(s) { `, + ` return s ? s.replace(__escapesre, function(key) { return __escapes[key]; }) : '';`, + ` };\n`, + ` with (obj) {\n`, + ])); + + const end = ast.children.length + ? ast.children[ast.children.length - 1].end + : { line: 0, column: 0 }; + const nodes = ast.children.reduce((chunk, node) => { + let code: string | SourceNode | (SourceNode | string)[] = ''; + switch (node.kind) { + case 'content': + code = [ + new SourceNode(node.start.line, node.start.column, sourceUrl, '__p = __p'), + ...node.content.split('\n').map((line, i, arr) => { + return new SourceNode( + node.start.line + i, + i == 0 ? node.start.column : 0, + sourceUrl, + '\n + ' + + JSON.stringify(line + (i == arr.length - 1 ? '' : '\n')), + ); + }), + new SourceNode(node.end.line, node.end.column, sourceUrl, ';\n'), + ]; + break; + case 'interpolate': + code = [ + new SourceNode(node.start.line, node.start.column, sourceUrl, '__p += ((__t = '), + ...node.expression.split('\n').map((line, i, arr) => { + return new SourceNode( + node.start.line + i, + i == 0 ? node.start.column : 0, + sourceUrl, + line + ((i == arr.length - 1) ? '' : '\n'), + ); + }), + new SourceNode(node.end.line, node.end.column, sourceUrl, ') == null ? "" : __t);\n'), + ]; + break; + case 'escape': + code = [ + new SourceNode(node.start.line, node.start.column, sourceUrl, '__p += __e('), + ...node.expression.split('\n').map((line, i, arr) => { + return new SourceNode( + node.start.line + i, + i == 0 ? node.start.column : 0, + sourceUrl, + line + ((i == arr.length - 1) ? '' : '\n'), + ); + }), + new SourceNode(node.end.line, node.end.column, sourceUrl, ');\n'), + ]; + break; + case 'evaluate': + code = [ + ...node.expression.split('\n').map((line, i, arr) => { + return new SourceNode( + node.start.line + i, + i == 0 ? node.start.column : 0, + sourceUrl, + line + ((i == arr.length - 1) ? '' : '\n'), + ); + }), + new SourceNode(node.end.line, node.end.column, sourceUrl, '\n'), + ]; + break; + } + + return chunk.add(new SourceNode(node.start.line, node.start.column, sourceUrl, code)); + }, preamble) + .add(new SourceNode(end.line, end.column, sourceUrl, [ + ' };\n', + '\n', + ' return __p;\n', + '}\n', + ])); + + const code = nodes.toStringWithSourceMap({ + file: sourceUrl, + sourceRoot: options && options.sourceRoot || '.', + }); + + // Set the source content in the source map, otherwise the sourceUrl is not enough + // to find the content. + code.map.setSourceContent(sourceUrl, ast.content); + + return code.code + + '\n//# sourceMappingURL=data:application/json;base64,' + + new Buffer(code.map.toString()).toString('base64'); +} + + +/** + * An equivalent of EJS templates, which is based on John Resig's `tmpl` implementation + * (http://ejohn.org/blog/javascript-micro-templating/) and Laura Doktorova's doT.js + * (https://github.com/olado/doT). + * + * This version differs from lodash by removing support from ES6 quasi-literals, and making the + * code slightly simpler to follow. It also does not depend on any third party, which is nice. + * + * Finally, it supports SourceMap, if you ever need to debug, which is super nice. + * + * @param content The template content. + * @param options Optional Options. See TemplateOptions for more description. + * @return {(input: T) => string} A function that accept an input object and returns the content + * of the template with the input applied. + */ +export function template(content: string, options?: TemplateOptions): (input: T) => string { + const sourceUrl = options && options.sourceURL || 'ejs'; + const ast = templateParser(content, sourceUrl); + + let source: string; + // If there's no need for source map support, we revert back to the fast implementation. + if (options && options.sourceMap) { + source = templateWithSourceMap(ast, options); + } else { + source = templateFast(ast, options); + } - const fn = Function(sourceURL + source); - const result = fn(); + // We pass a dummy module in case the module option is passed. If `module: true` is passed, we + // need to only use the source, not the function itself. Otherwise expect a module object to be + // passed, and we use that one. + const fn = Function('module', source); + const module = options && options.module + ? (options.module === true ? { exports: {} } : options.module) + : null; + const result = fn(module); // Provide the compiled function's source by its `toString` method or // the `source` property as a convenience for inlining compiled templates. diff --git a/scripts/build.ts b/scripts/build.ts index 28e07ac9ca..1dc0835957 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -239,12 +239,19 @@ export default function(argv: { local?: boolean }, logger: Logger) { const files = glob.sync(path.join(pkg.dist, '**/*.ejs')); templateLogger.info(` ${files.length} ejs files found...`); files.forEach(fileName => { - const fn = templateCompiler(fs.readFileSync(fileName).toString()); - _rm(fileName); - fs.writeFileSync( - fileName.replace(/\.ejs$/, '.js'), - fn.source.replace(/^\s*return /, 'module.exports.default = '), + const p = path.relative( + path.dirname(__dirname), + path.join(pkg.root, path.relative(pkg.dist, fileName)), ); + const fn = templateCompiler(fs.readFileSync(fileName).toString(), { + module: true, + sourceURL: p, + sourceMap: true, + sourceRoot: path.join(__dirname, '..'), + fileName: fileName.replace(/\.ejs$/, '.js'), + }); + _rm(fileName); + fs.writeFileSync(fileName.replace(/\.ejs$/, '.js'), fn.source); }); } From aec53db44ac3689364f1ae48e662e5c6c827a0da Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Mon, 25 Sep 2017 16:42:28 -0700 Subject: [PATCH 27/33] refactor: simplify Logger in schematics context Before it was a full Logger, now we properly export a new LoggerApi that only contain logging APIs. We also added a facade to avoid recasting shenanigans. --- .../angular_devkit/core/src/logger/logger.ts | 29 ++++++++++++++++++- .../core/src/logger/logger_spec.ts | 18 ++++++++++++ .../core/src/logger/null-logger.ts | 14 ++++++++- .../schematics/bin/schematics.ts | 2 +- .../schematics/src/engine/interface.ts | 4 +-- 5 files changed, 62 insertions(+), 5 deletions(-) diff --git a/packages/angular_devkit/core/src/logger/logger.ts b/packages/angular_devkit/core/src/logger/logger.ts index ea193fa3ce..3e5736f834 100644 --- a/packages/angular_devkit/core/src/logger/logger.ts +++ b/packages/angular_devkit/core/src/logger/logger.ts @@ -22,11 +22,20 @@ export interface LogEntry extends LoggerMetadata { message: string; timestamp: number; } +export interface LoggerApi { + createChild(name: string): Logger; + log(level: LogLevel, message: string, metadata?: JsonObject): void; + debug(message: string, metadata?: JsonObject): void; + info(message: string, metadata?: JsonObject): void; + warn(message: string, metadata?: JsonObject): void; + error(message: string, metadata?: JsonObject): void; + fatal(message: string, metadata?: JsonObject): void; +} export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal'; -export class Logger extends Observable { +export class Logger extends Observable implements LoggerApi { protected readonly _subject: Subject = new Subject(); protected _metadata: LoggerMetadata; @@ -74,6 +83,24 @@ export class Logger extends Observable { } } + asApi(): LoggerApi { + return { + createChild: (name: string) => this.createChild(name), + log: (level: LogLevel, message: string, metadata?: JsonObject) => { + return this.log(level, message, metadata); + }, + debug: (message: string, metadata?: JsonObject) => this.debug(message, metadata), + info: (message: string, metadata?: JsonObject) => this.info(message, metadata), + warn: (message: string, metadata?: JsonObject) => this.warn(message, metadata), + error: (message: string, metadata?: JsonObject) => this.error(message, metadata), + fatal: (message: string, metadata?: JsonObject) => this.fatal(message, metadata), + }; + } + + createChild(name: string) { + return new Logger(name, this); + } + complete() { this._subject.complete(); } diff --git a/packages/angular_devkit/core/src/logger/logger_spec.ts b/packages/angular_devkit/core/src/logger/logger_spec.ts index 3dc03dd6b9..2bb404d32d 100644 --- a/packages/angular_devkit/core/src/logger/logger_spec.ts +++ b/packages/angular_devkit/core/src/logger/logger_spec.ts @@ -52,4 +52,22 @@ describe('Logger', () => { childLogger.info('world'); logger.complete(); }); + + it('misses messages if not subscribed', (done: DoneFn) => { + const logger = new Logger('test'); + logger.debug('woah'); + + logger + .toArray() + .toPromise() + .then((observed: JsonValue[]) => { + expect(observed).toEqual([ + jasmine.objectContaining({ message: 'hello', level: 'debug', name: 'test' }) as any, + ]); + }) + .then(() => done(), err => done.fail(err)); + + logger.debug('hello'); + logger.complete(); + }); }); diff --git a/packages/angular_devkit/core/src/logger/null-logger.ts b/packages/angular_devkit/core/src/logger/null-logger.ts index 10bcd58101..233b1d827e 100644 --- a/packages/angular_devkit/core/src/logger/null-logger.ts +++ b/packages/angular_devkit/core/src/logger/null-logger.ts @@ -7,7 +7,7 @@ */ import { Observable } from 'rxjs/Observable'; import 'rxjs/add/observable/empty'; -import { Logger } from './logger'; +import { Logger, LoggerApi } from './logger'; export class NullLogger extends Logger { @@ -15,4 +15,16 @@ export class NullLogger extends Logger { super('', parent); this._observable = Observable.empty(); } + + asApi(): LoggerApi { + return { + createChild: () => new NullLogger(this), + log() {}, + debug() {}, + info() {}, + warn() {}, + error() {}, + fatal() {}, + } as LoggerApi; + } } diff --git a/packages/angular_devkit/schematics/bin/schematics.ts b/packages/angular_devkit/schematics/bin/schematics.ts index 4fa0412c35..44e4d78735 100644 --- a/packages/angular_devkit/schematics/bin/schematics.ts +++ b/packages/angular_devkit/schematics/bin/schematics.ts @@ -244,7 +244,7 @@ delete args._; * Then we proceed to run the dryRun commit. We run this before we then commit to the filesystem * (if --dry-run was not passed or an error was detected by dryRun). */ -schematic.call(args, host, { debug, logger }) +schematic.call(args, host, { debug, logger: logger.asApi() }) .map((tree: Tree) => Tree.optimize(tree)) .concatMap((tree: Tree) => { return dryRunSink.commit(tree).ignoreElements().concat(Observable.of(tree)); diff --git a/packages/angular_devkit/schematics/src/engine/interface.ts b/packages/angular_devkit/schematics/src/engine/interface.ts index ea036e0c7b..5380c48f70 100644 --- a/packages/angular_devkit/schematics/src/engine/interface.ts +++ b/packages/angular_devkit/schematics/src/engine/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 { Logger } from '@angular-devkit/core'; +import { LoggerApi } from '@angular-devkit/core'; import { Observable } from 'rxjs/Observable'; import { Url } from 'url'; import { FileEntry, MergeStrategy, Tree } from '../tree/interface'; @@ -128,7 +128,7 @@ export interface TypedSchematicContext { readonly debug: boolean; readonly engine: Engine; - readonly logger: Logger; + readonly logger: LoggerApi; readonly schematic: Schematic; readonly strategy: MergeStrategy; } From 3d51bda357cbde6d61554f5f5cf5f4dbe696914b Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Mon, 25 Sep 2017 17:08:44 -0700 Subject: [PATCH 28/33] feat(@angular-devkit/schematics): rules return values are optional now This simplifies writing simple rules that only change the tree. --- .../angular_devkit/schematics/src/engine/interface.ts | 2 +- .../angular_devkit/schematics/src/engine/schematic.ts | 2 ++ .../schematics/src/engine/schematic_spec.ts | 2 +- packages/angular_devkit/schematics/src/rules/call.ts | 8 +++++--- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/angular_devkit/schematics/src/engine/interface.ts b/packages/angular_devkit/schematics/src/engine/interface.ts index 5380c48f70..1bdbd0e4f8 100644 --- a/packages/angular_devkit/schematics/src/engine/interface.ts +++ b/packages/angular_devkit/schematics/src/engine/interface.ts @@ -166,4 +166,4 @@ export type AsyncFileOperator = (tree: FileEntry) => Observable Tree | Observable; -export type Rule = (tree: Tree, context: SchematicContext) => Tree | Observable; +export type Rule = (tree: Tree, context: SchematicContext) => Tree | Observable | void; diff --git a/packages/angular_devkit/schematics/src/engine/schematic.ts b/packages/angular_devkit/schematics/src/engine/schematic.ts index f8645af385..37ff06cb01 100644 --- a/packages/angular_devkit/schematics/src/engine/schematic.ts +++ b/packages/angular_devkit/schematics/src/engine/schematic.ts @@ -55,6 +55,8 @@ export class SchematicImpl = { createContext: (schematic: Schematic<{}, {}>) => ({ engine, schematic, ...context }), transformOptions: (_: {}, options: {}) => options, defaultMergeStrategy: MergeStrategy.Default, -} as Engine; +} as {} as Engine; const collection = { name: 'collection', description: 'description', diff --git a/packages/angular_devkit/schematics/src/rules/call.ts b/packages/angular_devkit/schematics/src/rules/call.ts index 82c288ea23..358bd7a92c 100644 --- a/packages/angular_devkit/schematics/src/rules/call.ts +++ b/packages/angular_devkit/schematics/src/rules/call.ts @@ -16,7 +16,7 @@ import { VirtualTree } from '../tree/virtual'; * When a rule or source returns an invalid value. */ export class InvalidRuleResultException extends BaseException { - constructor(value: {}) { + constructor(value?: {}) { let v = 'Unknown Type'; if (value === undefined) { v = 'undefined'; @@ -56,13 +56,15 @@ export function callSource(source: Source, context: SchematicContext): Observabl export function callRule(rule: Rule, input: Observable, context: SchematicContext): Observable { - return input.mergeMap(i => { - const result = rule(i, context); + return input.mergeMap(inputTree => { + const result = rule(inputTree, context); if (result instanceof VirtualTree) { return Observable.of(result as Tree); } else if (result instanceof Observable) { return result; + } else if (result === undefined) { + return Observable.of(inputTree); } else { throw new InvalidRuleResultException(result); } From 3849357397b916d1e50bee4a0846e0ffd4be9c3c Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Mon, 25 Sep 2017 17:28:18 -0700 Subject: [PATCH 29/33] feat(@angular-devkit/schematics): logger of new context is child of parent --- packages/angular_devkit/schematics/src/engine/engine.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/angular_devkit/schematics/src/engine/engine.ts b/packages/angular_devkit/schematics/src/engine/engine.ts index fda1e4ec69..ff1df1a927 100644 --- a/packages/angular_devkit/schematics/src/engine/engine.ts +++ b/packages/angular_devkit/schematics/src/engine/engine.ts @@ -83,7 +83,8 @@ export class SchematicEngine Date: Mon, 25 Sep 2017 18:34:25 -0700 Subject: [PATCH 30/33] feat(@angular-devkit/schematics): have a schema for collection.json --- .../schematics/collection-schema.json | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 packages/angular_devkit/schematics/collection-schema.json diff --git a/packages/angular_devkit/schematics/collection-schema.json b/packages/angular_devkit/schematics/collection-schema.json new file mode 100644 index 0000000000..7021bbcb54 --- /dev/null +++ b/packages/angular_devkit/schematics/collection-schema.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/schema", + "id": "SchematicsCollectionSchema", + "title": "Collection Schema for validating a 'collection.json'.", + "type": "object", + "properties": { + "schematics": { + "type": "object", + "properties": { + "additionalProperty": { + "type": "object", + "properties": { + "factory": { + "type": "string" + }, + "description": { + "type": "string" + }, + "extends": { + "type": "string" + }, + "schema": { + "type": "string" + } + }, + "required": [ + "factory", + "description" + ] + } + } + }, + "version": { + "type": "string" + } + }, + "required": [ + "schematics" + ] +} From 78e386738ad4e10769dc22a71bacd072bf27d9df Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Mon, 25 Sep 2017 19:11:47 -0700 Subject: [PATCH 31/33] feat(@angular-devkit/schematics): use the core json schema for SchematicTestRunner --- .../schematics/test/schematic-test-runner.ts | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/angular_devkit/schematics/test/schematic-test-runner.ts b/packages/angular_devkit/schematics/test/schematic-test-runner.ts index 2e52d1b631..a61e7ec267 100644 --- a/packages/angular_devkit/schematics/test/schematic-test-runner.ts +++ b/packages/angular_devkit/schematics/test/schematic-test-runner.ts @@ -5,6 +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 { Logger, schema } from '@angular-devkit/core'; import { Collection, SchematicEngine, @@ -15,7 +16,6 @@ import { FileSystemSchematicDesc, NodeModulesTestEngineHost, } from '@angular-devkit/schematics/tools'; -import { SchemaClassFactory } from '@ngtools/json-schema'; import { Observable } from 'rxjs/Observable'; @@ -25,9 +25,13 @@ export class SchematicTestRunner { private _engineHost = new NodeModulesTestEngineHost(); private _engine: SchematicEngine<{}, {}> = new SchematicEngine(this._engineHost); private _collection: Collection<{}, {}>; + private _logger: Logger; + private _registry: schema.JsonSchemaRegistry; constructor(private _collectionName: string, collectionPath: string) { this._engineHost.registerCollection(_collectionName, collectionPath); + this._logger = new Logger('test'); + this._registry = new schema.JsonSchemaRegistry(); this._engineHost.registerOptionsTransform(( schematicDescription: {}, @@ -36,10 +40,13 @@ export class SchematicTestRunner { const schematic: FileSystemSchematicDesc = schematicDescription as FileSystemSchematicDesc; if (schematic.schema && schematic.schemaJson) { - const SchemaMetaClass = SchemaClassFactory(schematic.schemaJson); - const schemaClass = new SchemaMetaClass(opts); + const schemaJson = schematic.schemaJson as schema.JsonSchemaObject; + const name = schemaJson.id || schematic.name; + this._registry.addSchema(name, schemaJson); + const serializer = new schema.serializers.JavascriptSerializer(); + const fn = serializer.serialize(name, this._registry); - return schemaClass.$$root(); + return fn(opts); } return opts; @@ -48,11 +55,13 @@ export class SchematicTestRunner { this._collection = this._engine.createCollection(this._collectionName); } + get logger() { return this._logger; } + runSchematicAsync(schematicName: string, opts?: SchematicSchemaT, tree?: Tree): Observable { const schematic = this._collection.createSchematic(schematicName); const host = Observable.of(tree || new VirtualTree); - return schematic.call(opts || {}, host); + return schematic.call(opts || {}, host, { logger: this._logger }); } runSchematic(schematicName: string, opts?: SchematicSchemaT, tree?: Tree): Tree { @@ -61,7 +70,7 @@ export class SchematicTestRunner { let result: Tree | null = null; const host = Observable.of(tree || new VirtualTree); - schematic.call(opts || {}, host) + schematic.call(opts || {}, host, { logger: this._logger }) .subscribe(t => result = t); if (result === null) { From 3842600c3e1da96e815ca53e3b9da05e621a5300 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Tue, 26 Sep 2017 09:53:19 -0700 Subject: [PATCH 32/33] fix(@angular-devkit/schematics): use loose mode for collection.json So that the collection.json can include comments and other goodies. Note that the package.json is still using require() as it is a regular node file and would fail npm anyway, so no incentive for us to make it loose. --- .../angular_devkit/schematics/tools/node-module-engine-host.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/angular_devkit/schematics/tools/node-module-engine-host.ts b/packages/angular_devkit/schematics/tools/node-module-engine-host.ts index a69d4b507e..82af693c6b 100644 --- a/packages/angular_devkit/schematics/tools/node-module-engine-host.ts +++ b/packages/angular_devkit/schematics/tools/node-module-engine-host.ts @@ -19,6 +19,7 @@ import { } from './description'; import { ExportStringRef } from './export-ref'; import { FileSystemEngineHostBase } from './file-system-engine-host-base'; +import { readJsonFile } from './file-system-utility'; /** @@ -51,7 +52,7 @@ export class NodeModulesEngineHost extends FileSystemEngineHostBase { const pkgJsonSchematics = require(packageJsonPath)['schematics']; if (pkgJsonSchematics) { const resolvedPath = this._resolvePath(pkgJsonSchematics, dirname(packageJsonPath), false); - require(resolvedPath); + readJsonFile(resolvedPath); return resolvedPath; } From 494f87d434932a8baab23cd64bbcfe9f5c600b1b Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Tue, 26 Sep 2017 12:45:27 -0700 Subject: [PATCH 33/33] feat(@angular-devkit/core): add support for schema in template Before we were messing with ownKeys to only return defined properties, but that has the side effect that those will not work inside a "with() {}" clause, which is what templates are using. We now need a serialize symbol if we want to only serialize defined properties, but for now parity with @ngtools/json-schema is not a priority. --- .../templates/javascript/prop-object.ejs | 18 +++++++++++++++--- .../schema/serializers/1.0.javascript_spec.ts | 5 ++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-object.ejs b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-object.ejs index 8242cc1fa5..eb2412e3cb 100644 --- a/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-object.ejs +++ b/packages/angular_devkit/core/src/json/schema/serializers/templates/javascript/prop-object.ejs @@ -37,9 +37,13 @@ const handlers = Object.create(null); } %> +const objectFunctions = { + hasOwnProperty(name) { return objectProxyHandler.has(null, name); }, +}; + let defined = false; -const proxy = new Proxy({}, { +const objectProxyHandler = { isExtensible() { return false; }, has(target, prop) { return (prop in handlers && handlers[prop].isDefined()) @@ -54,6 +58,9 @@ const proxy = new Proxy({}, { if (prop in handlers) { return handlers[prop].get(); } + if (prop in objectFunctions) { + return objectFunctions[prop]; + } return undefined; }, set(target, prop, v) { @@ -90,15 +97,20 @@ const proxy = new Proxy({}, { getOwnPropertyDescriptor(target, prop) { if (prop in handlers) { return { configurable: true, enumerable: true }; + } else if (additionalPropertyHandler && prop in additionalPropertyHandler) { + return { configurable: true, enumerable: true }; } }, ownKeys(target) { return [].concat( - Object.keys(handlers).filter(function(key) { return handlers[key].isDefined(); }), + Object.keys(handlers), additionalPropertyHandler ? Object.keys(additionalProperties) : [] ); }, -}); +}; + + +const proxy = new Proxy({}, objectProxyHandler); const objectHandler = { set(v) { diff --git a/tests/@angular_devkit/core/json/schema/serializers/1.0.javascript_spec.ts b/tests/@angular_devkit/core/json/schema/serializers/1.0.javascript_spec.ts index ec8e7d8038..9d37e38db4 100644 --- a/tests/@angular_devkit/core/json/schema/serializers/1.0.javascript_spec.ts +++ b/tests/@angular_devkit/core/json/schema/serializers/1.0.javascript_spec.ts @@ -36,7 +36,7 @@ export function works(registry: schema.JsonSchemaRegistry, schema: any) { v.objectKey1.objectKey = { stringKey: 'str2' }; expect(v.objectKey1.objectKey.stringKey).toBe('str2'); - expect(Object.keys(v.objectKey1)).toEqual(['stringKey', 'objectKey']); + expect(Object.keys(v.objectKey1)).toEqual(['stringKey', 'stringKeyDefault', 'objectKey']); } @@ -55,9 +55,8 @@ export function accessUndefined(registry: schema.JsonSchemaRegistry, schema: any expect(v.objectKey1).not.toBe(undefined); expect(v.objectKey1.stringKey).toBe('hello'); expect(v.objectKey1.numberKey).toBe(undefined); - expect(v).toEqual({ 'requiredKey': 1, 'objectKey1': { 'stringKey': 'hello' } }); v.objectKey1.stringKey = undefined; - expect(v).toEqual({ 'requiredKey': 1 }); + expect(v.objectKey1.stringKey).toBe(undefined); expect(v.stringKeyDefault).toBe('defaultValue'); expect(v.objectKey1.stringKeyDefault).toBe('defaultValue2');