diff --git a/e2e/schematics/ngrx.test.ts b/e2e/schematics/ngrx.test.ts index b4fba9ca67..cddbab953e 100644 --- a/e2e/schematics/ngrx.test.ts +++ b/e2e/schematics/ngrx.test.ts @@ -1,4 +1,4 @@ -import { newApp, newProject, runCLI, updateFile } from '../utils'; +import { newApp, newProject, runCLI } from '../utils'; describe('ngrx', () => { it( @@ -9,47 +9,11 @@ describe('ngrx', () => { runCLI( 'generate ngrx app --module=apps/myapp/src/app/app.module.ts --root --collection=@nrwl/schematics' ); - updateFile( - 'apps/myapp/src/app/+state/app.interfaces.ts', - ` - export interface App { - rootCount: number; - } - export interface AppState { - readonly app: App; - } - ` - ); - updateFile( - 'apps/myapp/src/app/+state/app.init.ts', - ` - import { App } from './app.interfaces'; - export const appInitialState: App = { - rootCount: 0 - }; - ` - ); + console.log('build'); + console.log(runCLI('build')); - updateFile( - 'apps/myapp/src/app/+state/app.reducer.spec.ts', - ` - import { appReducer } from './app.reducer'; - import { App } from './app.interfaces'; - import { DataLoaded } from './app.actions'; - - describe('appReducer', () => { - it('should work', () => { - const state: App = {rootCount: 0}; - const action: DataLoaded = {type: 'DATA_LOADED', payload: {}}; - const actual = appReducer(state, action); - expect(actual).toEqual({rootCount: 0}); - }); - }); - ` - ); - - runCLI('build'); + console.log('test'); runCLI('test --single-run'); }, 1000000 diff --git a/e2e/schematics/workspace.test.ts b/e2e/schematics/workspace.test.ts index a72382c311..68c4c6f7c1 100644 --- a/e2e/schematics/workspace.test.ts +++ b/e2e/schematics/workspace.test.ts @@ -5,8 +5,8 @@ import { runCLI, runNgNew, updateFile, - readJson, - readFile + readFile, + readJson } from '../utils'; import { angularCliSchema } from '../../packages/schematics/src/lib-versions'; diff --git a/e2e/utils.ts b/e2e/utils.ts index b0b8c488b3..6da3593246 100644 --- a/e2e/utils.ts +++ b/e2e/utils.ts @@ -53,7 +53,8 @@ export function copyMissingPackages(): void { 'jasmine-marbles', '@nrwl', 'angular', - '@angular/upgrade' + '@angular/upgrade', + 'npm-run-all' ]; modulesToCopy.forEach(m => copyNodeModule(projectName, m)); } diff --git a/package.json b/package.json index 3c2dc3f57f..bc762f2922 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "@angular/upgrade": "5.2.7", "@ngrx/effects": "5.2.0", "@ngrx/router-store": "5.2.0", + "@ngrx/schematics": "^5.2.0", "@ngrx/store": "5.2.0", "@ngrx/store-devtools": "5.2.0", "@types/jasmine": "~2.8.3", @@ -67,6 +68,9 @@ "author": "Victor Savkin", "license": "MIT", "jest": { - "modulePathIgnorePatterns": ["tmp", "files"] + "modulePathIgnorePatterns": [ + "tmp", + "files" + ] } } diff --git a/packages/schematics/package.json b/packages/schematics/package.json index 91bb7d1660..2e2a5222b6 100644 --- a/packages/schematics/package.json +++ b/packages/schematics/package.json @@ -2,21 +2,42 @@ "name": "@nrwl/schematics", "version": "0.0.1", "description": "Nrwl Extensions for Angular: Schematics", + "repository": { + "type": "git", + "url": "git+https://github.com/nrwl/nx.git" + }, + "keywords": [ + "RxJS", + "Angular", + "Workspace", + "NgRx", + "Schematics", + "Angular CLI" + ], "bin": { "create-nx-workspace": "./bin/create-nx-workspace.js", "nx": "./src/command-line/nx.js" }, "main": "index.js", "types": "index.d.js", - "peerDependencies": {}, + "peerDependencies": { }, "author": "Victor Savkin", "license": "MIT", + "bugs": { + "url": "https://github.com/nrwl/nx/issues" + }, + "homepage": "https://github.com/nrwl/nx#readme", "schematics": "./src/collection.json", "dependencies": { + "@ngrx/schematics": "5.2.0", + "@schematics/angular": "0.4.6", "app-root-path": "^2.0.1", "npm-run-all": "4.1.2", "semver": "5.4.1", "tmp": "0.0.33", "yargs-parser": "9.0.2" + }, + "devDependencies": { + "@angular-devkit/schematics": "0.4.6" } } diff --git a/packages/schematics/src/README.md b/packages/schematics/src/README.md new file mode 100644 index 0000000000..51212ada27 --- /dev/null +++ b/packages/schematics/src/README.md @@ -0,0 +1,6 @@ +@nrwl/schematics +======= + +The sources for this package are in the main [nrwl/nx](https://github.com/nrwl/nx) repo. Please file issues and pull requests against that repo. + +License: MIT diff --git a/packages/schematics/src/collection.json b/packages/schematics/src/collection.json index 0a155b3e59..7eef7fd525 100644 --- a/packages/schematics/src/collection.json +++ b/packages/schematics/src/collection.json @@ -1,7 +1,7 @@ { "name": "nx", "version": "0.1", - "extends": "@schematics/angular", + "extends": "@ngrx/schematics", "schematics": { "workspace": { "factory": "./collection/workspace", diff --git a/packages/schematics/src/collection/application/files/__directory__/package.json b/packages/schematics/src/collection/application/files/__directory__/package.json old mode 100644 new mode 100755 index 1763b6e807..a4d775b72f --- a/packages/schematics/src/collection/application/files/__directory__/package.json +++ b/packages/schematics/src/collection/application/files/__directory__/package.json @@ -47,6 +47,7 @@ "devDependencies": { "@angular/cli": "<%= angularCliVersion %>", "@angular/compiler-cli": "<%= angularVersion %>", + "@ngrx/schematics": "<%= ngrxSchematicsVersion %>", "@nrwl/schematics": "<%= schematicsVersion %>", "@angular/language-service": "<%= angularVersion %>",<% if (!minimal) { %> "@types/jasmine": "~2.8.3", diff --git a/packages/schematics/src/collection/ngrx/files/__directory__/__fileName__.actions.ts__tmpl__ b/packages/schematics/src/collection/ngrx/files/__directory__/__fileName__.actions.ts__tmpl__ deleted file mode 100644 index c18a0c45f6..0000000000 --- a/packages/schematics/src/collection/ngrx/files/__directory__/__fileName__.actions.ts__tmpl__ +++ /dev/null @@ -1,12 +0,0 @@ -export interface LoadData { - type: 'LOAD_DATA'; - payload: {}; -} - -export interface DataLoaded { - type: 'DATA_LOADED'; - payload: {}; -} - -export type <%= className %>Action = LoadData | DataLoaded; - diff --git a/packages/schematics/src/collection/ngrx/files/__directory__/__fileName__.effects.spec.ts__tmpl__ b/packages/schematics/src/collection/ngrx/files/__directory__/__fileName__.effects.spec.ts__tmpl__ index 87b67246ad..7281ab1233 100644 --- a/packages/schematics/src/collection/ngrx/files/__directory__/__fileName__.effects.spec.ts__tmpl__ +++ b/packages/schematics/src/collection/ngrx/files/__directory__/__fileName__.effects.spec.ts__tmpl__ @@ -1,13 +1,17 @@ -import {TestBed, async} from '@angular/core/testing'; +import {TestBed} from '@angular/core/testing'; import {StoreModule} from '@ngrx/store'; import {provideMockActions} from '@ngrx/effects/testing'; import {DataPersistence} from '@nrwl/nx'; import {hot} from '@nrwl/nx/testing'; + import {<%= className %>Effects} from './<%= fileName %>.effects'; +import {Load<%= className %>, <%= className %>Loaded } from './<%= fileName %>.actions'; + +import { Observable } from 'rxjs/Observable'; describe('<%= className %>Effects', () => { - let actions; - let effects: <%= className %>Effects; + let actions$: Observable; + let effects$: <%= className %>Effects; beforeEach(() => { TestBed.configureTestingModule({ @@ -17,18 +21,18 @@ describe('<%= className %>Effects', () => { providers: [ <%= className %>Effects, DataPersistence, - provideMockActions(() => actions) + provideMockActions(() => actions$) ], }); - effects = TestBed.get(<%= className %>Effects); + effects$ = TestBed.get(<%= className %>Effects); }); describe('someEffect', () => { it('should work', () => { - actions = hot('-a-|', {a: {type: 'LOAD_DATA'}}); - expect(effects.loadData).toBeObservable( - hot('-a-|', {a: {type: 'DATA_LOADED', payload: {}}}) + actions$ = hot('-a-|', {a: new Load<%= className %>({})}); + expect(effects$.load<%= className %>$).toBeObservable( + hot('-a-|', {a: new <%= className %>Loaded({})}) ); }); }); diff --git a/packages/schematics/src/collection/ngrx/files/__directory__/__fileName__.effects.ts__tmpl__ b/packages/schematics/src/collection/ngrx/files/__directory__/__fileName__.effects.ts__tmpl__ deleted file mode 100644 index 1967582a04..0000000000 --- a/packages/schematics/src/collection/ngrx/files/__directory__/__fileName__.effects.ts__tmpl__ +++ /dev/null @@ -1,25 +0,0 @@ -import {Injectable} from '@angular/core'; -import {Effect, Actions} from '@ngrx/effects'; -import {DataPersistence} from '@nrwl/nx'; -import {of} from 'rxjs/observable/of'; -import 'rxjs/add/operator/switchMap'; -import {<%= className %>State} from './<%= fileName %>.interfaces'; -import {LoadData, DataLoaded} from './<%= fileName %>.actions'; - -@Injectable() -export class <%= className %>Effects { - @Effect() loadData = this.dataPersistence.fetch('LOAD_DATA', { - run: (action: LoadData, state: <%= className %>State) => { - return { - type: 'DATA_LOADED', - payload: {} - }; - }, - - onError: (action: LoadData, error) => { - console.error('Error', error); - } - }); - - constructor(private actions: Actions, private dataPersistence: DataPersistence<<%= className %>State>) {} -} diff --git a/packages/schematics/src/collection/ngrx/files/__directory__/__fileName__.init.ts__tmpl__ b/packages/schematics/src/collection/ngrx/files/__directory__/__fileName__.init.ts__tmpl__ deleted file mode 100644 index 25de5db4b3..0000000000 --- a/packages/schematics/src/collection/ngrx/files/__directory__/__fileName__.init.ts__tmpl__ +++ /dev/null @@ -1,5 +0,0 @@ -import {<%= className %>} from './<%= fileName %>.interfaces'; - -export const <%= propertyName %>InitialState: <%= className %> = { - // fill it initial state here -}; diff --git a/packages/schematics/src/collection/ngrx/files/__directory__/__fileName__.interfaces.ts__tmpl__ b/packages/schematics/src/collection/ngrx/files/__directory__/__fileName__.interfaces.ts__tmpl__ deleted file mode 100644 index f26b28659a..0000000000 --- a/packages/schematics/src/collection/ngrx/files/__directory__/__fileName__.interfaces.ts__tmpl__ +++ /dev/null @@ -1,7 +0,0 @@ -export interface <%= className %> { - // define state here -} - -export interface <%= className %>State { - readonly <%= propertyName %>: <%= className %>; -} diff --git a/packages/schematics/src/collection/ngrx/files/__directory__/__fileName__.reducer.spec.ts__tmpl__ b/packages/schematics/src/collection/ngrx/files/__directory__/__fileName__.reducer.spec.ts__tmpl__ index de7aab3e7b..f77a4ef473 100644 --- a/packages/schematics/src/collection/ngrx/files/__directory__/__fileName__.reducer.spec.ts__tmpl__ +++ b/packages/schematics/src/collection/ngrx/files/__directory__/__fileName__.reducer.spec.ts__tmpl__ @@ -1,13 +1,10 @@ -import { <%= propertyName %>Reducer } from './<%= fileName %>.reducer'; -import { <%= propertyName %>InitialState } from './<%= fileName %>.init'; -import { <%= className %> } from './<%= fileName %>.interfaces'; -import { DataLoaded } from './<%= fileName %>.actions'; +import { <%= className %>Loaded } from './<%= fileName %>.actions'; +import { <%= propertyName %>Reducer, initialState } from './<%= fileName %>.reducer'; describe('<%= propertyName %>Reducer', () => { it('should work', () => { - const state: <%= className %> = {}; - const action: DataLoaded = {type: 'DATA_LOADED', payload: {}}; - const actual = <%= propertyName %>Reducer(state, action); + const action: <%= className %>Loaded = new <%= className %>Loaded({}); + const actual = <%= fileName %>Reducer(initialState, action); expect(actual).toEqual({}); }); }); diff --git a/packages/schematics/src/collection/ngrx/files/__directory__/__fileName__.reducer.ts__tmpl__ b/packages/schematics/src/collection/ngrx/files/__directory__/__fileName__.reducer.ts__tmpl__ deleted file mode 100644 index 1e5643b735..0000000000 --- a/packages/schematics/src/collection/ngrx/files/__directory__/__fileName__.reducer.ts__tmpl__ +++ /dev/null @@ -1,13 +0,0 @@ -import {<%= className %>} from './<%= fileName %>.interfaces'; -import {<%= className %>Action} from './<%= fileName %>.actions'; - -export function <%= propertyName %>Reducer(state: <%= className %>, action: <%= className %>Action): <%= className %> { - switch (action.type) { - case 'DATA_LOADED': { - return {...state, ...action.payload}; - } - default: { - return state; - } - } -} diff --git a/packages/schematics/src/collection/ngrx/index.ts b/packages/schematics/src/collection/ngrx/index.ts index a4c104e1ad..3883a13a50 100644 --- a/packages/schematics/src/collection/ngrx/index.ts +++ b/packages/schematics/src/collection/ngrx/index.ts @@ -2,238 +2,116 @@ import { apply, branchAndMerge, chain, + externalSchematic, mergeWith, move, noop, Rule, template, - Tree, url } from '@angular-devkit/schematics'; +import { Tree } from '@angular-devkit/schematics'; -import { - names, - toClassName, - toFileName, - toPropertyName -} from '../../utils/name-utils'; -import * as path from 'path'; -import * as ts from 'typescript'; -import { - addImportToModule, - addProviderToModule, - insert, - updateJson -} from '../../utils/ast-utils'; -import { insertImport } from '@schematics/angular/utility/route-utils'; import { Schema } from './schema'; -import { - ngrxVersion, - routerStoreVersion, - ngrxStoreFreezeVersion -} from '../../lib-versions'; +import * as path from 'path'; + +import { names, toFileName } from '../../utils/name-utils'; import { wrapIntoFormat } from '../../utils/tasks'; -function addImportsToModule(name: string, options: Schema): Rule { - return (host: Tree) => { - if (options.onlyAddFiles) { - return host; - } +import { + RequestContext, + updateNgrxReducers, + updateNgrxActions, + updateNgrxEffects, + addImportsToModule, + addNgRxToPackageJson +} from './rules'; +import { deleteFile } from '../../utils/rules/deleteFile'; - if (!host.exists(options.module)) { - throw new Error('Specified module does not exist'); - } +/** + * Rule to generate the Nx 'ngrx' Collection + */ +export default function generateNgrxCollection(_options: Schema): Rule { + return wrapIntoFormat((host: Tree) => { + const options = normalizeOptions(_options); + const context: RequestContext = { + featureName: options.name, + moduleDir: path.dirname(options.module), + options, + host + }; - const modulePath = options.module; + return chain([ + branchAndMerge(generateNgrxFiles(context)), + branchAndMerge(generateNxFiles(context)), - const sourceText = host.read(modulePath)!.toString('utf-8'); - const source = ts.createSourceFile( - modulePath, - sourceText, - ts.ScriptTarget.Latest, - true - ); + addImportsToModule(context), - if (options.onlyEmptyRoot) { - insert(host, modulePath, [ - insertImport(source, modulePath, 'StoreModule', '@ngrx/store'), - insertImport(source, modulePath, 'EffectsModule', '@ngrx/effects'), - insertImport( - source, - modulePath, - 'StoreDevtoolsModule', - '@ngrx/store-devtools' - ), - insertImport( - source, - modulePath, - 'environment', - '../environments/environment' - ), - insertImport( - source, - modulePath, - 'StoreRouterConnectingModule', - '@ngrx/router-store' - ), - insertImport(source, modulePath, 'storeFreeze', 'ngrx-store-freeze'), - ...addImportToModule( - source, - modulePath, - `StoreModule.forRoot({},{metaReducers: !environment.production ? [storeFreeze] : []})` - ), - ...addImportToModule(source, modulePath, `EffectsModule.forRoot([])`), - ...addImportToModule( - source, - modulePath, - `!environment.production ? StoreDevtoolsModule.instrument() : []` - ), - ...addImportToModule(source, modulePath, `StoreRouterConnectingModule`) - ]); - return host; - } else { - const reducerPath = `./${toFileName(options.directory)}/${toFileName( - name - )}.reducer`; - const effectsPath = `./${toFileName(options.directory)}/${toFileName( - name - )}.effects`; - const initPath = `./${toFileName(options.directory)}/${toFileName( - name - )}.init`; + updateNgrxActions(context), + updateNgrxReducers(context), + updateNgrxEffects(context), - const reducerName = `${toPropertyName(name)}Reducer`; - const effectsName = `${toClassName(name)}Effects`; - const initName = `${toPropertyName(name)}InitialState`; - - const common = [ - insertImport(source, modulePath, 'StoreModule', '@ngrx/store'), - insertImport(source, modulePath, 'EffectsModule', '@ngrx/effects'), - insertImport(source, modulePath, reducerName, reducerPath), - insertImport(source, modulePath, initName, initPath), - insertImport(source, modulePath, effectsName, effectsPath), - ...addProviderToModule(source, modulePath, effectsName) - ]; - - if (options.root) { - insert(host, modulePath, [ - ...common, - insertImport( - source, - modulePath, - 'StoreDevtoolsModule', - '@ngrx/store-devtools' - ), - insertImport( - source, - modulePath, - 'environment', - '../environments/environment' - ), - insertImport( - source, - modulePath, - 'StoreRouterConnectingModule', - '@ngrx/router-store' - ), - insertImport(source, modulePath, 'storeFreeze', 'ngrx-store-freeze'), - ...addImportToModule( - source, - modulePath, - `StoreModule.forRoot({${toPropertyName(name)}: ${reducerName}}, { - initialState: {${toPropertyName(name)}: ${initName}}, - metaReducers: !environment.production ? [storeFreeze] : [] - })` - ), - ...addImportToModule( - source, - modulePath, - `EffectsModule.forRoot([${effectsName}])` - ), - ...addImportToModule( - source, - modulePath, - `!environment.production ? StoreDevtoolsModule.instrument() : []` - ), - ...addImportToModule( - source, - modulePath, - `StoreRouterConnectingModule` - ) - ]); - } else { - insert(host, modulePath, [ - ...common, - ...addImportToModule( - source, - modulePath, - `StoreModule.forFeature('${toPropertyName( - name - )}', ${reducerName}, {initialState: ${initName}})` - ), - ...addImportToModule( - source, - modulePath, - `EffectsModule.forFeature([${effectsName}])` - ) - ]); - } - - return host; - } - }; -} - -function addNgRxToPackageJson(): Rule { - return updateJson('package.json', packageJson => { - if (!packageJson['dependencies']) { - packageJson['dependencies'] = {}; - } - - if (!packageJson['dependencies']['@ngrx/store']) { - packageJson['dependencies']['@ngrx/store'] = ngrxVersion; - } - if (!packageJson['dependencies']['@ngrx/router-store']) { - packageJson['dependencies']['@ngrx/router-store'] = routerStoreVersion; - } - if (!packageJson['dependencies']['@ngrx/effects']) { - packageJson['dependencies']['@ngrx/effects'] = ngrxVersion; - } - if (!packageJson['dependencies']['@ngrx/store-devtools']) { - packageJson['dependencies']['@ngrx/store-devtools'] = ngrxVersion; - } - if (!packageJson['dependencies']['ngrx-store-freeze']) { - packageJson['dependencies']['ngrx-store-freeze'] = ngrxStoreFreezeVersion; - } - return packageJson; + options.skipPackageJson ? noop() : addNgRxToPackageJson() + ]); }); } -export default function(schema: Schema): Rule { - return wrapIntoFormat(() => { - const options = normalizeOptions(schema); - const name = options.name; - const moduleDir = path.dirname(options.module); +// ******************************************************** +// Internal Function +// ******************************************************** - if (options.onlyEmptyRoot) { - return chain([ - addImportsToModule(name, options), - options.skipPackageJson ? noop() : addNgRxToPackageJson() - ]); - } else { - const templateSource = apply(url('./files'), [ - template({ ...options, tmpl: '', ...names(name) }), - move(moduleDir) - ]); - return chain([ - branchAndMerge(chain([mergeWith(templateSource)])), - addImportsToModule(name, options), - options.skipPackageJson ? noop() : addNgRxToPackageJson() - ]); - } - }); +/** + * Generate the Nx files that are NOT created by the @ngrx/schematic(s) + */ +function generateNxFiles(context: RequestContext) { + const templateSource = apply(url('./files'), [ + template({ ...context.options, tmpl: '', ...names(context.featureName) }), + move(context.moduleDir) + ]); + return chain([mergeWith(templateSource)]); } +/** + * Using @ngrx/schematics, generate scaffolding for 'feature': action, reducer, effect files + */ +function generateNgrxFiles(context: RequestContext) { + const filePrefix = `app/${context.featureName}/${context.featureName}`; + + return chain([ + externalSchematic('@ngrx/schematics', 'feature', { + name: context.featureName, + sourceDir: './', + flat: false + }), + deleteFile(`/${filePrefix}.effects.spec.ts`), + deleteFile(`/${filePrefix}.reducer.spec.ts`), + moveToNxMonoTree( + context.featureName, + context.moduleDir, + context.options.directory + ) + ]); +} + +/** + * @ngrx/schematics generates files in: + * `/apps//` + * + * For Nx monorepo, however, we need to move the files to either + * a) apps//src/app/, or + * b) libs//src/ + */ +function moveToNxMonoTree( + ngrxFeatureName: string, + nxDir: string, + directory: string +): Rule { + return move(`app/${ngrxFeatureName}`, path.join(nxDir, directory)); +} + +/** + * Extract the parent 'directory' for the specified + */ function normalizeOptions(options: Schema): Schema { return { ...options, directory: toFileName(options.directory) }; } diff --git a/packages/schematics/src/collection/ngrx/ngrx.md b/packages/schematics/src/collection/ngrx/ngrx.md new file mode 100644 index 0000000000..a5ef847e5b --- /dev/null +++ b/packages/schematics/src/collection/ngrx/ngrx.md @@ -0,0 +1,203 @@ +# ngrx +-------- + +## Overview + +Generates a ngrx feature set containing an `init`, `interfaces`, `actions`, `reducer` and `effects` files. + +You use this schematic to build out a new ngrx feature area that provides a new piece of state. + +## Command + +```sh +ng generate ngrx FeatureName [options] +``` + +##### OR + +```sh +ng generate f FeatureName [options] +``` + +### Options + +Specifies the name of the ngrx feature (e.g., Products, User, etc.) + +- `name` + - Type: `string` + - Required: true + +Path to Angular Module. Also used to determine the parent directory for the new **+state** +directory; unless the `--directory` option is used to override the dir name. + +> e.g. --module=apps/myapp/src/app/app.module.ts + +- `--module` + - Type: `string` + - Required: true + +Specifies the directory name used to nest the **ngrx** files within a folder. + +- `--directory` + - Type: `string` + - Default: `+state` + +#### Examples + +Generate a `User` feature set and register it within an `Angular Module`. + +```sh +ng generate ngrx User -m apps/myapp/src/app/app.module.ts +ng g ngrx Producrts -m libs/mylib/src/mylib.module.ts +``` + + +Generate a `User` feature set within a `user` folder and register it with the `user.module.ts` file in the same `user` folder. + +```sh +ng g ngrx User -m apps/myapp/src/app/app.module.ts -directory user +``` + +## Generated Files + +The files generated are shown below and include placeholders for the *feature* name specified. + +> The <Feature> notation used be below indicates a placeholder for the actual *feature* name. + +* [<feature>.actions.ts](#featureactionsts) +* [<feature>.reducer.ts](#featurereducerts) +* [<feature>.effects.ts](#featureeffectsts) +* [<feature>.selectors.ts](#featureselectorsts) +* [<feature>.facade.ts](#featurefacadests) + +* [../app.module.ts](#appmodulets) + +#### <feature>.actions.ts + +```ts +import {Action} from "@ngrx/store"; + +export enum ActionTypes { + = "[] Action", + Load = "[] Load Data", + Loaded = "[] Data Loaded" +} + +export class implements Action { + readonly type = ActionTypes.; +} + +export class Load implements Action { + readonly type = ActionTypes.Load; + constructor(public payload: any) { } +} + +export class DataLoaded implements Action { + readonly type = ActionTypes.Loaded; + constructor(public payload: any) { } +} + +export type Actions = | Load | Loaded; +``` + +#### <feature>.reducer.ts +```ts +import { } from './.interfaces'; +import { Action, ActionTypes } from './.actions'; + +/** + * Interface for the '' data used in + * - State, and + * - Reducer + */ +export interface Data { + +} + +/** + * Interface to the part of the Store containing State + * and other information related to Data. + */ +export interface State { + readonly : Data; +} + +export const initialState: Data = { }; + +export function Reducer(state: Data = initialState, action: Actions): Data { + switch (action.type) { + case ActionTypes.Loaded: { + return { ...state, ...action.payload }; + } + default: { + return state; + } + } +} +``` + +#### <feature>.effects.ts +```ts +import { Injectable } from '@angular/core'; +import { Effect, Actions } from '@ngrx/effects'; +import { DataPersistence } from '@nrwl/nx'; + +import { } from './.interfaces'; +import { Load, Loaded, ActionTypes } from './.actions'; + +@Injectable() +export class Effects { + @Effect() load$ = this.dataPersistence.fetch(ActionTypes.Load, { + run: (action: Load, state: ) => { + return new Loaded({}); + }, + + onError: (action: Load, error) => { + console.error('Error', error); + } + }); + + constructor( + private actions: Actions, + private dataPersistence: DataPersistence) { } +} +``` + + +#### ../app.module.ts +```ts +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { RouterModule } from '@angular/router'; +import { AppComponent } from './app.component'; +import { StoreModule } from '@ngrx/store'; +import { EffectsModule } from '@ngrx/effects'; +import { + Reducer, + State, + Data, + initialState as InitialState +} from './+state/.reducer'; +import { Effects } from './+state/.effects'; +import { StoreDevtoolsModule } from '@ngrx/store-devtools'; +import { environment } from '../environments/environment'; +import { StoreRouterConnectingModule } from '@ngrx/router-store'; +import { storeFreeze } from 'ngrx-store-freeze'; + +@NgModule({ + imports: [BrowserModule, RouterModule.forRoot([]), + StoreModule.forRoot({ : Reducer }, { + initialState: { : InitialState }, + metaReducers: !environment.production ? [storeFreeze] : [] + }), + EffectsModule.forRoot([Effects]), + !environment.production ? StoreDevtoolsModule.instrument() : [], + StoreRouterConnectingModule], + declarations: [AppComponent], + bootstrap: [AppComponent], + providers: [Effects] +}) +export class AppModule { +} + +``` diff --git a/packages/schematics/src/collection/ngrx/ngrx.spec.ts b/packages/schematics/src/collection/ngrx/ngrx.spec.ts index 890075ca15..f411082f5f 100644 --- a/packages/schematics/src/collection/ngrx/ngrx.spec.ts +++ b/packages/schematics/src/collection/ngrx/ngrx.spec.ts @@ -1,10 +1,20 @@ -import { SchematicTestRunner } from '@angular-devkit/schematics/testing'; -import * as path from 'path'; +import { + SchematicTestRunner, + UnitTestTree +} from '@angular-devkit/schematics/testing'; import { Tree, VirtualTree } from '@angular-devkit/schematics'; -import { createApp, createEmptyWorkspace } from '../../utils/testing-utils'; import { getFileContent } from '@schematics/angular/utility/test'; import { readJson } from '../../utils/ast-utils'; +import * as path from 'path'; +import { findModuleParent } from '../../utils/name-utils'; +import { + createApp, + createEmptyWorkspace, + AppConfig, + getAppConfig +} from '../../utils/testing-utils'; + describe('ngrx', () => { const schematicRunner = new SchematicTestRunner( '@nrwl/schematics', @@ -29,21 +39,23 @@ describe('ngrx', () => { }, appTree ); - const appModule = getFileContent(tree, '/apps/myapp/src/app/app.module.ts'); - expect(appModule).toContain( - 'StoreModule.forRoot({},{metaReducers: !environment.production ? [storeFreeze] : []})' - ); - expect(appModule).toContain('EffectsModule.forRoot'); expect(tree.exists('apps/myapp/src/app/+state')).toBeFalsy(); + + [ + 'StoreModule.forRoot({},{ metaReducers : !environment.production ? [storeFreeze] : [] })', + 'EffectsModule.forRoot' + ].forEach(text => { + expect(appModule).toContain(text); + }); }); it('should add root', () => { const tree = schematicRunner.runSchematic( 'ngrx', { - name: 'state', + name: 'app', module: 'apps/myapp/src/app/app.module.ts', root: true }, @@ -51,31 +63,26 @@ describe('ngrx', () => { ); const appModule = getFileContent(tree, '/apps/myapp/src/app/app.module.ts'); + + console.log(appModule); + expect(appModule).toContain('StoreModule.forRoot'); expect(appModule).toContain('EffectsModule.forRoot'); expect(appModule).toContain('!environment.production ? [storeFreeze] : []'); - expect( - tree.exists(`/apps/myapp/src/app/+state/state.actions.ts`) - ).toBeTruthy(); - expect( - tree.exists(`/apps/myapp/src/app/+state/state.effects.ts`) - ).toBeTruthy(); - expect( - tree.exists(`/apps/myapp/src/app/+state/state.effects.spec.ts`) - ).toBeTruthy(); - expect( - tree.exists(`/apps/myapp/src/app/+state/state.init.ts`) - ).toBeTruthy(); - expect( - tree.exists(`/apps/myapp/src/app/+state/state.interfaces.ts`) - ).toBeTruthy(); - expect( - tree.exists(`/apps/myapp/src/app/+state/state.reducer.ts`) - ).toBeTruthy(); - expect( - tree.exists(`/apps/myapp/src/app/+state/state.reducer.spec.ts`) - ).toBeTruthy(); + expect(appModule).toContain('appReducer, initialState'); + expect(appModule).not.toContain('AppData'); + expect(appModule).not.toContain('AppState'); + + [ + '/apps/myapp/src/app/+state/app.actions.ts', + '/apps/myapp/src/app/+state/app.effects.ts', + '/apps/myapp/src/app/+state/app.effects.spec.ts', + '/apps/myapp/src/app/+state/app.reducer.ts', + '/apps/myapp/src/app/+state/app.reducer.spec.ts' + ].forEach(fileName => { + expect(tree.exists(fileName)).toBeTruthy(); + }); }); it('should add feature', () => { @@ -173,4 +180,102 @@ describe('ngrx', () => { ) ).toThrow("should have required property 'module'"); }); + + it('should create the ngrx files', () => { + const appConfig = getAppConfig(); + const hasFile = file => expect(tree.exists(file)).toBeTruthy(); + const tree = buildNgrxTree(appConfig); + const statePath = `${findModuleParent(appConfig.appModule)}/+state`; + + hasFile(`${statePath}/user.actions.ts`); + hasFile(`${statePath}/user.effects.ts`); + hasFile(`${statePath}/user.effects.spec.ts`); + hasFile(`${statePath}/user.reducer.ts`); + hasFile(`${statePath}/user.reducer.spec.ts`); + }); + + it('should create ngrx action enums', () => { + const appConfig = getAppConfig(); + const tree = buildNgrxTree(appConfig); + + const statePath = `${findModuleParent(appConfig.appModule)}/+state`; + const content = getFileContent(tree, `${statePath}/user.actions.ts`); + + expect(content).toContain('UserActionTypes'); + expect(content).toContain("LoadUser = '[User] Load Data'"); + expect(content).toContain("UserLoaded = '[User] Data Loaded'"); + }); + + it('should create ngrx action classes', () => { + const appConfig = getAppConfig(); + const tree = buildNgrxTree(appConfig); + + const statePath = `${findModuleParent(appConfig.appModule)}/+state`; + const content = getFileContent(tree, `${statePath}/user.actions.ts`); + + expect(content).toContain('class LoadUser implements Action'); + expect(content).toContain('class UserLoaded implements Action'); + }); + + it('should enhance the ngrx action type', () => { + const appConfig = getAppConfig(); + const tree = buildNgrxTree(appConfig); + + const statePath = `${findModuleParent(appConfig.appModule)}/+state`; + const content = getFileContent(tree, `${statePath}/user.actions.ts`); + expect(content).toContain( + 'type UserActions = User | LoadUser | UserLoaded' + ); + }); + + it('should enhance the ngrx reducer', () => { + const appConfig = getAppConfig(); + const tree = buildNgrxTree(appConfig); + + const statePath = `${findModuleParent(appConfig.appModule)}/+state`; + const content = getFileContent(tree, `${statePath}/user.reducer.ts`); + + expect(content).not.toContain('function reducer'); + + [ + `import { UserActions, UserActionTypes } from \'./user.actions\'`, + `export interface UserData`, + `export interface UserState`, + `readonly user: UserData`, + `const initialState: UserData`, + 'function userReducer(state = initialState, action: UserActions): UserData', + 'case UserActionTypes.UserLoaded' + ].forEach(text => { + expect(content).toContain(text); + }); + }); + + it('should enhance the ngrx effects', () => { + const appConfig = getAppConfig(); + const tree = buildNgrxTree(appConfig); + const statePath = `${findModuleParent(appConfig.appModule)}/+state`; + const content = getFileContent(tree, `${statePath}/user.effects.ts`); + + [ + `import { DataPersistence } from \'@nrwl/nx\'`, + `import { UserActions, UserActionTypes, LoadUser, UserLoaded } from \'./user.actions\'`, + `loadUser$`, + `run: (action: LoadUser, state: UserState)`, + `return new UserLoaded(state)`, + 'constructor(private actions$: Actions, private dataPersistence: DataPersistence)' + ].forEach(text => { + expect(content).toContain(text); + }); + }); + + function buildNgrxTree(appConfig: AppConfig): UnitTestTree { + return schematicRunner.runSchematic( + 'ngrx', + { + name: 'user', + module: appConfig.appModule + }, + appTree + ); + } }); diff --git a/packages/schematics/src/collection/ngrx/rules/add-imports-to-module.ts b/packages/schematics/src/collection/ngrx/rules/add-imports-to-module.ts new file mode 100644 index 0000000000..e7f99ec7b0 --- /dev/null +++ b/packages/schematics/src/collection/ngrx/rules/add-imports-to-module.ts @@ -0,0 +1,119 @@ +import { Rule, Tree } from '@angular-devkit/schematics'; +import { Change } from '@ngrx/schematics/src/utility/change'; +import { insertImport } from '@schematics/angular/utility/route-utils'; +import * as ts from 'typescript'; +import { + toClassName, + toFileName, + toPropertyName +} from '../../../utils/name-utils'; +import { + insert, + addImportToModule, + addProviderToModule +} from '../../../utils/ast-utils'; +import { RequestContext } from './request-context'; + +export function addImportsToModule(context: RequestContext): Rule { + return (host: Tree) => { + if (context.options.onlyAddFiles) { + return host; + } + + if (!host.exists(context.options.module)) { + throw new Error('Specified module does not exist'); + } + const modulePath = context.options.module; + const sourceText = host.read(modulePath)!.toString('utf-8'); + const source = ts.createSourceFile( + modulePath, + sourceText, + ts.ScriptTarget.Latest, + true + ); + const addImport = (symbolName: string, fileName: string): Change => { + return insertImport(source, modulePath, symbolName, fileName); + }; + + const dir = `./${toFileName(context.options.directory)}`; + const pathPrefix = `${dir}/${toFileName(context.featureName)}`; + const reducerPath = `${pathPrefix}.reducer`; + const effectsPath = `${pathPrefix}.effects`; + + const featureName = `${toPropertyName(context.featureName)}`; + const reducerName = `${toPropertyName(context.featureName)}Reducer`; + const effectsName = `${toClassName(context.featureName)}Effects`; + const reducerImports = `${reducerName}, initialState as ${featureName}InitialState`; + + const storeReducers = `{ ${featureName}: ${reducerName} }`; + const storeInitState = `initialState : { ${featureName} : ${featureName}InitialState }`; + const storeMetaReducers = `metaReducers : !environment.production ? [storeFreeze] : []`; + + const storeForRoot = `StoreModule.forRoot( + ${storeReducers}, + { + ${storeInitState}, + ${storeMetaReducers} + } +)`; + const storeForEmptyRoot = `StoreModule.forRoot({},{ ${storeMetaReducers} })`; + const effectsForRoot = `EffectsModule.forRoot([${effectsName}])`; + const effectsForEmptyRoot = `EffectsModule.forRoot([])`; + const storeForFeature = `StoreModule.forFeature('${featureName}', ${reducerName}, { ${storeInitState} })`; + const effectsForFeature = `EffectsModule.forFeature([${effectsName}])`; + const devTools = `!environment.production ? StoreDevtoolsModule.instrument() : []`; + const storeRouterModule = 'StoreRouterConnectingModule'; + + // InsertImport [symbol,source] value pairs + const storeModule = ['StoreModule', '@ngrx/store']; + const effectsModule = ['EffectsModule', '@ngrx/effects']; + const storeDevTools = ['StoreDevtoolsModule', '@ngrx/store-devtools']; + const environment = ['environment', '../environments/environment']; + const storeRouter = ['StoreRouterConnectingModule', '@ngrx/router-store']; + const storeFreeze = ['storeFreeze', 'ngrx-store-freeze']; + + if (context.options.onlyEmptyRoot) { + insert(host, modulePath, [ + addImport.apply(this, storeModule), + addImport.apply(this, effectsModule), + addImport.apply(this, storeDevTools), + addImport.apply(this, environment), + addImport.apply(this, storeRouter), + addImport.apply(this, storeFreeze), + ...addImportToModule(source, modulePath, storeForEmptyRoot), + ...addImportToModule(source, modulePath, effectsForEmptyRoot), + ...addImportToModule(source, modulePath, devTools), + ...addImportToModule(source, modulePath, storeRouterModule) + ]); + } else { + const common = [ + addImport.apply(this, storeModule), + addImport.apply(this, effectsModule), + addImport(reducerImports, reducerPath), + addImport(effectsName, effectsPath), + ...addProviderToModule(source, modulePath, effectsName) + ]; + + if (context.options.root) { + insert(host, modulePath, [ + ...common, + addImport.apply(this, storeDevTools), + addImport.apply(this, environment), + addImport.apply(this, storeRouter), + addImport.apply(this, storeFreeze), + ...addImportToModule(source, modulePath, storeForRoot), + ...addImportToModule(source, modulePath, effectsForRoot), + ...addImportToModule(source, modulePath, devTools), + ...addImportToModule(source, modulePath, storeRouterModule) + ]); + } else { + insert(host, modulePath, [ + ...common, + ...addImportToModule(source, modulePath, storeForFeature), + ...addImportToModule(source, modulePath, effectsForFeature) + ]); + } + } + return host; + }; +} diff --git a/packages/schematics/src/collection/ngrx/rules/add-ngrx-to-package-json.ts b/packages/schematics/src/collection/ngrx/rules/add-ngrx-to-package-json.ts new file mode 100644 index 0000000000..9e0ff24797 --- /dev/null +++ b/packages/schematics/src/collection/ngrx/rules/add-ngrx-to-package-json.ts @@ -0,0 +1,41 @@ +import { Rule, Tree } from '@angular-devkit/schematics'; +import { + ngrxVersion, + routerStoreVersion, + ngrxStoreFreezeVersion +} from '../../../lib-versions'; +import { serializeJson } from '../../../utils/fileutils'; + +export function addNgRxToPackageJson(): Rule { + return (host: Tree) => { + if (!host.exists('package.json')) return host; + + const sourceText = host.read('package.json')!.toString('utf-8'); + const json = JSON.parse(sourceText); + if (!json['dependencies']) { + json['dependencies'] = {}; + } + + if (!json['dependencies']['@ngrx/store']) { + json['dependencies']['@ngrx/store'] = ngrxVersion; + } + if (!json['dependencies']['@ngrx/effects']) { + json['dependencies']['@ngrx/effects'] = ngrxVersion; + } + if (!json['dependencies']['@ngrx/entity']) { + json['dependencies']['@ngrx/entity'] = ngrxVersion; + } + if (!json['dependencies']['@ngrx/store-devtools']) { + json['dependencies']['@ngrx/store-devtools'] = ngrxVersion; + } + if (!json['dependencies']['@ngrx/router-store']) { + json['dependencies']['@ngrx/router-store'] = routerStoreVersion; + } + if (!json['dependencies']['ngrx-store-freeze']) { + json['dependencies']['ngrx-store-freeze'] = ngrxStoreFreezeVersion; + } + + host.overwrite('package.json', serializeJson(json)); + return host; + }; +} diff --git a/packages/schematics/src/collection/ngrx/rules/index.ts b/packages/schematics/src/collection/ngrx/rules/index.ts new file mode 100644 index 0000000000..0799952d84 --- /dev/null +++ b/packages/schematics/src/collection/ngrx/rules/index.ts @@ -0,0 +1,6 @@ +export { RequestContext } from './request-context'; +export { updateNgrxReducers } from './update-reducers'; +export { updateNgrxActions } from './update-actions'; +export { updateNgrxEffects } from './update-effects'; +export { addImportsToModule } from './add-imports-to-module'; +export { addNgRxToPackageJson } from './add-ngrx-to-package-json'; diff --git a/packages/schematics/src/collection/ngrx/rules/request-context.ts b/packages/schematics/src/collection/ngrx/rules/request-context.ts new file mode 100644 index 0000000000..a692708c24 --- /dev/null +++ b/packages/schematics/src/collection/ngrx/rules/request-context.ts @@ -0,0 +1,23 @@ +import * as path from 'path'; + +import { Tree } from '@angular-devkit/schematics'; +import { Schema } from '../schema'; +import * as stringUtils from '../../../utils/strings'; + +/** + * Schematic request context + */ +export interface RequestContext { + featureName: string; + moduleDir: string; + options?: Schema; + host?: Tree; +} + +export function buildNameToNgrxFile(context: RequestContext, suffice: string) { + return path.join( + context.moduleDir, + context.options.directory, + `${stringUtils.dasherize(context.featureName)}.${suffice}` + ); +} diff --git a/packages/schematics/src/collection/ngrx/rules/update-actions.ts b/packages/schematics/src/collection/ngrx/rules/update-actions.ts new file mode 100644 index 0000000000..68a38fcb08 --- /dev/null +++ b/packages/schematics/src/collection/ngrx/rules/update-actions.ts @@ -0,0 +1,103 @@ +import * as ts from 'typescript'; +import { SchematicsException, Rule, Tree } from '@angular-devkit/schematics'; +import { stripIndents } from '@angular-devkit/core/src/utils/literals'; +import { toClassName } from '../../../utils/name-utils'; +import { + insert, + addClass, + addEnumeratorValues, + addUnionTypes +} from '../../../utils/ast-utils'; + +import { RequestContext, buildNameToNgrxFile } from './request-context'; + +/** + * Add custom actions to .actions.ts + * See Ngrx Enhancement doc: https://bit.ly/2I5QwxQ + * + * Desired output: + * + * ``` + * import {Action} from "@ngrx/store"; + * + * export enum ActionTypes { + * = "[] Action", + * Load = "[] Load Data", + * Loaded = "[] Data Loaded" + * } + * + * export class implements Action { + * readonly type = ActionTypes.; + * } + * + * export class Load implements Action { + * readonly type = ActionTypes.Load; + * constructor(public payload: any) { } + * } + * + * export class LOADED implements Action { + * readonly type = ActionTypes.LOADED; + * constructor(public payload: any) { } + * } + * + * export type = | Load | Loaded + * + * ``` + * + */ +export function updateNgrxActions(context: RequestContext): Rule { + return (host: Tree) => { + const clazzName = toClassName(context.featureName); + const componentPath = buildNameToNgrxFile(context, 'actions.ts'); + const text = host.read(componentPath); + + if (text === null) { + throw new SchematicsException(`File ${componentPath} does not exist.`); + } + + const sourceText = text.toString('utf-8'); + const source = ts.createSourceFile( + componentPath, + sourceText, + ts.ScriptTarget.Latest, + true + ); + + insert(host, componentPath, [ + ...addEnumeratorValues(source, componentPath, `${clazzName}ActionTypes`, [ + { + name: `Load${clazzName}`, + value: `[${clazzName}] Load Data` + }, + { + name: `${clazzName}Loaded`, + value: `[${clazzName}] Data Loaded` + } + ]), + addClass( + source, + componentPath, + `Load${clazzName}`, + stripIndents` + export class Load${clazzName} implements Action { + readonly type = ${clazzName}ActionTypes.Load${clazzName}; + constructor(public payload: any) { } + }` + ), + addClass( + source, + componentPath, + `${clazzName}Loaded`, + stripIndents` + export class ${clazzName}Loaded implements Action { + readonly type = ${clazzName}ActionTypes.${clazzName}Loaded; + constructor(public payload: any) { } + }` + ), + addUnionTypes(source, componentPath, `${clazzName}Actions`, [ + `Load${clazzName}`, + `${clazzName}Loaded` + ]) + ]); + }; +} diff --git a/packages/schematics/src/collection/ngrx/rules/update-effects.ts b/packages/schematics/src/collection/ngrx/rules/update-effects.ts new file mode 100644 index 0000000000..a3fe62c5e0 --- /dev/null +++ b/packages/schematics/src/collection/ngrx/rules/update-effects.ts @@ -0,0 +1,101 @@ +import * as ts from 'typescript'; +import { SchematicsException, Rule, Tree } from '@angular-devkit/schematics'; +import { InsertChange } from '@schematics/angular/utility/change'; +import { findNodes } from '@schematics/angular/utility/ast-utils'; +import { insertImport } from '@schematics/angular/utility/route-utils'; +import { stripIndents } from '@angular-devkit/core/src/utils/literals'; + +import { toClassName } from '../../../utils/name-utils'; +import { insert } from '../../../utils/ast-utils'; +import { RequestContext, buildNameToNgrxFile } from './request-context'; + +/** + * + * Desired output: + * + * ``` + * import { Injectable } from '@angular/core'; + * import { Effect, Actions } from '@ngrx/effects'; + * import { DataPersistence } from '@nrwl/nx'; + * + * import { , State } from './.reducer'; + * import { Load, Loaded, ActionTypes } from './.actions'; + * + * @Injectable() + * export class Effects { + * @Effect() load$ = this.dataPersistence.fetch(ActionTypes.Load, { + * run: (action: Load, state: State) => { + * return new Loaded({}); + * }, + * + * onError: (action: Load, error) => { + * console.error('Error', error); + * } + * }); + * + * constructor( + * private actions: Actions, + * private dataPersistence: DataPersistence<State>) { } + * } + * + */ +export function updateNgrxEffects(context: RequestContext): Rule { + return (host: Tree) => { + const clazzName = toClassName(context.featureName); + const componentPath = buildNameToNgrxFile(context, 'effects.ts'); + const featureReducer = `./${context.featureName}.reducer`; + const text = host.read(componentPath); + + if (text === null) { + throw new SchematicsException(`File ${componentPath} does not exist.`); + } + + const modulePath = context.options.module; + const sourceText = text.toString('utf-8'); + const source = ts.createSourceFile( + componentPath, + sourceText, + ts.ScriptTarget.Latest, + true + ); + const updateConstructor = () => { + const astConstructor = findNodes(source, ts.SyntaxKind.Constructor)[0]; + const lastParameter = findNodes( + astConstructor, + ts.SyntaxKind.Parameter + ).pop(); + + return new InsertChange( + componentPath, + lastParameter.end, + stripIndents`, private dataPersistence: DataPersistence<${clazzName}State>` + ); + }; + const addEffect$ = () => { + const toInsert = `\n + @Effect() + load${clazzName}$ = this.dataPersistence.fetch(${clazzName}ActionTypes.Load${clazzName}, { + run: (action: Load${clazzName}, state: ${clazzName}State) => { + return new ${clazzName}Loaded(state); + }, + + onError: (action: Load${clazzName}, error) => { + console.error('Error', error); + } + });`; + const astConstructor = findNodes(source, ts.SyntaxKind.Constructor)[0]; + return new InsertChange(componentPath, astConstructor.pos, toInsert); + }; + + const actionsFile = `./${context.featureName}.actions`; + const actionImports = `Load${clazzName}, ${clazzName}Loaded`; + + insert(host, componentPath, [ + insertImport(source, modulePath, actionImports, actionsFile), + insertImport(source, modulePath, `${clazzName}State`, featureReducer), + insertImport(source, modulePath, 'DataPersistence', `@nrwl/nx`), + updateConstructor(), + addEffect$() + ]); + }; +} diff --git a/packages/schematics/src/collection/ngrx/rules/update-reducers.ts b/packages/schematics/src/collection/ngrx/rules/update-reducers.ts new file mode 100644 index 0000000000..d907f0dbd0 --- /dev/null +++ b/packages/schematics/src/collection/ngrx/rules/update-reducers.ts @@ -0,0 +1,186 @@ +import * as ts from 'typescript'; +import { SchematicsException, Rule, Tree } from '@angular-devkit/schematics'; +import { + Change, + ReplaceChange, + InsertChange +} from '@schematics/angular/utility/change'; +import { + findNodes, + insertAfterLastOccurrence +} from '@schematics/angular/utility/ast-utils'; +import { insertImport } from '@schematics/angular/utility/route-utils'; +import { toClassName, toPropertyName } from '../../../utils/name-utils'; +import { insert, findNodesOfType } from '../../../utils/ast-utils'; +import { RequestContext, buildNameToNgrxFile } from './request-context'; + +/** + * Update ngrx-generated Reducer to confirm to DataLoaded action to .reducer.ts + * + * Desired output: + * + * ``` + * import { Actions, ActionTypes } from './.actions'; + * + * export interface State { + * } + * + * export const initialState: State = { + * }; + * + * export function Reducer( + * state : State = initialState, + * action: Actions ) : State + * { + * switch (action.type) { + * case ActionTypes.Loaded: { + * return { ...state, ...action.payload }; + * } + * default: { + * return state; + * } + * } + * } + * ``` + * + * + */ +export function updateNgrxReducers(context: RequestContext): Rule { + return (host: Tree) => { + const clazzName = toClassName(context.featureName); + const propertyName = toPropertyName(context.featureName); + const componentPath = buildNameToNgrxFile(context, 'reducer.ts'); + const text = host.read(componentPath); + + if (text === null) { + throw new SchematicsException(`File ${componentPath} does not exist.`); + } + + const modulePath = context.options.module; + const sourceText = text.toString('utf-8'); + const source = ts.createSourceFile( + componentPath, + sourceText, + ts.ScriptTarget.Latest, + true + ); + const renameStateInterface = () => { + const name = findNodesOfType( + source, + ts.SyntaxKind.InterfaceDeclaration, + (it: ts.InterfaceDeclaration) => it.name.getText() === 'State', + (it: ts.InterfaceDeclaration) => it.name, + true + ); + return new ReplaceChange( + componentPath, + name.pos, + 'State', + `${clazzName}Data` + ); + }; + const addInterfaceComments = () => { + const node = findNodes(source, ts.SyntaxKind.InterfaceDeclaration, 1)[0]; + const toAdd = ` +/** + * Interface for the '${clazzName}' data used in + * - ${clazzName}State, and + * - ${propertyName}Reducer + */`; + return new InsertChange(componentPath, node.pos + 1, `\n ${toAdd}`); + }; + const addFeatureState = () => { + const node = findNodes(source, ts.SyntaxKind.VariableStatement, 1)[0]; + const toAdd = ` +/** + * Interface to the part of the Store containing ${clazzName}State + * and other information related to ${clazzName}Data. + */ +export interface ${clazzName}State { + readonly ${propertyName}: ${clazzName}Data; +}`; + return new InsertChange(componentPath, node.pos, `\n${toAdd}`); + }; + const renameInitialState = () => { + const getIdentifier = node => node.typeName; + const target = findNodes(source, ts.SyntaxKind.VariableStatement, 1); + const name = findNodesOfType( + target[0], + ts.SyntaxKind.TypeReference, + it => { + return getIdentifier(it).getText() === 'State'; + }, + it => getIdentifier(it), + true + ); + return new ReplaceChange( + componentPath, + name.pos, + 'State', + `${clazzName}Data` + ); + }; + const updateReducerFn = () => { + let actions: Change[] = []; + findNodes(source, ts.SyntaxKind.FunctionDeclaration) + .filter((it: ts.FunctionDeclaration) => it.name.getText() === 'reducer') + .map((it: ts.FunctionDeclaration) => { + const fnName: ts.Identifier = it.name; + const typeName = findNodes(it, ts.SyntaxKind.Identifier).reduce( + (result: ts.Identifier, it: ts.Identifier): ts.Identifier => { + return !!result + ? result + : it.getText() === 'State' ? it : undefined; + }, + undefined + ); + + actions = [ + new ReplaceChange( + componentPath, + fnName.pos, + fnName.getText(), + `${propertyName}Reducer` + ), + new ReplaceChange( + componentPath, + typeName.pos, + typeName.getText(), + `${clazzName}Data` + ) + ]; + }); + + return actions; + }; + const updateSwitchStatement = () => { + const toInsert = ` + + case ${clazzName}ActionTypes.${clazzName}Loaded: { + return { ...state, ...action.payload }; + }`; + return insertAfterLastOccurrence( + findNodes(source, ts.SyntaxKind.SwitchStatement), + toInsert, + componentPath, + 0, + ts.SyntaxKind.CaseClause + ); + }; + + insert(host, componentPath, [ + addInterfaceComments(), + addFeatureState(), + renameStateInterface(), + renameInitialState(), + insertImport( + source, + modulePath, + `${clazzName}Actions`, + `./${context.featureName}.actions` + ), + ...updateReducerFn(), + updateSwitchStatement() + ]); + }; +} diff --git a/packages/schematics/src/collection/ngrx/schema.json b/packages/schematics/src/collection/ngrx/schema.json index 763709594c..f5d31ec79b 100644 --- a/packages/schematics/src/collection/ngrx/schema.json +++ b/packages/schematics/src/collection/ngrx/schema.json @@ -6,17 +6,22 @@ "properties": { "name": { "type": "string", - "description": "Name of the directory (e.g., state)." + "description": "Name of the ngrx feature (e.g., Products, User, etc.)." }, "module": { "type": "string", - "description": "Path to an Angular module (e.g., src/app/app.module.ts)." + "description": "Path to ngModule; host directory will contain the new '+state' directory (e.g., src/libs/mylib/mylib.module.ts)." }, "onlyAddFiles": { "type": "boolean", "default": false, "description": "Only add new NgRx files, without changing the module file (e.g., --onlyAddFiles)." }, + "directory": { + "type": "string", + "default": "+state", + "description": "The directory name for the ngrx files: contains actions, effects, reducers. (e.g., +state)" + }, "root": { "type": "boolean", "default": false, @@ -31,15 +36,10 @@ "type": "boolean", "default": false, "description": "Do not add ngrx dependencies to package.json (e.g., --skipPackageJson)" - }, - "directory": { - "type": "string", - "default": "+state", - "description": "The store directory name (e.g., +state)" } }, "required": [ "name", "module" ] -} \ No newline at end of file +} diff --git a/packages/schematics/src/collection/workspace/index.ts b/packages/schematics/src/collection/workspace/index.ts old mode 100644 new mode 100755 diff --git a/packages/schematics/src/lib-versions.ts b/packages/schematics/src/lib-versions.ts index 7858e07972..591f8a464d 100644 --- a/packages/schematics/src/lib-versions.ts +++ b/packages/schematics/src/lib-versions.ts @@ -1,7 +1,7 @@ export const angularCliVersion = '1.7.1'; export const angularVersion = '5.2.6'; export const angularJsVersion = '1.6.6'; -export const ngrxVersion = '5.1.0'; +export const ngrxVersion = '5.2.0'; export const ngrxStoreFreezeVersion = '^0.2.1'; export const routerStoreVersion = '5.0.1'; export const nxVersion = '*'; @@ -14,6 +14,7 @@ export const typescriptVersion = '2.6.2'; export const rxjsVersion = '^5.5.6'; export const devKitCoreVersion = '^0.0.29'; export const devKitSchematicsVersion = '0.0.52'; +export const ngrxSchematicsVersion = '5.2.0'; export const schematicsAngularVersion = '0.1.17'; export const libVersions = { @@ -32,5 +33,6 @@ export const libVersions = { devKitCoreVersion, devKitSchematicsVersion, schematicsAngularVersion, + ngrxSchematicsVersion, routerStoreVersion }; diff --git a/packages/schematics/src/utils/ast-utils.ts b/packages/schematics/src/utils/ast-utils.ts old mode 100644 new mode 100755 index aadd860982..71b8f49781 --- a/packages/schematics/src/utils/ast-utils.ts +++ b/packages/schematics/src/utils/ast-utils.ts @@ -2,24 +2,27 @@ * @license * Copyright Google Inc. All Rights Reserved. * - * Use of this source code is governed by an MIT-style license that can be + * 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 { Tree, Rule } from '@angular-devkit/schematics'; import { findNodes, getDecoratorMetadata, - getSourceNodes + getSourceNodes, + insertAfterLastOccurrence } from '@schematics/angular/utility/ast-utils'; import { Change, InsertChange, NoopChange, - RemoveChange + RemoveChange, + ReplaceChange } from '@schematics/angular/utility/change'; + import * as ts from 'typescript'; -import { toFileName } from './name-utils'; import * as path from 'path'; +import { toFileName } from './name-utils'; import { serializeJson } from './fileutils'; // This should be moved to @schematics/angular once it allows to pass custom expressions as providers @@ -226,9 +229,10 @@ export function removeFromNgModule( } } -function findClass( +export function findClass( source: ts.SourceFile, - className: string + className: string, + silent: boolean = false ): ts.ClassDeclaration { const nodes = getSourceNodes(source); @@ -238,7 +242,7 @@ function findClass( (n).name.text === className )[0]; - if (!clazz) { + if (!clazz && !silent) { throw new Error(`Cannot find class '${className}'`); } @@ -531,6 +535,10 @@ export function insert(host: Tree, modulePath: string, changes: Change[]) { recorder.remove((change).pos - 1, (change).toRemove.length + 1); } else if (change instanceof NoopChange) { // do nothing + } else if (change instanceof ReplaceChange) { + const action = change; + recorder.remove(action.pos + 1, action.oldText.length); + recorder.insertLeft(action.pos + 1, action.newText); } else { throw new Error(`Unexpected Change '${change}'`); } @@ -658,3 +666,199 @@ export function readBootstrapInfo( bootstrapComponentFileName }; } + +export function addClass( + source: ts.SourceFile, + modulePath: string, + clazzName: string, + clazzSrc: string +): Change { + if (!findClass(source, clazzName, true)) { + const nodes = findNodes(source, ts.SyntaxKind.ClassDeclaration); + return insertAfterLastOccurrence( + nodes, + offset(clazzSrc, 0, true), + modulePath, + 0, + ts.SyntaxKind.ClassDeclaration + ); + } + return new NoopChange(); +} + +export function addUnionTypes( + source: ts.SourceFile, + modulePath: string, + typeName: string, + typeValues: string[] +) { + const target: ts.TypeAliasDeclaration = findNodesOfType( + source, + ts.SyntaxKind.TypeAliasDeclaration, + it => it.name.getText() === typeName + ); + if (!target) { + throw new Error(`Cannot find union type '${typeName}'`); + } + + const node = target.type as ts.TypeReferenceNode; + + // Append new types to create a union type... + return new InsertChange( + modulePath, + node.end, + ['', ...typeValues].join(' | ') + ); +} + +/** + * Add 1..n enumerators using name + (optional) value pairs + */ +export function addEnumeratorValues( + source: ts.SourceFile, + modulePath: string, + enumName: string, + pairs: NameValue[] = [] +): Change[] { + const target = findNodesOfType( + source, + ts.SyntaxKind.EnumDeclaration, + it => it.name.getText() === enumName + ); + const list = target ? target.members : undefined; + if (!target) { + throw new Error(`Cannot find enum '${enumName}'`); + } + const addComma = !(list.hasTrailingComma || list.length === 0); + + return pairs.reduce((buffer, it) => { + const member = it.value ? `${it.name} = '${it.value}'` : it.name; + const memberExists = () => { + return list.filter(m => m.name.getText() === it.name).length; + }; + + if (memberExists()) { + throw new Error(`Enum '${enumName}.${it.name}' already exists`); + } + + return [ + ...buffer, + new InsertChange(modulePath, list.end, (addComma ? ', ' : '') + member) + ]; + }, []); +} + +/** + * Find Enum declaration in source based on name + * e.g. + * export enum ProductsActionTypes { + * ProductsAction = '[Products] Action' + * } + */ +const IDENTITY = a => a; +export function findNodesOfType( + source: ts.Node, + kind: ts.SyntaxKind, + predicate: (a: any) => boolean, + extract: (a: any) => any = IDENTITY, + firstOnly: boolean = true +): any { + const nodes = findNodes(source, kind); + const matching = nodes.filter((i: any) => predicate(i)).map(extract); + return matching.length ? (firstOnly ? matching[0] : matching) : undefined; +} + +export interface NameValue { + name: string; + value?: string; +} + +export function insertImport( + source: ts.SourceFile, + fileToEdit: string, + symbolName: string, + fileName: string, + isDefault = false +): Change { + const rootNode = source; + const allImports = findNodes(rootNode, ts.SyntaxKind.ImportDeclaration); + + // get nodes that map to import statements from the file fileName + const relevantImports = allImports.filter(node => { + // StringLiteral of the ImportDeclaration is the import file (fileName in this case). + const importFiles = node + .getChildren() + .filter(child => child.kind === ts.SyntaxKind.StringLiteral) + .map(n => (n as ts.StringLiteral).text); + + return importFiles.filter(file => file === fileName).length === 1; + }); + + if (relevantImports.length > 0) { + let importsAsterisk = false; + // imports from import file + const imports: ts.Node[] = []; + relevantImports.forEach(n => { + Array.prototype.push.apply( + imports, + findNodes(n, ts.SyntaxKind.Identifier) + ); + if (findNodes(n, ts.SyntaxKind.AsteriskToken).length > 0) { + importsAsterisk = true; + } + }); + + // if imports * from fileName, don't add symbolName + if (importsAsterisk) { + return new NoopChange(); + } + + const importTextNodes = imports.filter( + n => (n as ts.Identifier).text === symbolName + ); + + // insert import if it's not there + if (importTextNodes.length === 0) { + const fallbackPos = + findNodes( + relevantImports[0], + ts.SyntaxKind.CloseBraceToken + )[0].getStart() || + findNodes(relevantImports[0], ts.SyntaxKind.FromKeyword)[0].getStart(); + + return insertAfterLastOccurrence( + imports, + `, ${symbolName}`, + fileToEdit, + fallbackPos + ); + } + + return new NoopChange(); + } + + // no such import declaration exists + const useStrict = findNodes(rootNode, ts.SyntaxKind.StringLiteral).filter( + (n: ts.StringLiteral) => n.text === 'use strict' + ); + let fallbackPos = 0; + if (useStrict.length > 0) { + fallbackPos = useStrict[0].end; + } + const open = isDefault ? '' : '{ '; + const close = isDefault ? '' : ' }'; + // if there are no imports or 'use strict' statement, insert import at beginning of file + const insertAtBeginning = allImports.length === 0 && useStrict.length === 0; + const separator = insertAtBeginning ? '' : ';\n'; + const toInsert = + `${separator}import ${open}${symbolName}${close}` + + ` from '${fileName}'${insertAtBeginning ? ';\n' : ''}`; + + return insertAfterLastOccurrence( + allImports, + toInsert, + fileToEdit, + fallbackPos, + ts.SyntaxKind.StringLiteral + ); +} diff --git a/packages/schematics/src/utils/name-utils.ts b/packages/schematics/src/utils/name-utils.ts index 3f6372b686..46e97ce9d3 100644 --- a/packages/schematics/src/utils/name-utils.ts +++ b/packages/schematics/src/utils/name-utils.ts @@ -1,3 +1,5 @@ +import * as path from 'path'; + export function names(name: string): any { return { name, @@ -30,3 +32,11 @@ export function toFileName(s: string): string { function toCapitalCase(s: string): string { return s.charAt(0).toUpperCase() + s.substr(1); } + +/** + * Determine the parent directory for the ngModule specified + * in the full-path option 'module' + */ +export function findModuleParent(modulePath) { + return path.dirname(modulePath); +} diff --git a/packages/schematics/src/utils/rules/deleteFile.ts b/packages/schematics/src/utils/rules/deleteFile.ts new file mode 100644 index 0000000000..e3ba0cdaad --- /dev/null +++ b/packages/schematics/src/utils/rules/deleteFile.ts @@ -0,0 +1,10 @@ +import { forEach, FileEntry, Rule } from '@angular-devkit/schematics'; + +/** + * Remove a file from the Virtual Schematic Tree + */ +export function deleteFile(from: string): Rule { + return forEach((entry: FileEntry): FileEntry | null => { + return entry.path === from ? null : entry; + }); +} diff --git a/packages/schematics/src/utils/strings.ts b/packages/schematics/src/utils/strings.ts new file mode 100644 index 0000000000..b2832e56b7 --- /dev/null +++ b/packages/schematics/src/utils/strings.ts @@ -0,0 +1,152 @@ +/** + * @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 + */ +const STRING_DASHERIZE_REGEXP = /[ _]/g; +const STRING_DECAMELIZE_REGEXP = /([a-z\d])([A-Z])/g; +const STRING_CAMELIZE_REGEXP = /(-|_|\.|\s)+(.)?/g; +const STRING_UNDERSCORE_REGEXP_1 = /([a-z\d])([A-Z]+)/g; +const STRING_UNDERSCORE_REGEXP_2 = /-|\s+/g; + +/** + * Converts a camelized string into all lower case separated by underscores. + * + ```javascript + decamelize('innerHTML'); // 'inner_html' + decamelize('action_name'); // 'action_name' + decamelize('css-class-name'); // 'css-class-name' + decamelize('my favorite items'); // 'my favorite items' + ``` + + @method decamelize + @param {String} str The string to decamelize. + @return {String} the decamelized string. + */ +export function decamelize(str: string): string { + return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase(); +} + +/** + Replaces underscores, spaces, or camelCase with dashes. + + ```javascript + dasherize('innerHTML'); // 'inner-html' + dasherize('action_name'); // 'action-name' + dasherize('css-class-name'); // 'css-class-name' + dasherize('my favorite items'); // 'my-favorite-items' + ``` + + @method dasherize + @param {String} str The string to dasherize. + @return {String} the dasherized string. + */ +export function dasherize(str?: string): string { + return decamelize(str || '').replace(STRING_DASHERIZE_REGEXP, '-'); +} + +/** + Returns the lowerCamelCase form of a string. + + ```javascript + camelize('innerHTML'); // 'innerHTML' + camelize('action_name'); // 'actionName' + camelize('css-class-name'); // 'cssClassName' + camelize('my favorite items'); // 'myFavoriteItems' + camelize('My Favorite Items'); // 'myFavoriteItems' + ``` + + @method camelize + @param {String} str The string to camelize. + @return {String} the camelized string. + */ +export function camelize(str: string): string { + return str + .replace( + STRING_CAMELIZE_REGEXP, + (_match: string, _separator: string, chr: string) => { + return chr ? chr.toUpperCase() : ''; + } + ) + .replace(/^([A-Z])/, (match: string) => match.toLowerCase()); +} + +/** + Returns the UpperCamelCase form of a string. + + ```javascript + 'innerHTML'.classify(); // 'InnerHTML' + 'action_name'.classify(); // 'ActionName' + 'css-class-name'.classify(); // 'CssClassName' + 'my favorite items'.classify(); // 'MyFavoriteItems' + ``` + + @method classify + @param {String} str the string to classify + @return {String} the classified string + */ +export function classify(str: string): string { + return str + .split('.') + .map(part => capitalize(camelize(part))) + .join('.'); +} + +/** + More general than decamelize. Returns the lower\_case\_and\_underscored + form of a string. + + ```javascript + 'innerHTML'.underscore(); // 'inner_html' + 'action_name'.underscore(); // 'action_name' + 'css-class-name'.underscore(); // 'css_class_name' + 'my favorite items'.underscore(); // 'my_favorite_items' + ``` + + @method underscore + @param {String} str The string to underscore. + @return {String} the underscored string. + */ +export function underscore(str: string): string { + return str + .replace(STRING_UNDERSCORE_REGEXP_1, '$1_$2') + .replace(STRING_UNDERSCORE_REGEXP_2, '_') + .toLowerCase(); +} + +/** + Returns the Capitalized form of a string + + ```javascript + 'innerHTML'.capitalize() // 'InnerHTML' + 'action_name'.capitalize() // 'Action_name' + 'css-class-name'.capitalize() // 'Css-class-name' + 'my favorite items'.capitalize() // 'My favorite items' + ``` + + @method capitalize + @param {String} str The string to capitalize. + @return {String} The capitalized string. + */ +export function capitalize(str: string): string { + return str.charAt(0).toUpperCase() + str.substr(1); +} + +export function group(name: string, group: string | undefined) { + return group ? `${group}/${name}` : name; +} + +export function featurePath( + group: boolean | undefined, + flat: boolean | undefined, + path: string, + name: string +) { + if (group && !flat) { + return `../../${path}/${name}/`; + } + + return group ? `../${path}/` : './'; +} diff --git a/packages/schematics/src/utils/testing-utils.ts b/packages/schematics/src/utils/testing-utils.ts index 2290ef34ce..25980b2227 100644 --- a/packages/schematics/src/utils/testing-utils.ts +++ b/packages/schematics/src/utils/testing-utils.ts @@ -1,5 +1,16 @@ import { Tree } from '@angular-devkit/schematics'; +export interface AppConfig { + appName: string; // name of app or lib + appModule: string; // app/app.module.ts in the above sourceDir +} + +var appConfig: AppConfig; // configure built in createApp() + +export function getAppConfig(): AppConfig { + return appConfig; +} + export function createEmptyWorkspace(tree: Tree): Tree { tree.create('/.angular-cli.json', JSON.stringify({})); tree.create('/package.json', JSON.stringify({})); @@ -22,8 +33,14 @@ export function createEmptyWorkspace(tree: Tree): Tree { } export function createApp(tree: Tree, appName: string): Tree { + // save for getAppDir() lookup by external *.spec.ts tests + appConfig = { + appName, + appModule: `/apps/${appName}/src/app/app.module.ts` + }; + tree.create( - `/apps/${appName}/src/app/app.module.ts`, + appConfig.appModule, ` import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; diff --git a/yarn.lock b/yarn.lock index 903b48d632..83695ee6df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -151,6 +151,10 @@ version "5.2.0" resolved "https://registry.yarnpkg.com/@ngrx/router-store/-/router-store-5.2.0.tgz#bf4b174ce19a36eba8211fc1ddeaf1e35ae74368" +"@ngrx/schematics@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@ngrx/schematics/-/schematics-5.2.0.tgz#ab7180d79f0ec68bd4ac3ef4ec83b9348d18d26c" + "@ngrx/store-devtools@5.2.0": version "5.2.0" resolved "https://registry.yarnpkg.com/@ngrx/store-devtools/-/store-devtools-5.2.0.tgz#2fff916a9aa349375826772b359dbb64b9e5d622" @@ -205,8 +209,8 @@ "@types/jasmine" "*" "@types/node@~6.0.60": - version "6.0.102" - resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.102.tgz#a6cf3b9843286b63eb362a8522bc382d96fe68d1" + version "6.0.103" + resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.103.tgz#4fddb6c254756e98004039da4e4f4230d1e397ca" "@types/prettier@^1.10.0": version "1.10.0" @@ -297,12 +301,13 @@ ajv@^5.0.0, ajv@^5.1.0, ajv@~5.5.1: json-schema-traverse "^0.3.0" ajv@^6.1.0, ajv@^6.1.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.2.1.tgz#28a6abc493a2abe0fb4c8507acaedb43fa550671" + version "6.4.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.4.0.tgz#d3aff78e9277549771daf0164cff48482b754fc6" dependencies: fast-deep-equal "^1.0.0" fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.3.0" + uri-js "^3.0.2" align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" @@ -567,8 +572,8 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" atob@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.0.3.tgz#19c7a760473774468f20b2d2d03372ad7d4cbf5d" + version "2.1.0" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.0.tgz#ab2b150e51d7b122b9efc8d7340c06b6c41076bc" autoprefixer@^7.1.1, autoprefixer@^7.2.3: version "7.2.6" @@ -928,8 +933,8 @@ brorand@^1.0.1: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" browser-pack@^6.0.1: - version "6.0.4" - resolved "https://registry.yarnpkg.com/browser-pack/-/browser-pack-6.0.4.tgz#9a73beb3b48f9e36868be007b64400102c04a99f" + version "6.1.0" + resolved "https://registry.yarnpkg.com/browser-pack/-/browser-pack-6.1.0.tgz#c34ba10d0b9ce162b5af227c7131c92c2ecd5774" dependencies: JSONStream "^1.0.3" combine-source-map "~0.8.0" @@ -1071,6 +1076,10 @@ buffer-crc32@^0.2.5: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" +buffer-from@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.0.0.tgz#4cb8832d23612589b0406e9e2956c17f06fdf531" + buffer-indexof@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" @@ -1210,8 +1219,8 @@ camelcase@^4.0.0, camelcase@^4.1.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" caniuse-lite@^1.0.30000792, caniuse-lite@^1.0.30000805: - version "1.0.30000813" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000813.tgz#7b25e27fdfb8d133f3c932b01f77452140fcc6c9" + version "1.0.30000821" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000821.tgz#0f3223f1e048ed96451c56ca6cf197058c42cb93" capture-stack-trace@^1.0.0: version "1.0.0" @@ -1242,7 +1251,7 @@ chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1: +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.2.tgz#250dc96b07491bfd601e648d66ddf5f60c7a5c65" dependencies: @@ -1274,8 +1283,8 @@ chokidar@^1.4.1, chokidar@^1.4.2, chokidar@^1.6.0, chokidar@^1.7.0: fsevents "^1.0.0" chokidar@^2.0.0, chokidar@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.2.tgz#4dc65139eeb2714977735b6a35d06e97b494dfd7" + version "2.0.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.3.tgz#dcbd4f6cbb2a55b4799ba8a840ac527e5f4b1176" dependencies: anymatch "^2.0.0" async-each "^1.0.0" @@ -1289,7 +1298,7 @@ chokidar@^2.0.0, chokidar@^2.0.2: readdirp "^2.0.0" upath "^1.0.0" optionalDependencies: - fsevents "^1.0.0" + fsevents "^1.1.2" chownr@^1.0.1: version "1.0.1" @@ -1369,8 +1378,8 @@ clone-deep@^2.0.1: shallow-clone "^1.0.0" clone@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.1.tgz#d217d1e961118e3ac9a4b8bba3285553bf647cdb" + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" co@^4.6.0: version "4.6.0" @@ -1415,16 +1424,7 @@ combine-lists@^1.0.0: dependencies: lodash "^4.5.0" -combine-source-map@~0.7.1: - version "0.7.2" - resolved "https://registry.yarnpkg.com/combine-source-map/-/combine-source-map-0.7.2.tgz#0870312856b307a87cc4ac486f3a9a62aeccc09e" - dependencies: - convert-source-map "~1.1.0" - inline-source-map "~0.6.0" - lodash.memoize "~3.0.3" - source-map "~0.5.3" - -combine-source-map@~0.8.0: +combine-source-map@^0.8.0, combine-source-map@~0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/combine-source-map/-/combine-source-map-0.8.0.tgz#a58d0df042c186fcf822a8e8015f5450d2d79a8b" dependencies: @@ -1439,13 +1439,9 @@ combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5: dependencies: delayed-stream "~1.0.0" -commander@2.14.x, commander@~2.14.1: - version "2.14.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa" - -commander@^2.12.0, commander@^2.12.1, commander@^2.9.0: - version "2.15.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.0.tgz#ad2a23a1c3b036e392469b8012cec6b33b4c1322" +commander@2.15.x, commander@^2.12.0, commander@^2.12.1, commander@^2.9.0, commander@~2.15.0: + version "2.15.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" commander@~2.13.0: version "2.13.0" @@ -1489,7 +1485,7 @@ compressible@~2.0.13: compression@^1.5.2: version "1.7.2" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.2.tgz#aaffbcd6aaf854b44ebb280353d5ad1651f59a69" + resolved "http://registry.npmjs.org/compression/-/compression-1.7.2.tgz#aaffbcd6aaf854b44ebb280353d5ad1651f59a69" dependencies: accepts "~1.3.4" bytes "3.0.0" @@ -1503,10 +1499,11 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" -concat-stream@^1.5.0: - version "1.6.1" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.1.tgz#261b8f518301f1d834e36342b9fea095d2620a26" +concat-stream@^1.5.0, concat-stream@^1.6.1: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" dependencies: + buffer-from "^1.0.0" inherits "^2.0.3" readable-stream "^2.2.2" typedarray "^0.0.6" @@ -1520,8 +1517,8 @@ concat-stream@~1.5.0, concat-stream@~1.5.1: typedarray "~0.0.5" configstore@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.1.tgz#094ee662ab83fad9917678de114faaea8fcdca90" + version "3.1.2" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f" dependencies: dot-prop "^4.1.0" graceful-fs "^4.1.2" @@ -1614,8 +1611,8 @@ copy-webpack-plugin@~4.4.1: serialize-javascript "^1.4.0" core-js@^2.2.0, core-js@^2.4.0, core-js@^2.5.0: - version "2.5.3" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e" + version "2.5.4" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.4.tgz#f2c8bf181f2a80b92f360121429ce63a2f0aeae0" core-object@^3.1.0: version "3.1.5" @@ -2112,12 +2109,12 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" ejs@^2.5.7: - version "2.5.7" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a" + version "2.5.8" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.8.tgz#2ab6954619f225e6193b7ac5f7c39c48fefe4380" electron-to-chromium@^1.3.30: - version "1.3.37" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.37.tgz#4a92734e0044c8cf0b1553be57eae21a4c6e5fab" + version "1.3.41" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.41.tgz#7e33643e00cd85edfd17e04194f6d00e73737235" elliptic@^6.0.0: version "6.4.0" @@ -2218,8 +2215,8 @@ error-ex@^1.2.0, error-ex@^1.3.1: is-arrayish "^0.2.1" es-abstract@^1.4.3, es-abstract@^1.7.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.10.0.tgz#1ecb36c197842a00d8ee4c2dfd8646bb97d60864" + version "1.11.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.11.0.tgz#cce87d518f0496893b1a30cd8461835535480681" dependencies: es-to-primitive "^1.1.1" function-bind "^1.1.1" @@ -2236,11 +2233,12 @@ es-to-primitive@^1.1.1: is-symbol "^1.0.1" es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14: - version "0.10.40" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.40.tgz#ab3d2179b943008c5e9ef241beb25ef41424c774" + version "0.10.42" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.42.tgz#8c07dd33af04d5dcd1310b5cef13bea63a89ba8d" dependencies: es6-iterator "~2.0.3" es6-symbol "~3.1.1" + next-tick "1" es6-iterator@^2.0.1, es6-iterator@~2.0.1, es6-iterator@~2.0.3: version "2.0.3" @@ -2681,8 +2679,8 @@ find-up@^2.0.0, find-up@^2.1.0: locate-path "^2.0.0" flush-write-stream@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.2.tgz#c81b90d8746766f1a609a46809946c45dd8ae417" + version "1.0.3" + resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.3.tgz#c5d586ef38af6097650b49bc41b55fabb19f35bd" dependencies: inherits "^2.0.1" readable-stream "^2.0.4" @@ -2805,7 +2803,7 @@ fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" -fsevents@^1.0.0: +fsevents@^1.0.0, fsevents@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.3.tgz#11f82318f5fe7bb2cd22965a108e9306208216d8" dependencies: @@ -3229,12 +3227,12 @@ html-entities@^1.2.0: resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" html-minifier@^3.2.3: - version "3.5.10" - resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-3.5.10.tgz#8522c772c388db81aa5c26f62033302d906ea1c7" + version "3.5.12" + resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-3.5.12.tgz#6bfad4d0327f5b8d2b62f5854654ac3703b9b031" dependencies: camel-case "3.0.x" clean-css "4.1.x" - commander "2.14.x" + commander "2.15.x" he "1.1.x" ncname "1.0.x" param-case "2.1.x" @@ -3269,7 +3267,7 @@ http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" -http-errors@1.6.2, http-errors@~1.6.2: +http-errors@1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" dependencies: @@ -3278,6 +3276,15 @@ http-errors@1.6.2, http-errors@~1.6.2: setprototypeof "1.0.3" statuses ">= 1.3.1 < 2" +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + http-parser-js@>=0.4.0: version "0.4.11" resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.11.tgz#5b720849c650903c27e521633d94696ee95f3529" @@ -3362,8 +3369,8 @@ iconv-lite@0.4.19: resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" ieee754@^1.1.4: - version "1.1.8" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" + version "1.1.11" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.11.tgz#c16384ffe00f5b7835824e67b6f2bd44a5229455" iferr@^0.1.5: version "0.1.5" @@ -3444,12 +3451,12 @@ inline-source-map@~0.6.0: source-map "~0.5.3" insert-module-globals@^7.0.0: - version "7.0.2" - resolved "https://registry.yarnpkg.com/insert-module-globals/-/insert-module-globals-7.0.2.tgz#012c56baa7d3307a8b417d4ec5270cf9741c18f4" + version "7.0.5" + resolved "https://registry.yarnpkg.com/insert-module-globals/-/insert-module-globals-7.0.5.tgz#6d0a6f28d4a7e0eae171ad305e0f47bdfe0c258e" dependencies: JSONStream "^1.0.3" - combine-source-map "~0.7.1" - concat-stream "~1.5.1" + combine-source-map "^0.8.0" + concat-stream "^1.6.1" is-buffer "^1.1.0" lexical-scope "^1.2.0" process "~0.11.0" @@ -3467,8 +3474,8 @@ interpret@^1.0.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" invariant@^2.2.2: - version "2.2.3" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.3.tgz#1a827dfde7dcbd7c323f0ca826be8fa7c5e9d688" + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" dependencies: loose-envify "^1.0.0" @@ -3692,8 +3699,8 @@ is-path-cwd@^1.0.0: resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" is-path-in-cwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz#5ac48b345ef675339bd6c7a48a912110b241cf52" dependencies: is-path-inside "^1.0.0" @@ -3759,7 +3766,7 @@ is-wsl@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" -isarray@0.0.1, isarray@~0.0.1: +isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" @@ -3771,6 +3778,10 @@ isarray@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e" +isarray@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.4.tgz#38e7bcbb0f3ba1b7933c86ba1894ddfc3781bbb7" + isbinaryfile@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-3.0.2.tgz#4a3e974ec0cba9004d3fc6cde7209ea69368a621" @@ -3811,8 +3822,8 @@ istanbul-api@^1.1.1: once "^1.4.0" istanbul-instrumenter-loader@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/istanbul-instrumenter-loader/-/istanbul-instrumenter-loader-3.0.0.tgz#9f553923b22360bac95e617aaba01add1f7db0b2" + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-instrumenter-loader/-/istanbul-instrumenter-loader-3.0.1.tgz#9957bd59252b373fae5c52b7b5188e6fde2a0949" dependencies: convert-source-map "^1.5.0" istanbul-lib-instrument "^1.7.3" @@ -4155,8 +4166,8 @@ json-loader@^0.5.4: resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d" json-parse-better-errors@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.1.tgz#50183cd1b2d25275de069e9e71b467ac9eab973a" + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" json-schema-traverse@^0.3.0: version "0.3.1" @@ -4302,11 +4313,11 @@ kind-of@^6.0.0, kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" labeled-stream-splicer@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/labeled-stream-splicer/-/labeled-stream-splicer-2.0.0.tgz#a52e1d138024c00b86b1c0c91f677918b8ae0a59" + version "2.0.1" + resolved "https://registry.yarnpkg.com/labeled-stream-splicer/-/labeled-stream-splicer-2.0.1.tgz#9cffa32fd99e1612fd1d86a8db962416d5292926" dependencies: inherits "^2.0.1" - isarray "~0.0.1" + isarray "^2.0.4" stream-splicer "^2.0.0" latest-version@^3.0.0: @@ -4380,8 +4391,8 @@ libqp@1.1.0: resolved "https://registry.yarnpkg.com/libqp/-/libqp-1.1.0.tgz#f5e6e06ad74b794fb5b5b66988bf728ef1dedbe8" license-webpack-plugin@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/license-webpack-plugin/-/license-webpack-plugin-1.3.0.tgz#b2c547f1a16d80426eecef38560d1312438d988e" + version "1.3.1" + resolved "https://registry.yarnpkg.com/license-webpack-plugin/-/license-webpack-plugin-1.3.1.tgz#688b76472188ef597918b7cae3eec7dc2fa5a0e8" dependencies: ejs "^2.5.7" @@ -4528,8 +4539,8 @@ lower-case@^1.1.1: resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" lowercase-keys@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" lru-cache@4.1.x, lru-cache@^4.0.1, lru-cache@^4.1.1: version "4.1.2" @@ -4542,12 +4553,18 @@ lru-cache@~2.6.5: version "2.6.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.6.5.tgz#e56d6354148ede8d7707b58d143220fd08df0fd5" -magic-string@0.22.4, magic-string@^0.22.3, magic-string@^0.22.4: +magic-string@0.22.4: version "0.22.4" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.22.4.tgz#31039b4e40366395618c1d6cf8193c53917475ff" dependencies: vlq "^0.2.1" +magic-string@^0.22.3, magic-string@^0.22.4: + version "0.22.5" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.22.5.tgz#8e9cf5afddf44385c1da5bc2a6a0dbd10b03657e" + dependencies: + vlq "^0.2.2" + mailcomposer@4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/mailcomposer/-/mailcomposer-4.0.1.tgz#0e1c44b2a07cf740ee17dc149ba009f19cadfeb4" @@ -4673,8 +4690,8 @@ micromatch@^2.1.5, micromatch@^2.3.11: regex-cache "^0.4.2" micromatch@^3.1.4, micromatch@^3.1.8: - version "3.1.9" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.9.tgz#15dc93175ae39e52e93087847096effc73efcf89" + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" dependencies: arr-diff "^4.0.0" array-unique "^0.3.2" @@ -4688,7 +4705,7 @@ micromatch@^3.1.4, micromatch@^3.1.8: object.pick "^1.3.0" regex-not "^1.0.0" snapdragon "^0.8.1" - to-regex "^3.0.1" + to-regex "^3.0.2" miller-rabin@^4.0.0: version "4.0.1" @@ -4838,9 +4855,9 @@ multicast-dns@^6.0.1: dns-packet "^1.3.1" thunky "^1.0.2" -nan@^2.3.0, nan@^2.3.2: - version "2.9.2" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.9.2.tgz#f564d75f5f8f36a6d9456cca7a6c4fe488ab7866" +nan@^2.10.0, nan@^2.3.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" nanomatch@^1.2.9: version "1.2.9" @@ -4881,6 +4898,10 @@ netmask@~1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35" +next-tick@1: + version "1.0.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" + ng-packagr@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/ng-packagr/-/ng-packagr-2.2.0.tgz#a66b7d822c40e8aa0dee34e06cca05151c522a3b" @@ -5004,14 +5025,14 @@ node-pre-gyp@^0.6.39: tar-pack "^3.4.0" node-sass-tilde-importer@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/node-sass-tilde-importer/-/node-sass-tilde-importer-1.0.1.tgz#3eab5247a3bf53354766bb1e6fd2214ce17e74ee" + version "1.0.2" + resolved "https://registry.yarnpkg.com/node-sass-tilde-importer/-/node-sass-tilde-importer-1.0.2.tgz#1a15105c153f648323b4347693fdb0f331bad1ce" dependencies: find-parent-dir "^0.3.0" node-sass@^4.5.3, node-sass@^4.7.2: - version "4.7.2" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.7.2.tgz#9366778ba1469eb01438a9e8592f4262bcb6794e" + version "4.8.3" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.8.3.tgz#d077cc20a08ac06f661ca44fb6f19cd2ed41debb" dependencies: async-foreach "^0.1.3" chalk "^1.1.1" @@ -5025,7 +5046,7 @@ node-sass@^4.5.3, node-sass@^4.7.2: lodash.mergewith "^4.6.0" meow "^3.7.0" mkdirp "^0.5.1" - nan "^2.3.2" + nan "^2.10.0" node-gyp "^3.3.1" npmlog "^4.0.0" request "~2.79.0" @@ -5170,8 +5191,8 @@ number-is-nan@^1.0.0: resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" "nwmatcher@>= 1.3.9 < 2.0.0": - version "1.4.3" - resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.3.tgz#64348e3b3d80f035b40ac11563d278f8b72db89c" + version "1.4.4" + resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.4.tgz#2285631f34a95f0d0395cd900c96ed39b58f346e" oauth-sign@~0.8.1, oauth-sign@~0.8.2: version "0.8.2" @@ -5243,8 +5264,8 @@ onetime@^2.0.0: mimic-fn "^1.0.0" opn@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/opn/-/opn-5.2.0.tgz#71fdf934d6827d676cecbea1531f95d354641225" + version "5.3.0" + resolved "https://registry.yarnpkg.com/opn/-/opn-5.3.0.tgz#64871565c863875f052cfdf53d3e3cb5adb53b1c" dependencies: is-wsl "^1.1.0" @@ -5625,8 +5646,8 @@ postcss-load-plugins@^2.3.0: object-assign "^4.1.0" postcss-loader@^2.0.10: - version "2.1.1" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.1.1.tgz#208935af3b1d65e1abb1a870a912dd12e7b36895" + version "2.1.3" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.1.3.tgz#eb210da734e475a244f76ccd61f9860f5bb3ee09" dependencies: loader-utils "^1.1.0" postcss "^6.0.0" @@ -5648,12 +5669,12 @@ postcss-value-parser@^3.2.3: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15" postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.16, postcss@^6.0.17, postcss@^6.0.2, postcss@^6.x: - version "6.0.19" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.19.tgz#76a78386f670b9d9494a655bf23ac012effd1555" + version "6.0.21" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.21.tgz#8265662694eddf9e9a5960db6da33c39e4cd069d" dependencies: - chalk "^2.3.1" + chalk "^2.3.2" source-map "^0.6.1" - supports-color "^5.2.0" + supports-color "^5.3.0" precise-commits@1.0.0: version "1.0.0" @@ -5790,6 +5811,10 @@ punycode@1.4.1, punycode@^1.2.4, punycode@^1.3.2, punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" +punycode@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.0.tgz#5f863edc89b96db09074bad7947bf09056ca4e7d" + q@~1.4.0: version "1.4.1" resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e" @@ -5868,8 +5893,8 @@ raw-loader@^0.5.1: resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa" rc@^1.0.1, rc@^1.1.6, rc@^1.1.7: - version "1.2.5" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.5.tgz#275cd687f6e3b36cc756baa26dfee80a790301fd" + version "1.2.6" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.6.tgz#eb18989c6d4f4f162c399f79ddd29f3835568092" dependencies: deep-extend "~0.4.0" ini "~1.3.0" @@ -6229,8 +6254,8 @@ resolve@1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" resolve@^1.1.3, resolve@^1.1.4, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.2, resolve@^1.4.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36" + version "1.6.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.6.0.tgz#0fbd21278b27b4004481c395349e7aba60a9ff5c" dependencies: path-parse "^1.0.5" @@ -6293,8 +6318,8 @@ rollup-plugin-license@^0.6.0: moment "2.21.0" rollup-plugin-node-resolve@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-3.2.0.tgz#31534952f3ab21f9473c1d092be7ed43937ea4d4" + version "3.3.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-3.3.0.tgz#c26d110a36812cbefa7ce117cadcd3439aa1c713" dependencies: builtin-modules "^2.0.0" is-module "^1.0.0" @@ -6318,8 +6343,8 @@ run-queue@^1.0.0, run-queue@^1.0.3: aproba "^1.1.1" rxjs@^5.5.0, rxjs@^5.5.6: - version "5.5.6" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.6.tgz#e31fb96d6fd2ff1fd84bcea8ae9c02d007179c02" + version "5.5.8" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.8.tgz#b2b0809a57614ad6254c03d7446dea0d83ca3791" dependencies: symbol-observable "1.0.1" @@ -6521,8 +6546,8 @@ setprototypeof@1.1.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" sha.js@^2.4.0, sha.js@^2.4.8, sha.js@~2.4.4: - version "2.4.10" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.10.tgz#b1fde5cd7d11a5626638a07c604ab909cfa31f9b" + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" dependencies: inherits "^2.0.1" safe-buffer "^5.0.1" @@ -6750,8 +6775,8 @@ source-map-support@^0.4.1, source-map-support@^0.4.15: source-map "^0.5.6" source-map-support@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.3.tgz#2b3d5fff298cfa4d1afd7d4352d569e9a0158e76" + version "0.5.4" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.4.tgz#54456efa89caa9270af7cd624cc2f123e51fbae8" dependencies: source-map "^0.6.0" @@ -6780,10 +6805,8 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" sourcemap-codec@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.0.tgz#905439c4c65a4db421a2f2d06065bd8b55846a8e" - dependencies: - vlq "^1.0.0" + version "1.4.1" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.1.tgz#c8fd92d91889e902a07aee392bdd2c5863958ba2" spdx-correct@^3.0.0: version "3.0.0" @@ -6808,8 +6831,8 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz#7a7cd28470cc6d3a1cfe6d66886f6bc430d3ac87" spdy-transport@^2.0.18: - version "2.0.20" - resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-2.0.20.tgz#735e72054c486b2354fe89e702256004a39ace4d" + version "2.1.0" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-2.1.0.tgz#4bbb15aaffed0beefdd56ad61dbdc8ba3e2cb7a1" dependencies: debug "^2.6.8" detect-node "^2.0.3" @@ -6847,8 +6870,8 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" sshpk@^1.7.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" + version "1.14.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.1.tgz#130f5975eddad963f1d56f92b9ac6c51fa9f83eb" dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" @@ -6861,8 +6884,8 @@ sshpk@^1.7.0: tweetnacl "~0.14.0" ssri@^5.2.4: - version "5.2.4" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-5.2.4.tgz#9985e14041e65fc397af96542be35724ac11da52" + version "5.3.0" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-5.3.0.tgz#ba3872c9c6d33a0704a7d71ff045e5ec48999d06" dependencies: safe-buffer "^5.1.1" @@ -6873,14 +6896,18 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -"statuses@>= 1.3.1 < 2", statuses@~1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" +"statuses@>= 1.3.1 < 2", "statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" statuses@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" +statuses@~1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" + stdout-stream@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.0.tgz#a2c7c8587e54d9427ea9edb3ac3f2cd522df378b" @@ -6915,8 +6942,8 @@ stream-each@^1.1.0: stream-shift "^1.0.0" stream-http@^2.0.0, stream-http@^2.7.2: - version "2.8.0" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.0.tgz#fd86546dac9b1c91aff8fc5d287b98fafb41bc10" + version "2.8.1" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.1.tgz#d0441be1a457a73a733a8a7b53570bebd9ef66a4" dependencies: builtin-status-codes "^3.0.0" inherits "^2.0.1" @@ -6973,9 +7000,9 @@ string.prototype.padend@^3.0.0: es-abstract "^1.4.3" function-bind "^1.0.2" -string_decoder@^1.0.0, string_decoder@~1.0.0, string_decoder@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" +string_decoder@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" dependencies: safe-buffer "~5.1.0" @@ -6983,6 +7010,12 @@ string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" +string_decoder@~1.0.0, string_decoder@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" + dependencies: + safe-buffer "~5.1.0" + stringstream@~0.0.4, stringstream@~0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" @@ -7075,7 +7108,7 @@ supports-color@^4.0.0, supports-color@^4.2.1: dependencies: has-flag "^2.0.0" -supports-color@^5.1.0, supports-color@^5.2.0, supports-color@^5.3.0: +supports-color@^5.1.0, supports-color@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.3.0.tgz#5b24ac15db80fa927cf5227a4a33fd3c4c7676c0" dependencies: @@ -7218,7 +7251,7 @@ to-regex-range@^2.1.0: is-number "^3.0.0" repeat-string "^1.6.1" -to-regex@^3.0.1: +to-regex@^3.0.1, to-regex@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" dependencies: @@ -7294,8 +7327,8 @@ tsscmp@~1.0.0: resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.5.tgz#7dc4a33af71581ab4337da91d85ca5427ebd9a97" tsutils@^2.12.1: - version "2.22.2" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.22.2.tgz#0b9f3d87aa3eb95bd32d26ce2b88aa329a657951" + version "2.25.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.25.0.tgz#14d616ef59224a3c9fb7eb483e1c182b6665ae5e" dependencies: tslib "^1.8.1" @@ -7350,10 +7383,10 @@ uglify-es@^3.3.4: source-map "~0.6.1" uglify-js@3.3.x, uglify-js@^3.0.7: - version "3.3.14" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.3.14.tgz#d3d84d18722ff342fa96029cca71c67367700079" + version "3.3.16" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.3.16.tgz#23ba13efa27aa00885be7417819e8a9787f94028" dependencies: - commander "~2.14.1" + commander "~2.15.0" source-map "~0.6.1" uglify-js@^2.6, uglify-js@^2.8.29: @@ -7378,8 +7411,8 @@ uglifyjs-webpack-plugin@^0.4.6: webpack-sources "^1.0.1" uglifyjs-webpack-plugin@^1.1.8: - version "1.2.3" - resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.3.tgz#bf23197b37a8fc953fecfbcbab66e506f9a0ae72" + version "1.2.4" + resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.4.tgz#5eec941b2e9b8538be0a20fc6eda25b14c7c1043" dependencies: cacache "^10.0.4" find-cache-dir "^1.0.0" @@ -7399,8 +7432,8 @@ ultron@~1.1.0: resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" umd@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/umd/-/umd-3.0.2.tgz#95bdbc6d3983050df600431f44e5faeb4b7b3d45" + version "3.0.3" + resolved "https://registry.yarnpkg.com/umd/-/umd-3.0.3.tgz#aa9fe653c42b9097678489c01000acb69f0b26cf" underscore@~1.7.0: version "1.7.0" @@ -7457,13 +7490,14 @@ upath@^1.0.0: resolved "https://registry.yarnpkg.com/upath/-/upath-1.0.4.tgz#ee2321ba0a786c50973db043a50b7bcba822361d" update-notifier@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.3.0.tgz#4e8827a6bb915140ab093559d7014e3ebb837451" + version "2.4.0" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.4.0.tgz#f9b4c700fbfd4ec12c811587258777d563d8c866" dependencies: boxen "^1.2.1" chalk "^2.0.1" configstore "^3.0.0" import-lazy "^2.1.0" + is-ci "^1.0.10" is-installed-globally "^0.1.0" is-npm "^1.0.0" latest-version "^3.0.0" @@ -7474,6 +7508,12 @@ upper-case@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" +uri-js@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-3.0.2.tgz#f90b858507f81dea4dcfbb3c4c3dbfa2b557faaa" + dependencies: + punycode "^2.1.0" + urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" @@ -7575,14 +7615,10 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -vlq@^0.2.1: +vlq@^0.2.1, vlq@^0.2.2: version "0.2.3" resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26" -vlq@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/vlq/-/vlq-1.0.0.tgz#8101be90843422954c2b13eb27f2f3122bdcc806" - vm-browserify@0.0.4, vm-browserify@~0.0.1: version "0.0.4" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73" @@ -7961,5 +7997,5 @@ yeast@0.1.2: resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" zone.js@^0.8.19: - version "0.8.20" - resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.8.20.tgz#a218c48db09464b19ff6fc8f0d4bb5b1046e185d" + version "0.8.21" + resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.8.21.tgz#8c0e8e361bd326ee1474e4f96e17d5ef99bec4b2"