feat(builders): introduce node build and execute builders

These builders handle building and executing node applications
This commit is contained in:
Jason Jean 2018-08-24 21:12:32 -04:00 committed by Victor Savkin
parent 039c1510d9
commit 469af6e1a0
16 changed files with 1416 additions and 43 deletions

2
.gitignore vendored
View File

@ -2,7 +2,7 @@ node_modules
.idea
.vscode
dist
build
/build
test
.DS_Store
tmp

View File

@ -1,5 +1,5 @@
tmp
build
/build
node_modules
/package.json
packages/schematics/src/collection/**/files/*.json
packages/schematics/src/collection/**/files/*.json

View File

@ -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"

View File

@ -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"
}
}

View File

@ -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",

View File

@ -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({

View File

@ -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<JestBuilderOptions> {
);
}
return from(runJest(config, [options.jestConfig])).pipe(
return from(runCLI(config, [options.jestConfig])).pipe(
map((results: any) => {
return {
success: results.results.success

View File

@ -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: <any>{},
logger: new TestLogger('test'),
workspace: <any>{
root: '/root'
},
architect: <any>{}
});
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 = (<any>builder).normalizeOptions(testOptions);
expect(result.root).toEqual('/root');
});
it('should resolve main from root', () => {
const result = (<any>builder).normalizeOptions(testOptions);
expect(result.main).toEqual('/root/apps/nodeapp/src/main.ts');
});
it('should resolve the output path', () => {
const result = (<any>builder).normalizeOptions(testOptions);
expect(result.outputPath).toEqual('/root/dist/apps/nodeapp');
});
it('should resolve the tsConfig path', () => {
const result = (<any>builder).normalizeOptions(testOptions);
expect(result.tsConfig).toEqual('/root/apps/nodeapp/tsconfig.app.json');
});
it('should resolve the file replacement paths', () => {
const result = (<any>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'
}
]);
});
});
});

View File

@ -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<BuildNodeBuilderOptions> {
webpackBuilder: WebpackBuilder;
private get root() {
return this.context.workspace.root;
}
constructor(private context: BuilderContext) {
this.webpackBuilder = new WebpackBuilder(this.context);
}
run(
builderConfig: BuilderConfiguration<BuildNodeBuilderOptions>
): Observable<NodeBuildEvent> {
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)
}));
}
}

View File

@ -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"]
}

View File

@ -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 = (<any>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();
});
});
});

View File

@ -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
}),
{}
);
}

View File

@ -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: <any>{
root: '/root'
},
logger,
host: <any>{},
architect: <any>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();
});
});

View File

@ -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<NodeExecuteBuilderOptions> {
private subProcess: ChildProcess;
constructor(private context: BuilderContext) {}
run(
target: BuilderConfiguration<NodeExecuteBuilderOptions>
): Observable<BuildEvent> {
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<void | Error> {
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<NodeBuildEvent> {
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;

View File

@ -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"]
}

253
yarn.lock
View File

@ -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"