run(builderConfig: BuilderConfiguration<BrowserBuilderSchema>): Observable<BuildEvent> { const options = builderConfig.options; const root = this.context.workspace.root; const projectRoot = resolve(root, builderConfig.root); const host = new virtualFs.AliasHost(this.context.host as virtualFs.Host<fs.Stats>); const webpackBuilder = new WebpackBuilder({ ...this.context, host }); return of(null).pipe( concatMap(() => options.deleteOutputPath ? this._deleteOutputDir(root, normalize(options.outputPath), this.context.host) : of(null)), concatMap(() => normalizeFileReplacements(options.fileReplacements, host, root)), tap(fileReplacements => options.fileReplacements = fileReplacements), concatMap(() => normalizeAssetPatterns( options.assets, host, root, projectRoot, builderConfig.sourceRoot)), // Replace the assets in options with the normalized version. tap((assetPatternObjects => options.assets = assetPatternObjects)), concatMap(() => { // Ensure Build Optimizer is only used with AOT. if (options.buildOptimizer && !options.aot) { throw new Error('The `--build-optimizer` option cannot be used without `--aot`.'); } let webpackConfig; try { webpackConfig = this.buildWebpackConfig(root, projectRoot, host, options as NormalizedBrowserBuilderSchema); } catch (e) { return throwError(e); } return webpackBuilder.runWebpack(webpackConfig, getBrowserLoggingCb(options.verbose)); }), concatMap(buildEvent => { if (buildEvent.success && !options.watch && options.serviceWorker) { return new Observable(obs => { augmentAppWithServiceWorker( this.context.host, root, projectRoot, resolve(root, normalize(options.outputPath)), options.baseHref || '/', options.ngswConfigPath, ).then( () => { obs.next({ success: true }); obs.complete(); }, (err: Error) => { obs.error(err); }, ); }); } else { return of(buildEvent); } }), ); }
run(builderConfig: BuilderConfiguration<ExtractI18nBuilderOptions>): Observable<BuildEvent> { const architect = this.context.architect; const options = builderConfig.options; const root = this.context.workspace.root; const projectRoot = resolve(root, builderConfig.root); const [project, targetName, configuration] = options.browserTarget.split(':'); // Override browser build watch setting. const overrides = { watch: false }; const browserTargetSpec = { project, target: targetName, configuration, overrides }; const browserBuilderConfig = architect.getBuilderConfiguration<BrowserBuilderSchema>( browserTargetSpec); const webpackBuilder = new WebpackBuilder(this.context); const loggingCb: LoggingCallback = (stats, config, logger) => { const json = stats.toJson(); if (stats.hasWarnings()) { this.context.logger.warn(statsWarningsToString(json, config.stats)); } if (stats.hasErrors()) { this.context.logger.error(statsErrorsToString(json, config.stats)); } }; return architect.getBuilderDescription(browserBuilderConfig).pipe( concatMap(browserDescription => architect.validateBuilderOptions(browserBuilderConfig, browserDescription)), map(browserBuilderConfig => browserBuilderConfig.options), concatMap((validatedBrowserOptions) => { const browserOptions = validatedBrowserOptions; // We need to determine the outFile name so that AngularCompiler can retrieve it. let outFile = options.outFile || getI18nOutfile(options.i18nFormat); if (options.outputPath) { // AngularCompilerPlugin doesn't support genDir so we have to adjust outFile instead. outFile = path.join(options.outputPath, outFile); } // Extracting i18n uses the browser target webpack config with some specific options. const webpackConfig = this.buildWebpackConfig(root, projectRoot, { // todo: remove this casting when 'CurrentFileReplacement' is changed to 'FileReplacement' ...(browserOptions as NormalizedBrowserBuilderSchema), optimization: false, i18nLocale: options.i18nLocale, i18nFormat: options.i18nFormat, i18nFile: outFile, aot: true, progress: options.progress, assets: [], scripts: [], styles: [], }); return webpackBuilder.runWebpack(webpackConfig, loggingCb); }), ); }
private _gitInit(): Observable<void> { return this._exec('git', ['init']).pipe( concatMap(() => this._exec('git', ['config', 'user.email', '*****@*****.**'])), concatMap(() => this._exec('git', ['config', 'user.name', 'Angular DevKit Tests'])), concatMap(() => this._exec('git', ['add', '--all'])), concatMap(() => this._exec('git', ['commit', '-am', '"Initial commit"'])), map(() => { }), ); }
it('lint works', (done) => { const targetSpec: TargetSpecifier = { project: 'lib', target: 'lint' }; return workspace.loadWorkspaceFromHost(workspaceFile).pipe( concatMap(ws => new Architect(ws).loadArchitect()), concatMap(arch => arch.run(arch.getBuilderConfiguration(targetSpec))), tap((buildEvent) => expect(buildEvent.success).toBe(true)), ).subscribe(undefined, done.fail, done); }, 30000);
buildBrowserConfig(options: CordovaBuildBuilderSchema): Observable<BuilderConfiguration<BrowserBuilderSchema>> { let browserConfig: BuilderConfiguration<BrowserBuilderSchema>; return of(null).pipe(// tslint:disable-line:no-null-keyword concatMap(() => this._getBrowserConfig(options)), tap(config => browserConfig = config), tap(() => this.prepareBrowserConfig(options, browserConfig.options)), concatMap(() => of(browserConfig)) ); }
it('supports options', (done) => { host.writeMultipleFiles({ 'src/styles.css': `h1 { background: url('./spectrum.png')}` }); host.writeMultipleFiles(lazyModuleFiles); host.writeMultipleFiles(lazyModuleImport); const overrides = { outputHashing: 'all', extractCss: true }; // We must do several builds instead of a single one in watch mode, so that the output // path is deleted on each run and only contains the most recent files. // 'all' should hash everything. runTargetSpec(host, browserTargetSpec, overrides, DefaultTimeout * 2).pipe( tap(() => { expect(host.fileMatchExists('dist', /runtime\.[0-9a-f]{20}\.js/)).toBeTruthy(); expect(host.fileMatchExists('dist', /main\.[0-9a-f]{20}\.js/)).toBeTruthy(); expect(host.fileMatchExists('dist', /polyfills\.[0-9a-f]{20}\.js/)).toBeTruthy(); expect(host.fileMatchExists('dist', /vendor\.[0-9a-f]{20}\.js/)).toBeTruthy(); expect(host.fileMatchExists('dist', /styles\.[0-9a-f]{20}\.css/)).toBeTruthy(); expect(host.fileMatchExists('dist', /spectrum\.[0-9a-f]{20}\.png/)).toBeTruthy(); }), // 'none' should hash nothing. concatMap(() => runTargetSpec(host, browserTargetSpec, { outputHashing: 'none', extractCss: true })), tap(() => { expect(host.fileMatchExists('dist', /runtime\.[0-9a-f]{20}\.js/)).toBeFalsy(); expect(host.fileMatchExists('dist', /main\.[0-9a-f]{20}\.js/)).toBeFalsy(); expect(host.fileMatchExists('dist', /polyfills\.[0-9a-f]{20}\.js/)).toBeFalsy(); expect(host.fileMatchExists('dist', /vendor\.[0-9a-f]{20}\.js/)).toBeFalsy(); expect(host.fileMatchExists('dist', /styles\.[0-9a-f]{20}\.css/)).toBeFalsy(); expect(host.fileMatchExists('dist', /spectrum\.[0-9a-f]{20}\.png/)).toBeFalsy(); }), // 'media' should hash css resources only. concatMap(() => runTargetSpec(host, browserTargetSpec, { outputHashing: 'media', extractCss: true })), tap(() => { expect(host.fileMatchExists('dist', /runtime\.[0-9a-f]{20}\.js/)).toBeFalsy(); expect(host.fileMatchExists('dist', /main\.[0-9a-f]{20}\.js/)).toBeFalsy(); expect(host.fileMatchExists('dist', /polyfills\.[0-9a-f]{20}\.js/)).toBeFalsy(); expect(host.fileMatchExists('dist', /vendor\.[0-9a-f]{20}\.js/)).toBeFalsy(); expect(host.fileMatchExists('dist', /styles\.[0-9a-f]{20}\.css/)).toBeFalsy(); expect(host.fileMatchExists('dist', /spectrum\.[0-9a-f]{20}\.png/)).toBeTruthy(); }), // 'bundles' should hash bundles only. concatMap(() => runTargetSpec(host, browserTargetSpec, { outputHashing: 'bundles', extractCss: true })), tap(() => { expect(host.fileMatchExists('dist', /runtime\.[0-9a-f]{20}\.js/)).toBeTruthy(); expect(host.fileMatchExists('dist', /main\.[0-9a-f]{20}\.js/)).toBeTruthy(); expect(host.fileMatchExists('dist', /polyfills\.[0-9a-f]{20}\.js/)).toBeTruthy(); expect(host.fileMatchExists('dist', /vendor\.[0-9a-f]{20}\.js/)).toBeTruthy(); expect(host.fileMatchExists('dist', /styles\.[0-9a-f]{20}\.css/)).toBeTruthy(); expect(host.fileMatchExists('dist', /spectrum\.[0-9a-f]{20}\.png/)).toBeFalsy(); }), ).toPromise().then(done, done.fail); });
it('works', (done) => { const overrides: Partial<DevServerBuilderOptions> = { servePath: 'test/' }; runTargetSpec(host, devServerTargetSpec, overrides).pipe( tap((buildEvent) => expect(buildEvent.success).toBe(true)), concatMap(() => from(request('http://localhost:4200/test/'))), tap(response => expect(response).toContain('<title>HelloWorldApp</title>')), concatMap(() => from(request('http://localhost:4200/test/abc/'))), tap(response => expect(response).toContain('<title>HelloWorldApp</title>')), take(1), ).subscribe(undefined, done.fail, done); }, 30000);
run(builderConfig: BuilderConfiguration<BuildWebpackServerSchema>): Observable<BuildEvent> { const options = builderConfig.options; const root = this.context.workspace.root; const projectRoot = resolve(root, builderConfig.root); const host = new virtualFs.AliasHost(this.context.host as virtualFs.Host<Stats>); // TODO: verify using of(null) to kickstart things is a pattern. return of(null).pipe( concatMap(() => options.deleteOutputPath ? this._deleteOutputDir(root, normalize(options.outputPath), this.context.host) : of(null)), concatMap(() => addFileReplacements(root, host, options.fileReplacements)), concatMap(() => new Observable(obs => { // Ensure Build Optimizer is only used with AOT. const webpackConfig = this.buildWebpackConfig(root, projectRoot, host, options); const webpackCompiler = webpack(webpackConfig); const statsConfig = getWebpackStatsConfig(options.verbose); const callback: webpack.compiler.CompilerCallback = (err, stats) => { if (err) { return obs.error(err); } const json = stats.toJson(statsConfig); if (options.verbose) { this.context.logger.info(stats.toString(statsConfig)); } else { this.context.logger.info(statsToString(json, statsConfig)); } if (stats.hasWarnings()) { this.context.logger.warn(statsWarningsToString(json, statsConfig)); } if (stats.hasErrors()) { this.context.logger.error(statsErrorsToString(json, statsConfig)); } obs.next({ success: !stats.hasErrors() }); obs.complete(); }; try { webpackCompiler.run(callback); } catch (err) { if (err) { this.context.logger.error( '\nAn error occured during the build:\n' + ((err && err.stack) || err)); } throw err; } })), ); }
run(builderConfig: BuilderConfiguration<BrowserBuilderSchema>): Observable<BuildEvent> { const root = this.context.workspace.root; const projectRoot = resolve(root, builderConfig.root); const host = new virtualFs.AliasHost(this.context.host as virtualFs.Host<fs.Stats>); const webpackBuilder = this.createWebpackBuilder({ ...this.context, host }); const getLoggingCb = this.createLoggingFactory(); const options = normalizeBuilderSchema( host, root, builderConfig, ); return of(null).pipe( concatMap(() => options.deleteOutputPath ? this._deleteOutputDir(root, normalize(options.outputPath), this.context.host) : of(null)), concatMap(() => { let webpackConfig; try { webpackConfig = this.buildWebpackConfig(root, projectRoot, host, options); } catch (e) { return throwError(e); } return webpackBuilder.runWebpack(webpackConfig, getLoggingCb(options.verbose)); }), concatMap(buildEvent => { if (buildEvent.success && !options.watch && options.serviceWorker) { return new Observable(obs => { augmentAppWithServiceWorker( this.context.host, root, projectRoot, resolve(root, normalize(options.outputPath)), options.baseHref || '/', options.ngswConfigPath, ).then( () => { obs.next({ success: true }); obs.complete(); }, (err: Error) => { obs.error(err); }, ); }); } else { return of(buildEvent); } }), ); }
export function runTargetSpec( host: TestProjectHost, targetSpec: TargetSpecifier, overrides = {}, logger: logging.Logger = new logging.NullLogger(), ): Observable<BuildEvent> { targetSpec = { ...targetSpec, overrides }; const workspace = new experimental.workspace.Workspace(workspaceRoot, host); return workspace.loadWorkspaceFromHost(workspaceFile).pipe( concatMap(ws => new Architect(ws).loadArchitect()), concatMap(arch => arch.run(arch.getBuilderConfiguration(targetSpec), { logger })), ); }