diff --git a/.gitignore b/.gitignore index 3c1c126779eb7..118dc126a9a70 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ node_modules .idea .vscode dist -build +/build test .DS_Store tmp diff --git a/.prettierignore b/.prettierignore index 239bccbf8659a..207e2e9e7fedd 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,5 +1,5 @@ tmp -build +/build node_modules /package.json -packages/schematics/src/collection/**/files/*.json \ No newline at end of file +packages/schematics/src/collection/**/files/*.json diff --git a/package.json b/package.json index 794e956199f6b..cd32a6fce6fb1 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,9 @@ "checkformat": "prettier \"./**/*.{ts,js,json,css,md}\" \"!./**/{__name__,__directory__}/**\" --list-different" }, "devDependencies": { + "@angular-devkit/architect": "0.8.3", "@angular-devkit/build-angular": "0.8.3", + "@angular-devkit/build-webpack": "0.8.3", "@angular-devkit/core": "0.8.3", "@angular-devkit/schematics": "0.8.3", "@angular/cli": "6.2.3", @@ -39,15 +41,19 @@ "@schematics/angular": "0.8.3", "@types/jasmine": "~2.8.6", "@types/jasminewd2": "~2.0.3", + "@types/jest": "^23.3.2", "@types/node": "~8.9.4", "@types/prettier": "^1.10.0", + "@types/webpack": "^4.4.11", "@types/yargs": "^11.0.0", "angular": "1.6.6", "app-root-path": "^2.0.1", + "circular-dependency-plugin": "^5.0.2", "commitizen": "^2.10.1", "conventional-changelog-cli": "^1.3.21", "cosmiconfig": "^4.0.0", "cz-conventional-changelog": "^2.1.0", + "fork-ts-checker-webpack-plugin": "^0.4.9", "fs-extra": "5.0.0", "graphviz": "^0.0.8", "husky": "^1.0.0-rc.13", @@ -60,6 +66,7 @@ "karma-chrome-launcher": "~2.2.0", "karma-jasmine": "~1.1.1", "karma-webpack": "2.0.4", + "license-webpack-plugin": "^1.4.0", "lint-staged": "^7.2.2", "ng-packagr": "3.0.6", "npm-run-all": "4.1.2", @@ -74,6 +81,8 @@ "tslint": "5.11.0", "typescript": "~2.9.2", "viz.js": "^1.8.1", + "webpack": "4.9.2", + "webpack-node-externals": "^1.7.2", "yargs": "^11.0.0", "yargs-parser": "10.0.0", "zone.js": "^0.8.26" diff --git a/packages/builders/package.json b/packages/builders/package.json index 327a7de760858..ca6144e2b12d2 100644 --- a/packages/builders/package.json +++ b/packages/builders/package.json @@ -25,6 +25,12 @@ "builders": "./src/builders.json", "dependencies": { "@angular-devkit/architect": "~0.8.0", - "rxjs": "6.2.2" + "@angular-devkit/build-webpack": "~0.8.0", + "fork-ts-checker-webpack-plugin": "0.4.9", + "license-webpack-plugin": "^1.4.0", + "rxjs": "6.2.2", + "ts-loader": "4.5.0", + "webpack": "4.9.2", + "webpack-node-externals": "1.7.2" } } diff --git a/packages/builders/src/builders.json b/packages/builders/src/builders.json index f2e19deaf6059..64e46103caa40 100644 --- a/packages/builders/src/builders.json +++ b/packages/builders/src/builders.json @@ -1,6 +1,16 @@ { - "$schema": "../architect/src/builders-schema.json", + "$schema": "@angular-devkit/architect/src/builders-schema.json", "builders": { + "node-build": { + "class": "./node/build/node-build.builder", + "schema": "./node/build/schema.json", + "description": "Build a Node application" + }, + "node-execute": { + "class": "./node/execute/node-execute.builder", + "schema": "./node/execute/schema.json", + "description": "Build a Node application" + }, "jest": { "class": "./jest/jest.builder", "schema": "./jest/schema.json", diff --git a/packages/builders/src/jest/jest.builder.spec.ts b/packages/builders/src/jest/jest.builder.spec.ts index 44a6f7fd25f65..d3ac203b0707c 100644 --- a/packages/builders/src/jest/jest.builder.spec.ts +++ b/packages/builders/src/jest/jest.builder.spec.ts @@ -1,6 +1,7 @@ import JestBuilder from './jest.builder'; import { normalize } from '@angular-devkit/core'; -import * as jestCLI from 'jest'; +jest.mock('jest'); +const { runCLI } = require('jest'); import * as path from 'path'; describe('Jest Builder', () => { @@ -8,16 +9,16 @@ describe('Jest Builder', () => { beforeEach(() => { builder = new JestBuilder(); - }); - - it('should send appropriate options to jestCLI', () => { - const runCLI = spyOn(jestCLI, 'runCLI').and.returnValue( + runCLI.mockReturnValue( Promise.resolve({ results: { success: true } }) ); + }); + + it('should send appropriate options to jestCLI', () => { const root = normalize('/root'); builder .run({ @@ -46,13 +47,6 @@ describe('Jest Builder', () => { }); it('should send other options to jestCLI', () => { - const runCLI = spyOn(jestCLI, 'runCLI').and.returnValue( - Promise.resolve({ - results: { - success: true - } - }) - ); const root = normalize('/root'); builder .run({ @@ -98,19 +92,12 @@ describe('Jest Builder', () => { ); }); - it('should send the main to jestCLI', () => { - const runCLI = spyOn(jestCLI, 'runCLI').and.returnValue( - Promise.resolve({ - results: { - success: true - } - }) - ); + it('should send the main to runCLI', () => { const root = normalize('/root'); builder .run({ root, - builder: '', + builder: '@nrwl/builders:jest', projectType: 'application', options: { jestConfig: './jest.config.js', @@ -139,13 +126,6 @@ describe('Jest Builder', () => { }); it('should return the proper result', async done => { - spyOn(jestCLI, 'runCLI').and.returnValue( - Promise.resolve({ - results: { - success: true - } - }) - ); const root = normalize('/root'); const result = await builder .run({ diff --git a/packages/builders/src/jest/jest.builder.ts b/packages/builders/src/jest/jest.builder.ts index 6f5e576fd70f9..b1b5319c0ff52 100644 --- a/packages/builders/src/jest/jest.builder.ts +++ b/packages/builders/src/jest/jest.builder.ts @@ -9,7 +9,7 @@ import { map } from 'rxjs/operators'; import * as path from 'path'; -import { runCLI as runJest } from 'jest'; +const { runCLI } = require('jest'); export interface JestBuilderOptions { jestConfig: string; @@ -61,7 +61,7 @@ export default class JestBuilder implements Builder { ); } - return from(runJest(config, [options.jestConfig])).pipe( + return from(runCLI(config, [options.jestConfig])).pipe( map((results: any) => { return { success: results.results.success diff --git a/packages/builders/src/node/build/node-build.builder.spec.ts b/packages/builders/src/node/build/node-build.builder.spec.ts new file mode 100644 index 0000000000000..aebf78f3b7591 --- /dev/null +++ b/packages/builders/src/node/build/node-build.builder.spec.ts @@ -0,0 +1,162 @@ +import { normalize } from '@angular-devkit/core'; +import { TestLogger } from '@angular-devkit/architect/testing'; +import BuildNodeBuilder from './node-build.builder'; +import { BuildNodeBuilderOptions } from './node-build.builder'; +import { of } from 'rxjs'; +import * as fs from 'fs'; + +describe('NodeBuildBuilder', () => { + let builder: BuildNodeBuilder; + let testOptions: BuildNodeBuilderOptions; + + beforeEach(() => { + builder = new BuildNodeBuilder({ + host: {}, + logger: new TestLogger('test'), + workspace: { + root: '/root' + }, + architect: {} + }); + testOptions = { + main: 'apps/nodeapp/src/main.ts', + tsConfig: 'apps/nodeapp/tsconfig.app.json', + outputPath: 'dist/apps/nodeapp', + externalDependencies: 'all', + fileReplacements: [ + { + replace: 'apps/environment/environment.ts', + with: 'apps/environment/environment.prod.ts' + }, + { + replace: 'module1.ts', + with: 'module2.ts' + } + ] + }; + }); + + describe('run', () => { + it('should call runWebpack', () => { + const runWebpack = spyOn( + builder.webpackBuilder, + 'runWebpack' + ).and.returnValue( + of({ + success: true + }) + ); + + builder.run({ + root: normalize('/root'), + projectType: 'application', + builder: '@nrwl/builders:node-build', + options: testOptions + }); + + expect(runWebpack).toHaveBeenCalled(); + }); + + it('should emit the outfile along with success', async () => { + const runWebpack = spyOn( + builder.webpackBuilder, + 'runWebpack' + ).and.returnValue( + of({ + success: true + }) + ); + + const buildEvent = await builder + .run({ + root: normalize('/root'), + projectType: 'application', + builder: '@nrwl/builders:node-build', + options: testOptions + }) + .toPromise(); + + expect(buildEvent.success).toEqual(true); + expect(buildEvent.outfile).toEqual('/root/dist/apps/nodeapp/main.js'); + }); + + describe('when stats json option is passed', () => { + beforeEach(() => { + const stats = { + stats: 'stats' + }; + spyOn(builder.webpackBuilder, 'runWebpack').and.callFake((opts, cb) => { + cb({ + toJson: () => stats, + toString: () => JSON.stringify(stats) + }); + return of({ + success: true + }); + }); + spyOn(fs, 'writeFileSync'); + }); + + it('should generate a stats json', async () => { + await builder + .run({ + root: normalize('/root'), + projectType: 'application', + builder: '@nrwl/builders:node-build', + options: { + ...testOptions, + statsJson: true + } + }) + .toPromise(); + + expect(fs.writeFileSync).toHaveBeenCalledWith( + '/root/dist/apps/nodeapp/stats.json', + JSON.stringify( + { + stats: 'stats' + }, + null, + 2 + ) + ); + }); + }); + }); + + describe('options normalization', () => { + it('should add the root', () => { + const result = (builder).normalizeOptions(testOptions); + expect(result.root).toEqual('/root'); + }); + + it('should resolve main from root', () => { + const result = (builder).normalizeOptions(testOptions); + expect(result.main).toEqual('/root/apps/nodeapp/src/main.ts'); + }); + + it('should resolve the output path', () => { + const result = (builder).normalizeOptions(testOptions); + expect(result.outputPath).toEqual('/root/dist/apps/nodeapp'); + }); + + it('should resolve the tsConfig path', () => { + const result = (builder).normalizeOptions(testOptions); + expect(result.tsConfig).toEqual('/root/apps/nodeapp/tsconfig.app.json'); + }); + + it('should resolve the file replacement paths', () => { + const result = (builder).normalizeOptions(testOptions); + expect(result.fileReplacements).toEqual([ + { + replace: '/root/apps/environment/environment.ts', + with: '/root/apps/environment/environment.prod.ts' + }, + { + replace: '/root/module1.ts', + with: '/root/module2.ts' + } + ]); + }); + }); +}); diff --git a/packages/builders/src/node/build/node-build.builder.ts b/packages/builders/src/node/build/node-build.builder.ts new file mode 100644 index 0000000000000..61282122f594f --- /dev/null +++ b/packages/builders/src/node/build/node-build.builder.ts @@ -0,0 +1,100 @@ +import { + Builder, + BuildEvent, + BuilderConfiguration, + BuilderContext +} from '@angular-devkit/architect'; +import { virtualFs, Path } from '@angular-devkit/core'; +import { WebpackBuilder } from '@angular-devkit/build-webpack'; + +import { Observable } from 'rxjs'; +import { Stats, writeFileSync } from 'fs'; +import { getWebpackConfig, OUT_FILENAME } from './webpack/config'; +import { resolve } from 'path'; +import { map } from 'rxjs/operators'; + +export interface BuildNodeBuilderOptions { + main: string; + outputPath: string; + tsConfig: string; + watch?: boolean; + optimization?: boolean; + externalDependencies: 'all' | 'none' | string[]; + showCircularDependencies?: boolean; + maxWorkers?: number; + + fileReplacements: FileReplacement[]; + + progress?: boolean; + statsJson?: boolean; + extractLicenses?: boolean; + + root?: Path; +} + +export interface FileReplacement { + replace: string; + with: string; +} + +export interface NodeBuildEvent extends BuildEvent { + outfile: string; +} + +export default class BuildNodeBuilder + implements Builder { + webpackBuilder: WebpackBuilder; + + private get root() { + return this.context.workspace.root; + } + + constructor(private context: BuilderContext) { + this.webpackBuilder = new WebpackBuilder(this.context); + } + + run( + builderConfig: BuilderConfiguration + ): Observable { + const options = this.normalizeOptions(builderConfig.options); + + let config = getWebpackConfig(options); + return this.webpackBuilder + .runWebpack(config, stats => { + if (options.statsJson) { + writeFileSync( + resolve(this.root, options.outputPath, 'stats.json'), + JSON.stringify(stats.toJson(), null, 2) + ); + } + + this.context.logger.info(stats.toString()); + }) + .pipe( + map(buildEvent => ({ + ...buildEvent, + outfile: resolve(this.root, options.outputPath, OUT_FILENAME) + })) + ); + } + + private normalizeOptions(options: BuildNodeBuilderOptions) { + return { + ...options, + root: this.root, + main: resolve(this.root, options.main), + outputPath: resolve(this.root, options.outputPath), + tsConfig: resolve(this.root, options.tsConfig), + fileReplacements: this.normalizeFileReplacements(options.fileReplacements) + }; + } + + private normalizeFileReplacements( + fileReplacements: FileReplacement[] + ): FileReplacement[] { + return fileReplacements.map(fileReplacement => ({ + replace: resolve(this.root, fileReplacement.replace), + with: resolve(this.root, fileReplacement.with) + })); + } +} diff --git a/packages/builders/src/node/build/schema.json b/packages/builders/src/node/build/schema.json new file mode 100644 index 0000000000000..e428bb188b7fa --- /dev/null +++ b/packages/builders/src/node/build/schema.json @@ -0,0 +1,88 @@ +{ + "title": "Node Application Build Target", + "description": "Node application build target options for Build Facade", + "type": "object", + "properties": { + "main": { + "type": "string", + "description": "The name of the main entry-point file." + }, + "tsConfig": { + "type": "string", + "description": "The name of the Typescript configuration file." + }, + "watch": { + "type": "boolean", + "description": "Run build when files change.", + "default": false + }, + "progress": { + "type": "boolean", + "description": "Log progress to the console while building.", + "default": false + }, + "externalDependencies": { + "oneOf": [ + { + "type": "string", + "enum": ["none", "all"] + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ], + "description": + "Dependencies to keep external to the bundle. (\"all\" (default), \"none\", or an array of module names)", + "default": "all" + }, + "statsJson": { + "type": "boolean", + "description": + "Generates a 'stats.json' file which can be analyzed using tools such as: #webpack-bundle-analyzer' or https: //webpack.github.io/analyse.", + "default": false + }, + "extractLicenses": { + "type": "boolean", + "description": + "Extract all licenses in a separate file, in the case of production builds only.", + "default": false + }, + "optimization": { + "type": "boolean", + "description": "Defines the optimization level of the build.", + "default": false + }, + "showCircularDependencies": { + "type": "boolean", + "description": "Show circular dependency warnings on builds.", + "default": true + }, + "maxWorkers": { + "type": "number", + "description": + "Number of workers to use for type checking. (defaults to # of CPUS - 2)" + }, + "fileReplacements": { + "description": "Replace files with other files in the build.", + "type": "array", + "items": { + "type": "object", + "properties": { + "replace": { + "type": "string" + }, + "with": { + "type": "string" + } + }, + "additionalProperties": false, + "required": ["replace", "with"] + }, + "default": [] + } + }, + "required": ["tsConfig", "main"] +} diff --git a/packages/builders/src/node/build/webpack/config.spec.ts b/packages/builders/src/node/build/webpack/config.spec.ts new file mode 100644 index 0000000000000..5a5874bd490e3 --- /dev/null +++ b/packages/builders/src/node/build/webpack/config.spec.ts @@ -0,0 +1,310 @@ +import { getWebpackConfig } from './config'; +import { BuildNodeBuilderOptions } from '../node-build.builder'; +import { normalize } from '@angular-devkit/core'; + +import * as ts from 'typescript'; +import { LicenseWebpackPlugin } from 'license-webpack-plugin'; +import CircularDependencyPlugin = require('circular-dependency-plugin'); +import ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); +import { ProgressPlugin } from 'webpack'; + +describe('getWebpackConfig', () => { + let input: BuildNodeBuilderOptions; + beforeEach(() => { + input = { + main: 'main.ts', + outputPath: 'dist', + tsConfig: 'tsconfig.json', + externalDependencies: 'all', + fileReplacements: [], + root: normalize('/root') + }; + }); + + describe('unconditional options', () => { + it('should have output options', () => { + const result = getWebpackConfig(input); + + expect(result.output.filename).toEqual('main.js'); + expect(result.output.libraryTarget).toEqual('commonjs'); + }); + + it('should have a rule for typescript', () => { + const result = getWebpackConfig(input); + + const typescriptRule = result.module.rules.find(rule => + (rule.test as RegExp).test('app/main.ts') + ); + expect(typescriptRule).toBeTruthy(); + + expect(typescriptRule.loader).toEqual('ts-loader'); + }); + + it('should split typescript type checking into a separate workers', () => { + const result = getWebpackConfig(input); + + const typeCheckerPlugin = result.plugins.find( + plugin => plugin instanceof ForkTsCheckerWebpackPlugin + ) as ForkTsCheckerWebpackPlugin; + expect(typeCheckerPlugin).toBeTruthy(); + }); + + it('should target node', () => { + const result = getWebpackConfig(input); + + expect(result.target).toEqual('node'); + }); + + it('should disable performance hints', () => { + const result = getWebpackConfig(input); + + expect(result.performance).toEqual({ + hints: false + }); + }); + + it('should resolve typescript and javascript', () => { + const result = getWebpackConfig(input); + + expect(result.resolve.extensions).toEqual(['.ts', '.js']); + }); + + it('should not polyfill node apis', () => { + const result = getWebpackConfig(input); + + expect(result.node).toEqual(false); + }); + }); + + describe('the main option', () => { + it('should set the correct entry options', () => { + const result = getWebpackConfig(input); + + expect(result.entry).toEqual(['main.ts']); + }); + }); + + describe('the output option', () => { + it('should set the correct output options', () => { + const result = getWebpackConfig(input); + + expect(result.output.path).toEqual('dist'); + }); + }); + + describe('the tsConfig option', () => { + it('should set the correct typescript rule', () => { + const result = getWebpackConfig(input); + + expect( + result.module.rules.find(rule => rule.loader === 'ts-loader').options + ).toEqual({ + configFile: 'tsconfig.json', + transpileOnly: true, + experimentalWatchApi: true + }); + }); + + it('should set the correct options for the type checker plugin', () => { + const result = getWebpackConfig(input); + + const typeCheckerPlugin = result.plugins.find( + plugin => plugin instanceof ForkTsCheckerWebpackPlugin + ) as ForkTsCheckerWebpackPlugin; + expect(typeCheckerPlugin.options.tsconfig).toBe('tsconfig.json'); + }); + + it('should set aliases for compilerOptionPaths', () => { + spyOn(ts, 'parseJsonConfigFileContent').and.returnValue({ + options: { + paths: { + '@npmScope/libraryName': ['libs/libraryName/src/index.ts'] + } + } + }); + + const result = getWebpackConfig(input); + expect(result.resolve.alias).toEqual({ + '@npmScope/libraryName': '/root/libs/libraryName/src/index.ts' + }); + }); + }); + + describe('the file replacements option', () => { + it('should set aliases', () => { + spyOn(ts, 'parseJsonConfigFileContent').and.returnValue({ + options: {} + }); + + const result = getWebpackConfig({ + ...input, + fileReplacements: [ + { + replace: 'environments/environment.ts', + with: 'environments/environment.prod.ts' + } + ] + }); + + expect(result.resolve.alias).toEqual({ + 'environments/environment.ts': 'environments/environment.prod.ts' + }); + }); + }); + + describe('the externalDependencies option', () => { + it('should change all node_modules to commonjs imports', () => { + const result = getWebpackConfig(input); + const callback = jest.fn(); + result.externals[0](null, '@angular/core', callback); + expect(callback).toHaveBeenCalledWith(null, 'commonjs @angular/core'); + }); + + it('should change given module names to commonjs imports but not others', () => { + const result = getWebpackConfig({ + ...input, + externalDependencies: ['module1'] + }); + const callback = jest.fn(); + result.externals[0](null, 'module1', callback); + expect(callback).toHaveBeenCalledWith(null, 'commonjs module1'); + result.externals[0](null, '@angular/core', callback); + expect(callback).toHaveBeenCalledWith(); + }); + + it('should not change any modules to commonjs imports', () => { + const result = getWebpackConfig({ + ...input, + externalDependencies: 'none' + }); + + expect(result.externals).not.toBeDefined(); + }); + }); + + describe('the watch option', () => { + it('should enable file watching', () => { + const result = getWebpackConfig({ + ...input, + watch: true + }); + + expect(result.watch).toEqual(true); + }); + }); + + describe('the optimization option', () => { + describe('by default', () => { + it('should set the mode to development', () => { + const result = getWebpackConfig(input); + + expect(result.mode).toEqual('development'); + }); + }); + + describe('when true', () => { + it('should set the mode to production', () => { + const result = getWebpackConfig({ + ...input, + optimization: true + }); + + expect(result.mode).toEqual('production'); + }); + + it('should not minify', () => { + const result = getWebpackConfig({ + ...input, + optimization: true + }); + + expect(result.optimization.minimize).toEqual(false); + }); + + it('should not concatenate modules', () => { + const result = getWebpackConfig({ + ...input, + optimization: true + }); + + expect(result.optimization.concatenateModules).toEqual(false); + }); + }); + }); + + describe('the max workers option', () => { + it('should set the maximum workers for the type checker', () => { + const result = getWebpackConfig({ + ...input, + maxWorkers: 1 + }); + + const typeCheckerPlugin = result.plugins.find( + plugin => plugin instanceof ForkTsCheckerWebpackPlugin + ) as ForkTsCheckerWebpackPlugin; + expect(typeCheckerPlugin.options.workers).toEqual(1); + }); + }); + + describe('the circular dependencies option', () => { + it('should show warnings for circular dependencies', () => { + const result = getWebpackConfig({ + ...input, + showCircularDependencies: true + }); + + expect( + result.plugins.find( + plugin => plugin instanceof CircularDependencyPlugin + ) + ).toBeTruthy(); + }); + + it('should exclude node modules', () => { + const result = getWebpackConfig({ + ...input, + showCircularDependencies: true + }); + + const circularDependencyPlugin: CircularDependencyPlugin = result.plugins.find( + plugin => plugin instanceof CircularDependencyPlugin + ); + expect(circularDependencyPlugin.options.exclude).toEqual( + /[\\\/]node_modules[\\\/]/ + ); + }); + }); + + describe('the extract licenses option', () => { + it('should extract licenses to a separate file', () => { + const result = getWebpackConfig({ + ...input, + extractLicenses: true + }); + + const licensePlugin = result.plugins.find( + plugin => plugin instanceof LicenseWebpackPlugin + ) as LicenseWebpackPlugin; + const options = (licensePlugin).options; + + expect(licensePlugin).toBeTruthy(); + expect(options.pattern).toEqual(/.*/); + expect(options.suppressErrors).toEqual(true); + expect(options.perChunkOutput).toEqual(false); + expect(options.outputFilename).toEqual('3rdpartylicenses.txt'); + }); + }); + + describe('the progress option', () => { + it('should show build progress', () => { + const result = getWebpackConfig({ + ...input, + progress: true + }); + + expect( + result.plugins.find(plugin => plugin instanceof ProgressPlugin) + ).toBeTruthy(); + }); + }); +}); diff --git a/packages/builders/src/node/build/webpack/config.ts b/packages/builders/src/node/build/webpack/config.ts new file mode 100644 index 0000000000000..4c4120b87ef7c --- /dev/null +++ b/packages/builders/src/node/build/webpack/config.ts @@ -0,0 +1,137 @@ +import * as webpack from 'webpack'; +import { Configuration, ProgressPlugin } from 'webpack'; + +import * as ts from 'typescript'; +import { dirname, resolve } from 'path'; + +import { LicenseWebpackPlugin } from 'license-webpack-plugin'; +import CircularDependencyPlugin = require('circular-dependency-plugin'); +import ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); + +import { BuildNodeBuilderOptions } from '../node-build.builder'; +import * as nodeExternals from 'webpack-node-externals'; + +export const OUT_FILENAME = 'main.js'; + +export function getWebpackConfig( + options: BuildNodeBuilderOptions +): Configuration { + const webpackConfig: Configuration = { + entry: [options.main], + mode: options.optimization ? 'production' : 'development', + output: { + path: options.outputPath, + filename: OUT_FILENAME, + libraryTarget: 'commonjs' + }, + module: { + rules: [ + { + test: /\.ts$/, + loader: `ts-loader`, + options: { + configFile: options.tsConfig, + transpileOnly: true, + // https://github.com/TypeStrong/ts-loader/pull/685 + experimentalWatchApi: true + } + } + ] + }, + resolve: { + extensions: ['.ts', '.js'], + alias: getAliases(options) + }, + target: 'node', + node: false, + performance: { + hints: false + }, + plugins: [ + new ForkTsCheckerWebpackPlugin({ + tsconfig: options.tsConfig, + workers: options.maxWorkers || ForkTsCheckerWebpackPlugin.TWO_CPUS_FREE + }) + ], + watch: options.watch + }; + + const extraPlugins: webpack.Plugin[] = []; + + if (options.progress) { + extraPlugins.push(new ProgressPlugin()); + } + + if (options.optimization) { + webpackConfig.optimization = { + minimize: false, + concatenateModules: false + }; + } + + if (options.extractLicenses) { + extraPlugins.push( + new LicenseWebpackPlugin({ + pattern: /.*/, + suppressErrors: true, + perChunkOutput: false, + outputFilename: `3rdpartylicenses.txt` + }) + ); + } + + if (options.externalDependencies === 'all') { + webpackConfig.externals = [nodeExternals()]; + } else if (Array.isArray(options.externalDependencies)) { + webpackConfig.externals = [ + function(context, request, callback: Function) { + if (options.externalDependencies.includes(request)) { + // not bundled + return callback(null, 'commonjs ' + request); + } + // bundled + callback(); + } + ]; + } + + if (options.showCircularDependencies) { + extraPlugins.push( + new CircularDependencyPlugin({ + exclude: /[\\\/]node_modules[\\\/]/ + }) + ); + } + + webpackConfig.plugins = [...webpackConfig.plugins, ...extraPlugins]; + + return webpackConfig; +} + +function getAliases( + options: BuildNodeBuilderOptions +): { [key: string]: string } { + const readResult = ts.readConfigFile(options.tsConfig, ts.sys.readFile); + const tsConfig = ts.parseJsonConfigFileContent( + readResult.config, + ts.sys, + dirname(options.tsConfig) + ); + const compilerOptions = tsConfig.options; + const replacements = [ + ...options.fileReplacements, + ...(compilerOptions.paths + ? Object.entries(compilerOptions.paths).map(([importPath, values]) => ({ + replace: importPath, + with: resolve(options.root, values[0]) + })) + : []) + ]; + return replacements.reduce( + (aliases, replacement) => ({ + ...aliases, + [replacement.replace]: replacement.with + }), + {} + ); +} diff --git a/packages/builders/src/node/execute/node-execute.builder.spec.ts b/packages/builders/src/node/execute/node-execute.builder.spec.ts new file mode 100644 index 0000000000000..55f0b434d4ded --- /dev/null +++ b/packages/builders/src/node/execute/node-execute.builder.spec.ts @@ -0,0 +1,175 @@ +import { + NodeExecuteBuilder, + NodeExecuteBuilderOptions +} from './node-execute.builder'; +import { TestLogger } from '@angular-devkit/architect/testing'; +import { normalize } from '@angular-devkit/core'; +import { of } from 'rxjs'; +import { cold } from 'jasmine-marbles'; +jest.mock('child_process'); +let { fork } = require('child_process'); +jest.mock('tree-kill'); +let treeKill = require('tree-kill'); + +class MockArchitect { + getBuilderConfiguration() { + return { + config: 'testConfig' + }; + } + run() { + return cold('--a--b--a', { + a: { + success: true, + outfile: 'outfile.js' + }, + b: { + success: false, + outfile: 'outfile.js' + } + }); + } + getBuilderDescription() { + return of({ + description: 'testDescription' + }); + } + validateBuilderOptions() { + return of({ + options: {} + }); + } +} + +describe('NodeExecuteBuilder', () => { + let builder: NodeExecuteBuilder; + let architect: MockArchitect; + let logger: TestLogger; + let testOptions: NodeExecuteBuilderOptions; + + beforeEach(() => { + fork.mockReturnValue({ + pid: 123 + }); + treeKill.mockImplementation((pid, signal, callback) => { + callback(); + }); + logger = new TestLogger('test'); + architect = new MockArchitect(); + builder = new NodeExecuteBuilder({ + workspace: { + root: '/root' + }, + logger, + host: {}, + architect: architect + }); + testOptions = { + inspect: true, + args: [], + buildTarget: 'nodeapp:build' + }; + }); + + it('should build the application and start the built file', () => { + const getBuilderConfiguration = spyOn( + architect, + 'getBuilderConfiguration' + ).and.callThrough(); + expect( + builder.run({ + root: normalize('/root'), + projectType: 'application', + builder: '@nrwl/builders:node-execute', + options: testOptions + }) + ).toBeObservable( + cold('--a--b--a', { + a: { + success: true, + outfile: 'outfile.js' + }, + b: { + success: false, + outfile: 'outfile.js' + } + }) + ); + expect(getBuilderConfiguration).toHaveBeenCalledWith({ + project: 'nodeapp', + target: 'build', + overrides: { + watch: true + } + }); + expect(fork).toHaveBeenCalledWith('outfile.js', [], { + execArgv: ['--inspect'] + }); + expect(treeKill).toHaveBeenCalledTimes(1); + expect(fork).toHaveBeenCalledTimes(2); + }); + + it('should build the application and start the built file with options', () => { + expect( + builder.run({ + root: normalize('/root'), + projectType: 'application', + builder: '@nrwl/builders:node-execute', + options: { + ...testOptions, + inspect: false, + args: ['arg1', 'arg2'] + } + }) + ).toBeObservable( + cold('--a--b--a', { + a: { + success: true, + outfile: 'outfile.js' + }, + b: { + success: false, + outfile: 'outfile.js' + } + }) + ); + expect(fork).toHaveBeenCalledWith('outfile.js', ['arg1', 'arg2'], { + execArgv: [] + }); + }); + + it('should warn users who try to use it in production', () => { + spyOn(architect, 'validateBuilderOptions').and.returnValue( + of({ + options: { + optimization: true + } + }) + ); + spyOn(logger, 'warn'); + expect( + builder.run({ + root: normalize('/root'), + projectType: 'application', + builder: '@nrwl/builders:node-execute', + options: { + ...testOptions, + inspect: false, + args: ['arg1', 'arg2'] + } + }) + ).toBeObservable( + cold('--a--b--a', { + a: { + success: true, + outfile: 'outfile.js' + }, + b: { + success: false, + outfile: 'outfile.js' + } + }) + ); + expect(logger.warn).toHaveBeenCalled(); + }); +}); diff --git a/packages/builders/src/node/execute/node-execute.builder.ts b/packages/builders/src/node/execute/node-execute.builder.ts new file mode 100644 index 0000000000000..511c1ce1fbc30 --- /dev/null +++ b/packages/builders/src/node/execute/node-execute.builder.ts @@ -0,0 +1,133 @@ +import { + BuildEvent, + Builder, + BuilderConfiguration, + BuilderContext +} from '@angular-devkit/architect'; +import { ChildProcess, fork } from 'child_process'; +import * as treeKill from 'tree-kill'; + +import { Observable, bindCallback, of } from 'rxjs'; +import { concatMap, tap, mapTo } from 'rxjs/operators'; + +import { + BuildNodeBuilderOptions, + NodeBuildEvent +} from '../build/node-build.builder'; +import { stripIndents } from '@angular-devkit/core/src/utils/literals'; + +export interface NodeExecuteBuilderOptions { + inspect: boolean; + args: string[]; + buildTarget: string; +} + +export class NodeExecuteBuilder implements Builder { + private subProcess: ChildProcess; + + constructor(private context: BuilderContext) {} + + run( + target: BuilderConfiguration + ): Observable { + const options = target.options; + + return this.startBuild(options).pipe( + concatMap((event: NodeBuildEvent) => { + if (event.success) { + return this.restartProcess(event.outfile, options).pipe(mapTo(event)); + } else { + this.context.logger.error( + 'There was an error with the build. See above.' + ); + this.context.logger.info(`${event.outfile} was not restarted.`); + return of(event); + } + }) + ); + } + + private runProcess(file: string, options: NodeExecuteBuilderOptions) { + if (this.subProcess) { + throw new Error('Already running'); + } + this.subProcess = fork(file, options.args, { + execArgv: options.inspect ? ['--inspect'] : [] + }); + } + + private restartProcess(file: string, options: NodeExecuteBuilderOptions) { + return this.killProcess().pipe( + tap(() => { + this.runProcess(file, options); + }) + ); + } + + private killProcess(): Observable { + if (!this.subProcess) { + return of(undefined); + } + + const observableTreeKill = bindCallback(treeKill); + return observableTreeKill(this.subProcess.pid, 'SIGTERM').pipe( + tap(err => { + if (err) { + throw err; + } else { + this.subProcess = null; + } + }) + ); + } + + private startBuild( + options: NodeExecuteBuilderOptions + ): Observable { + const builderConfig = this._getBuildBuilderConfig(options); + + return this.context.architect.getBuilderDescription(builderConfig).pipe( + concatMap(buildDescription => + this.context.architect.validateBuilderOptions( + builderConfig, + buildDescription + ) + ), + tap(builderConfig => { + if (builderConfig.options.optimization) { + this.context.logger.warn(stripIndents` + ************************************************ + This is a simple process manager for use in + testing or debugging Node applications locally. + DO NOT USE IT FOR PRODUCTION! + You should look into proper means of deploying + your node application to production. + ************************************************`); + } + }), + concatMap( + builderConfig => + this.context.architect.run(builderConfig, this.context) as Observable< + NodeBuildEvent + > + ) + ); + } + + private _getBuildBuilderConfig(options: NodeExecuteBuilderOptions) { + const [project, target, configuration] = options.buildTarget.split(':'); + + return this.context.architect.getBuilderConfiguration< + BuildNodeBuilderOptions + >({ + project, + target, + configuration, + overrides: { + watch: true + } + }); + } +} + +export default NodeExecuteBuilder; diff --git a/packages/builders/src/node/execute/schema.json b/packages/builders/src/node/execute/schema.json new file mode 100644 index 0000000000000..d2d40b85b2796 --- /dev/null +++ b/packages/builders/src/node/execute/schema.json @@ -0,0 +1,26 @@ +{ + "title": "Schema for Executing NodeJS apps", + "description": "NodeJS execution options", + "type": "object", + "properties": { + "buildTarget": { + "type": "string", + "description": "The target to run to build you the app" + }, + "inspect": { + "type": "boolean", + "description": "Ensures the app is starting with debugging", + "default": true + }, + "args": { + "type": "array", + "description": "Extra args when starting the app", + "default": [], + "items": { + "type": "string" + } + } + }, + "additionalProperties": false, + "required": ["buildTarget"] +} diff --git a/yarn.lock b/yarn.lock index bcf41d1cc5fd0..4c49c7996effa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -278,6 +278,10 @@ dependencies: "@types/jasmine" "*" +"@types/jest@^23.3.2": + version "23.3.2" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.2.tgz#07b90f6adf75d42c34230c026a2529e56c249dbb" + "@types/node@*": version "10.5.3" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.5.3.tgz#5bcfaf088ad17894232012877669634c06b20cc5" @@ -290,10 +294,38 @@ version "1.13.2" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.13.2.tgz#ffe96278e712a8d4e467e367a338b05e22872646" +"@types/tapable@*": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.4.tgz#b4ffc7dc97b498c969b360a41eee247f82616370" + +"@types/uglify-js@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.0.3.tgz#801a5ca1dc642861f47c46d14b700ed2d610840b" + dependencies: + source-map "^0.6.1" + +"@types/webpack@^4.4.11": + version "4.4.11" + resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.4.11.tgz#0ca832870d55c4e92498c01d22d00d02b0f62ae9" + dependencies: + "@types/node" "*" + "@types/tapable" "*" + "@types/uglify-js" "*" + source-map "^0.6.0" + "@types/yargs@^11.0.0": version "11.1.1" resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-11.1.1.tgz#2e724257167fd6b615dbe4e54301e65fe597433f" +"@webassemblyjs/ast@1.4.3": + version "1.4.3" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.4.3.tgz#3b3f6fced944d8660273347533e6d4d315b5934a" + dependencies: + "@webassemblyjs/helper-wasm-bytecode" "1.4.3" + "@webassemblyjs/wast-parser" "1.4.3" + debug "^3.1.0" + webassemblyjs "1.4.3" + "@webassemblyjs/ast@1.7.6": version "1.7.6" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.7.6.tgz#3ef8c45b3e5e943a153a05281317474fef63e21e" @@ -303,6 +335,10 @@ "@webassemblyjs/wast-parser" "1.7.6" mamacro "^0.0.3" +"@webassemblyjs/floating-point-hex-parser@1.4.3": + version "1.4.3" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.4.3.tgz#f5aee4c376a717c74264d7bacada981e7e44faad" + "@webassemblyjs/floating-point-hex-parser@1.7.6": version "1.7.6" resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.7.6.tgz#7cb37d51a05c3fe09b464ae7e711d1ab3837801f" @@ -311,16 +347,32 @@ version "1.7.6" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.7.6.tgz#99b7e30e66f550a2638299a109dda84a622070ef" +"@webassemblyjs/helper-buffer@1.4.3": + version "1.4.3" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.4.3.tgz#0434b55958519bf503697d3824857b1dea80b729" + dependencies: + debug "^3.1.0" + "@webassemblyjs/helper-buffer@1.7.6": version "1.7.6" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.7.6.tgz#ba0648be12bbe560c25c997e175c2018df39ca3e" +"@webassemblyjs/helper-code-frame@1.4.3": + version "1.4.3" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.4.3.tgz#f1349ca3e01a8e29ee2098c770773ef97af43641" + dependencies: + "@webassemblyjs/wast-printer" "1.4.3" + "@webassemblyjs/helper-code-frame@1.7.6": version "1.7.6" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.7.6.tgz#5a94d21b0057b69a7403fca0c253c3aaca95b1a5" dependencies: "@webassemblyjs/wast-printer" "1.7.6" +"@webassemblyjs/helper-fsm@1.4.3": + version "1.4.3" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.4.3.tgz#65a921db48fb43e868f17b27497870bdcae22b79" + "@webassemblyjs/helper-fsm@1.7.6": version "1.7.6" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.7.6.tgz#ae1741c6f6121213c7a0b587fb964fac492d3e49" @@ -331,10 +383,24 @@ dependencies: mamacro "^0.0.3" +"@webassemblyjs/helper-wasm-bytecode@1.4.3": + version "1.4.3" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.4.3.tgz#0e5b4b5418e33f8a26e940b7809862828c3721a5" + "@webassemblyjs/helper-wasm-bytecode@1.7.6": version "1.7.6" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.7.6.tgz#98e515eaee611aa6834eb5f6a7f8f5b29fefb6f1" +"@webassemblyjs/helper-wasm-section@1.4.3": + version "1.4.3" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.4.3.tgz#9ceedd53a3f152c3412e072887ade668d0b1acbf" + dependencies: + "@webassemblyjs/ast" "1.4.3" + "@webassemblyjs/helper-buffer" "1.4.3" + "@webassemblyjs/helper-wasm-bytecode" "1.4.3" + "@webassemblyjs/wasm-gen" "1.4.3" + debug "^3.1.0" + "@webassemblyjs/helper-wasm-section@1.7.6": version "1.7.6" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.7.6.tgz#783835867bdd686df7a95377ab64f51a275e8333" @@ -350,6 +416,12 @@ dependencies: "@xtuc/ieee754" "^1.2.0" +"@webassemblyjs/leb128@1.4.3": + version "1.4.3" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.4.3.tgz#5a5e5949dbb5adfe3ae95664d0439927ac557fb8" + dependencies: + leb "^0.3.0" + "@webassemblyjs/leb128@1.7.6": version "1.7.6" resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.7.6.tgz#197f75376a29f6ed6ace15898a310d871d92f03b" @@ -360,6 +432,26 @@ version "1.7.6" resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.7.6.tgz#eb62c66f906af2be70de0302e29055d25188797d" +"@webassemblyjs/validation@1.4.3": + version "1.4.3" + resolved "https://registry.yarnpkg.com/@webassemblyjs/validation/-/validation-1.4.3.tgz#9e66c9b3079d7bbcf2070c1bf52a54af2a09aac9" + dependencies: + "@webassemblyjs/ast" "1.4.3" + +"@webassemblyjs/wasm-edit@1.4.3": + version "1.4.3" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.4.3.tgz#87febd565e0ffb5ae25f6495bb3958d17aa0a779" + dependencies: + "@webassemblyjs/ast" "1.4.3" + "@webassemblyjs/helper-buffer" "1.4.3" + "@webassemblyjs/helper-wasm-bytecode" "1.4.3" + "@webassemblyjs/helper-wasm-section" "1.4.3" + "@webassemblyjs/wasm-gen" "1.4.3" + "@webassemblyjs/wasm-opt" "1.4.3" + "@webassemblyjs/wasm-parser" "1.4.3" + "@webassemblyjs/wast-printer" "1.4.3" + debug "^3.1.0" + "@webassemblyjs/wasm-edit@1.7.6": version "1.7.6" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.7.6.tgz#fa41929160cd7d676d4c28ecef420eed5b3733c5" @@ -373,6 +465,14 @@ "@webassemblyjs/wasm-parser" "1.7.6" "@webassemblyjs/wast-printer" "1.7.6" +"@webassemblyjs/wasm-gen@1.4.3": + version "1.4.3" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.4.3.tgz#8553164d0154a6be8f74d653d7ab355f73240aa4" + dependencies: + "@webassemblyjs/ast" "1.4.3" + "@webassemblyjs/helper-wasm-bytecode" "1.4.3" + "@webassemblyjs/leb128" "1.4.3" + "@webassemblyjs/wasm-gen@1.7.6": version "1.7.6" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.7.6.tgz#695ac38861ab3d72bf763c8c75e5f087ffabc322" @@ -383,6 +483,16 @@ "@webassemblyjs/leb128" "1.7.6" "@webassemblyjs/utf8" "1.7.6" +"@webassemblyjs/wasm-opt@1.4.3": + version "1.4.3" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.4.3.tgz#26c7a23bfb136aa405b1d3410e63408ec60894b8" + dependencies: + "@webassemblyjs/ast" "1.4.3" + "@webassemblyjs/helper-buffer" "1.4.3" + "@webassemblyjs/wasm-gen" "1.4.3" + "@webassemblyjs/wasm-parser" "1.4.3" + debug "^3.1.0" + "@webassemblyjs/wasm-opt@1.7.6": version "1.7.6" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.7.6.tgz#fbafa78e27e1a75ab759a4b658ff3d50b4636c21" @@ -392,6 +502,16 @@ "@webassemblyjs/wasm-gen" "1.7.6" "@webassemblyjs/wasm-parser" "1.7.6" +"@webassemblyjs/wasm-parser@1.4.3": + version "1.4.3" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.4.3.tgz#7ddd3e408f8542647ed612019cfb780830993698" + dependencies: + "@webassemblyjs/ast" "1.4.3" + "@webassemblyjs/helper-wasm-bytecode" "1.4.3" + "@webassemblyjs/leb128" "1.4.3" + "@webassemblyjs/wasm-parser" "1.4.3" + webassemblyjs "1.4.3" + "@webassemblyjs/wasm-parser@1.7.6": version "1.7.6" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.7.6.tgz#84eafeeff405ad6f4c4b5777d6a28ae54eed51fe" @@ -403,6 +523,17 @@ "@webassemblyjs/leb128" "1.7.6" "@webassemblyjs/utf8" "1.7.6" +"@webassemblyjs/wast-parser@1.4.3": + version "1.4.3" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.4.3.tgz#3250402e2c5ed53dbe2233c9de1fe1f9f0d51745" + dependencies: + "@webassemblyjs/ast" "1.4.3" + "@webassemblyjs/floating-point-hex-parser" "1.4.3" + "@webassemblyjs/helper-code-frame" "1.4.3" + "@webassemblyjs/helper-fsm" "1.4.3" + long "^3.2.0" + webassemblyjs "1.4.3" + "@webassemblyjs/wast-parser@1.7.6": version "1.7.6" resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.7.6.tgz#ca4d20b1516e017c91981773bd7e819d6bd9c6a7" @@ -415,6 +546,14 @@ "@xtuc/long" "4.2.1" mamacro "^0.0.3" +"@webassemblyjs/wast-printer@1.4.3": + version "1.4.3" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.4.3.tgz#3d59aa8d0252d6814a3ef4e6d2a34c9ded3904e0" + dependencies: + "@webassemblyjs/ast" "1.4.3" + "@webassemblyjs/wast-parser" "1.4.3" + long "^3.2.0" + "@webassemblyjs/wast-printer@1.7.6": version "1.7.6" resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.7.6.tgz#a6002c526ac5fa230fe2c6d2f1bdbf4aead43a5e" @@ -1856,7 +1995,7 @@ chokidar@^1.4.2: optionalDependencies: fsevents "^1.0.0" -chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.0.3: +chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.0.3, chokidar@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.4.tgz#356ff4e2b0e8e43e322d18a372460bbcf3accd26" dependencies: @@ -1879,6 +2018,10 @@ chownr@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181" +chrome-trace-event@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-0.1.3.tgz#d395af2d31c87b90a716c831fe326f69768ec084" + chrome-trace-event@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.0.tgz#45a91bd2c20c9411f0963b5aaeb9a1b95e09cc48" @@ -3126,7 +3269,7 @@ engine.io@~3.1.0: optionalDependencies: uws "~9.14.0" -enhanced-resolve@^4.1.0: +enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f" dependencies: @@ -3235,6 +3378,13 @@ escodegen@1.x.x, escodegen@^1.9.0: optionalDependencies: source-map "~0.6.1" +eslint-scope@^3.7.1: + version "3.7.3" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.3.tgz#bb507200d3d17f60247636160b4826284b108535" + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + eslint-scope@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.0.tgz#50bf3071e9338bcdc43331794a0cb533f0136172" @@ -3731,6 +3881,21 @@ forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" +fork-ts-checker-webpack-plugin@^0.4.9: + version "0.4.9" + resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-0.4.9.tgz#78607899d4411fdc6faeca5b4db7654c9d8d28a2" + dependencies: + babel-code-frame "^6.22.0" + chalk "^2.4.1" + chokidar "^2.0.4" + lodash.endswith "^4.2.1" + lodash.isfunction "^3.0.8" + lodash.isstring "^4.0.1" + lodash.startswith "^4.2.1" + minimatch "^3.0.4" + resolve "^1.5.0" + tapable "^1.0.0" + form-data@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.0.0.tgz#6f0aebadcc5da16c13e1ecc11137d85f9b883b25" @@ -5745,6 +5910,10 @@ lcid@^1.0.0: dependencies: invert-kv "^1.0.0" +leb@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/leb/-/leb-0.3.0.tgz#32bee9fad168328d6aea8522d833f4180eed1da3" + left-pad@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" @@ -5818,6 +5987,12 @@ license-webpack-plugin@^1.3.1: dependencies: ejs "^2.5.7" +license-webpack-plugin@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/license-webpack-plugin/-/license-webpack-plugin-1.4.0.tgz#be504a849ba7d736f1a6da4b133864f30af885fa" + dependencies: + ejs "^2.5.7" + lint-staged@^7.2.2: version "7.2.2" resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-7.2.2.tgz#0983d55d497f19f36d11ff2c8242b2f56cc2dd05" @@ -5962,6 +6137,18 @@ lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" +lodash.endswith@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/lodash.endswith/-/lodash.endswith-4.2.1.tgz#fed59ac1738ed3e236edd7064ec456448b37bc09" + +lodash.isfunction@^3.0.8: + version "3.0.9" + resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + lodash.map@^4.5.1: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" @@ -5974,6 +6161,10 @@ lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" +lodash.startswith@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/lodash.startswith/-/lodash.startswith-4.2.1.tgz#c598c4adce188a27e53145731cdc6c0e7177600c" + lodash.tail@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664" @@ -6060,6 +6251,10 @@ loglevelnext@^1.0.1: es6-symbol "^3.1.1" object.assign "^4.1.0" +long@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b" + longest@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" @@ -9789,6 +9984,16 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" +webassemblyjs@1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/webassemblyjs/-/webassemblyjs-1.4.3.tgz#0591893efb8fbde74498251cbe4b2d83df9239cb" + dependencies: + "@webassemblyjs/ast" "1.4.3" + "@webassemblyjs/validation" "1.4.3" + "@webassemblyjs/wasm-parser" "1.4.3" + "@webassemblyjs/wast-parser" "1.4.3" + long "^3.2.0" + webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" @@ -9870,16 +10075,20 @@ webpack-merge@^4.1.2: dependencies: lodash "^4.17.5" -webpack-sources@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.1.0.tgz#a101ebae59d6507354d71d8013950a3a8b7a5a54" +webpack-node-externals@^1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/webpack-node-externals/-/webpack-node-externals-1.7.2.tgz#6e1ee79ac67c070402ba700ef033a9b8d52ac4e3" + +webpack-sources@^1.0.1, webpack-sources@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.3.0.tgz#2a28dcb9f1f45fe960d8f1493252b5ee6530fa85" dependencies: source-list-map "^2.0.0" source-map "~0.6.1" -webpack-sources@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.3.0.tgz#2a28dcb9f1f45fe960d8f1493252b5ee6530fa85" +webpack-sources@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.1.0.tgz#a101ebae59d6507354d71d8013950a3a8b7a5a54" dependencies: source-list-map "^2.0.0" source-map "~0.6.1" @@ -9890,6 +10099,34 @@ webpack-subresource-integrity@^1.1.0-rc.4: dependencies: webpack-core "^0.6.8" +webpack@4.9.2: + version "4.9.2" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.9.2.tgz#d347097cc87c9242527c2e8ee9cdcb90f05856c3" + dependencies: + "@webassemblyjs/ast" "1.4.3" + "@webassemblyjs/wasm-edit" "1.4.3" + "@webassemblyjs/wasm-parser" "1.4.3" + acorn "^5.0.0" + acorn-dynamic-import "^3.0.0" + ajv "^6.1.0" + ajv-keywords "^3.1.0" + chrome-trace-event "^0.1.1" + enhanced-resolve "^4.0.0" + eslint-scope "^3.7.1" + json-parse-better-errors "^1.0.2" + loader-runner "^2.3.0" + loader-utils "^1.1.0" + memory-fs "~0.4.1" + micromatch "^3.1.8" + mkdirp "~0.5.0" + neo-async "^2.5.0" + node-libs-browser "^2.0.0" + schema-utils "^0.4.4" + tapable "^1.0.0" + uglifyjs-webpack-plugin "^1.2.4" + watchpack "^1.5.0" + webpack-sources "^1.0.1" + webpack@^4.15.1: version "4.19.1" resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.19.1.tgz#096674bc3b573f8756c762754366e5b333d6576f"