@angular-devkit/build-angular 19.0.0-next.0 → 19.0.0-next.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License
2
2
 
3
- Copyright (c) 2017 Google, Inc.
3
+ Copyright (c) 2010-2024 Google LLC. https://angular.dev/license
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
9
  copies of the Software, and to permit persons to whom the Software is
10
10
  furnished to do so, subject to the following conditions:
11
11
 
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
14
 
15
15
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
16
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
17
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
18
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/package.json CHANGED
@@ -1,38 +1,38 @@
1
1
  {
2
2
  "name": "@angular-devkit/build-angular",
3
- "version": "19.0.0-next.0",
3
+ "version": "19.0.0-next.10",
4
4
  "description": "Angular Webpack Build Facade",
5
5
  "main": "src/index.js",
6
6
  "typings": "src/index.d.ts",
7
7
  "builders": "builders.json",
8
8
  "dependencies": {
9
9
  "@ampproject/remapping": "2.3.0",
10
- "@angular-devkit/architect": "0.1900.0-next.0",
11
- "@angular-devkit/build-webpack": "0.1900.0-next.0",
12
- "@angular-devkit/core": "19.0.0-next.0",
13
- "@angular/build": "19.0.0-next.0",
14
- "@babel/core": "7.25.2",
15
- "@babel/generator": "7.25.0",
16
- "@babel/helper-annotate-as-pure": "7.24.7",
10
+ "@angular-devkit/architect": "0.1900.0-next.10",
11
+ "@angular-devkit/build-webpack": "0.1900.0-next.10",
12
+ "@angular-devkit/core": "19.0.0-next.10",
13
+ "@angular/build": "19.0.0-next.10",
14
+ "@babel/core": "7.25.7",
15
+ "@babel/generator": "7.25.7",
16
+ "@babel/helper-annotate-as-pure": "7.25.7",
17
17
  "@babel/helper-split-export-declaration": "7.24.7",
18
- "@babel/plugin-transform-async-generator-functions": "7.25.0",
19
- "@babel/plugin-transform-async-to-generator": "7.24.7",
20
- "@babel/plugin-transform-runtime": "7.24.7",
21
- "@babel/preset-env": "7.25.3",
22
- "@babel/runtime": "7.25.0",
18
+ "@babel/plugin-transform-async-generator-functions": "7.25.7",
19
+ "@babel/plugin-transform-async-to-generator": "7.25.7",
20
+ "@babel/plugin-transform-runtime": "7.25.7",
21
+ "@babel/preset-env": "7.25.7",
22
+ "@babel/runtime": "7.25.7",
23
23
  "@discoveryjs/json-ext": "0.6.1",
24
- "@ngtools/webpack": "19.0.0-next.0",
24
+ "@ngtools/webpack": "19.0.0-next.10",
25
25
  "@vitejs/plugin-basic-ssl": "1.1.0",
26
26
  "ansi-colors": "4.1.3",
27
27
  "autoprefixer": "10.4.20",
28
- "babel-loader": "9.1.3",
28
+ "babel-loader": "9.2.1",
29
29
  "browserslist": "^4.21.5",
30
30
  "copy-webpack-plugin": "12.0.2",
31
31
  "critters": "0.0.24",
32
32
  "css-loader": "7.1.2",
33
- "esbuild-wasm": "0.23.0",
33
+ "esbuild-wasm": "0.24.0",
34
34
  "fast-glob": "3.3.2",
35
- "http-proxy-middleware": "3.0.0",
35
+ "http-proxy-middleware": "3.0.3",
36
36
  "https-proxy-agent": "7.0.5",
37
37
  "istanbul-lib-instrument": "6.0.3",
38
38
  "jsonc-parser": "3.3.1",
@@ -42,43 +42,43 @@
42
42
  "license-webpack-plugin": "4.0.2",
43
43
  "loader-utils": "3.3.1",
44
44
  "magic-string": "0.30.11",
45
- "mini-css-extract-plugin": "2.9.0",
45
+ "mini-css-extract-plugin": "2.9.1",
46
46
  "mrmime": "2.0.0",
47
47
  "open": "10.1.0",
48
48
  "ora": "5.4.1",
49
49
  "parse5-html-rewriting-stream": "7.0.0",
50
50
  "picomatch": "4.0.2",
51
- "piscina": "4.6.1",
52
- "postcss": "8.4.41",
51
+ "piscina": "4.7.0",
52
+ "postcss": "8.4.47",
53
53
  "postcss-loader": "8.1.1",
54
54
  "resolve-url-loader": "5.0.0",
55
55
  "rxjs": "7.8.1",
56
- "sass": "1.77.8",
57
- "sass-loader": "16.0.0",
56
+ "sass": "1.79.4",
57
+ "sass-loader": "16.0.2",
58
58
  "semver": "7.6.3",
59
59
  "source-map-loader": "5.0.0",
60
60
  "source-map-support": "0.5.21",
61
- "terser": "5.31.6",
61
+ "terser": "5.34.1",
62
62
  "tree-kill": "1.2.2",
63
- "tslib": "2.6.3",
64
- "vite": "5.4.0",
63
+ "tslib": "2.7.0",
64
+ "vite": "5.4.8",
65
65
  "watchpack": "2.4.2",
66
- "webpack": "5.93.0",
67
- "webpack-dev-middleware": "7.3.0",
68
- "webpack-dev-server": "5.0.4",
66
+ "webpack": "5.95.0",
67
+ "webpack-dev-middleware": "7.4.2",
68
+ "webpack-dev-server": "5.1.0",
69
69
  "webpack-merge": "6.0.1",
70
70
  "webpack-subresource-integrity": "5.1.0"
71
71
  },
72
72
  "optionalDependencies": {
73
- "esbuild": "0.23.0"
73
+ "esbuild": "0.24.0"
74
74
  },
75
75
  "peerDependencies": {
76
76
  "@angular/compiler-cli": "^19.0.0-next.0",
77
77
  "@angular/localize": "^19.0.0-next.0",
78
78
  "@angular/platform-server": "^19.0.0-next.0",
79
79
  "@angular/service-worker": "^19.0.0-next.0",
80
- "@angular/ssr": "^19.0.0-next.0",
81
- "@web/test-runner": "^0.18.0",
80
+ "@angular/ssr": "^19.0.0-next.10",
81
+ "@web/test-runner": "^0.19.0",
82
82
  "browser-sync": "^3.0.2",
83
83
  "jest": "^29.5.0",
84
84
  "jest-environment-jsdom": "^29.5.0",
@@ -86,7 +86,7 @@
86
86
  "ng-packagr": "^19.0.0-next.0",
87
87
  "protractor": "^7.0.0",
88
88
  "tailwindcss": "^2.0.0 || ^3.0.0",
89
- "typescript": ">=5.4 <5.6"
89
+ "typescript": ">=5.5 <5.7"
90
90
  },
91
91
  "peerDependenciesMeta": {
92
92
  "@angular/localize": {
@@ -133,7 +133,7 @@
133
133
  "devkit",
134
134
  "sdk"
135
135
  ],
136
- "packageManager": "yarn@4.4.0",
136
+ "packageManager": "yarn@4.5.0",
137
137
  "repository": {
138
138
  "type": "git",
139
139
  "url": "https://github.com/angular/angular-cli.git"
@@ -11,7 +11,6 @@ exports.buildEsbuildBrowser = buildEsbuildBrowser;
11
11
  exports.convertBrowserOptions = convertBrowserOptions;
12
12
  const build_1 = require("@angular/build");
13
13
  const architect_1 = require("@angular-devkit/architect");
14
- const builder_status_warnings_1 = require("./builder-status-warnings");
15
14
  /**
16
15
  * Main execution function for the esbuild-based application builder.
17
16
  * The options are compatible with the Webpack-based builder.
@@ -20,9 +19,19 @@ const builder_status_warnings_1 = require("./builder-status-warnings");
20
19
  * @returns An async iterable with the builder result output
21
20
  */
22
21
  async function* buildEsbuildBrowser(userOptions, context, infrastructureSettings, plugins) {
23
- // Inform user of status of builder and options
24
- (0, builder_status_warnings_1.logBuilderStatusWarnings)(userOptions, context);
22
+ // Warn about any unsupported options
23
+ if (userOptions['vendorChunk']) {
24
+ context.logger.warn(`The 'vendorChunk' option is not used by this builder and will be ignored.`);
25
+ }
26
+ if (userOptions['commonChunk'] === false) {
27
+ context.logger.warn(`The 'commonChunk' option is always enabled by this builder and will be ignored.`);
28
+ }
29
+ if (userOptions['webWorkerTsConfig']) {
30
+ context.logger.warn(`The 'webWorkerTsConfig' option is not yet supported by this builder.`);
31
+ }
32
+ // Convert browser builder options to application builder options
25
33
  const normalizedOptions = convertBrowserOptions(userOptions);
34
+ // Execute the application builder
26
35
  yield* (0, build_1.buildApplication)(normalizedOptions, context, { codePlugins: plugins });
27
36
  }
28
37
  function convertBrowserOptions(options) {
@@ -32,7 +32,7 @@ async function normalizeOptions(context, projectName, options) {
32
32
  const projectRoot = node_path_1.default.join(workspaceRoot, projectMetadata.root ?? '');
33
33
  const cacheOptions = (0, normalize_cache_1.normalizeCacheOptions)(projectMetadata, workspaceRoot);
34
34
  // Target specifier defaults to the current project's build target using a development configuration
35
- const buildTargetSpecifier = options.buildTarget ?? options.browserTarget ?? `::development`;
35
+ const buildTargetSpecifier = options.buildTarget ?? `::development`;
36
36
  const buildTarget = (0, architect_1.targetFromTargetString)(buildTargetSpecifier, projectName, 'build');
37
37
  // Get the application builder options.
38
38
  const browserBuilderName = await context.getBuilderNameForTarget(buildTarget);
@@ -7,19 +7,12 @@ export interface Schema {
7
7
  * using the 'application' or other esbuild-based builders.
8
8
  */
9
9
  allowedHosts?: string[];
10
- /**
11
- * A browser builder target to serve in the format of `project:target[:configuration]`. You
12
- * can also pass in more than one configuration name as a comma-separated list. Example:
13
- * `project:target:production,staging`.
14
- * @deprecated Use 'buildTarget' instead.
15
- */
16
- browserTarget?: string;
17
10
  /**
18
11
  * A build builder target to serve in the format of `project:target[:configuration]`. You
19
12
  * can also pass in more than one configuration name as a comma-separated list. Example:
20
13
  * `project:target:production,staging`.
21
14
  */
22
- buildTarget?: string;
15
+ buildTarget: string;
23
16
  /**
24
17
  * Don't verify connected clients are part of allowed hosts. This option has no effect when
25
18
  * using the 'application' or other esbuild-based builders.
@@ -4,12 +4,6 @@
4
4
  "description": "Dev Server target options for Build Facade.",
5
5
  "type": "object",
6
6
  "properties": {
7
- "browserTarget": {
8
- "type": "string",
9
- "description": "A browser builder target to serve in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
10
- "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$",
11
- "x-deprecated": "Use 'buildTarget' instead."
12
- },
13
7
  "buildTarget": {
14
8
  "type": "string",
15
9
  "description": "A build builder target to serve in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
@@ -138,5 +132,5 @@
138
132
  }
139
133
  },
140
134
  "additionalProperties": false,
141
- "anyOf": [{ "required": ["buildTarget"] }, { "required": ["browserTarget"] }]
135
+ "required": ["buildTarget"]
142
136
  }
@@ -12,6 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.extractMessages = extractMessages;
14
14
  const private_1 = require("@angular/build/private");
15
+ const node_fs_1 = require("node:fs");
15
16
  const node_path_1 = __importDefault(require("node:path"));
16
17
  const browser_esbuild_1 = require("../browser-esbuild");
17
18
  async function extractMessages(options, builderName, context, extractorConstructor) {
@@ -30,6 +31,7 @@ async function extractMessages(options, builderName, context, extractorConstruct
30
31
  buildOptions.budgets = undefined;
31
32
  buildOptions.index = false;
32
33
  buildOptions.serviceWorker = false;
34
+ buildOptions.server = undefined;
33
35
  buildOptions.ssr = false;
34
36
  buildOptions.appShell = false;
35
37
  buildOptions.prerender = false;
@@ -77,6 +79,9 @@ function setupLocalizeExtractor(extractorConstructor, files, context) {
77
79
  if (file?.origin === 'memory') {
78
80
  content = textDecoder.decode(file.contents);
79
81
  }
82
+ else if (file?.origin === 'disk') {
83
+ content = (0, node_fs_1.readFileSync)(file.inputPath, 'utf-8');
84
+ }
80
85
  if (content === undefined) {
81
86
  throw new Error('Unknown file requested: ' + requestedPath);
82
87
  }
@@ -31,7 +31,7 @@ async function normalizeOptions(context, projectName, options) {
31
31
  const projectMetadata = await context.getProjectMetadata(projectName);
32
32
  const projectRoot = node_path_1.default.join(workspaceRoot, projectMetadata.root ?? '');
33
33
  // Target specifier defaults to the current project's build target with no specified configuration
34
- const buildTargetSpecifier = options.buildTarget ?? options.browserTarget ?? ':';
34
+ const buildTargetSpecifier = options.buildTarget ?? ':';
35
35
  const buildTarget = (0, architect_1.targetFromTargetString)(buildTargetSpecifier, projectName, 'build');
36
36
  const i18nOptions = (0, private_1.createI18nOptions)(projectMetadata);
37
37
  // Normalize xliff format extensions
@@ -2,13 +2,6 @@
2
2
  * Extract i18n target options for Build Facade.
3
3
  */
4
4
  export interface Schema {
5
- /**
6
- * A browser builder target to extract i18n messages in the format of
7
- * `project:target[:configuration]`. You can also pass in more than one configuration name
8
- * as a comma-separated list. Example: `project:target:production,staging`.
9
- * @deprecated Use 'buildTarget' instead.
10
- */
11
- browserTarget?: string;
12
5
  /**
13
6
  * A builder target to extract i18n messages in the format of
14
7
  * `project:target[:configuration]`. You can also pass in more than one configuration name
@@ -4,12 +4,6 @@
4
4
  "description": "Extract i18n target options for Build Facade.",
5
5
  "type": "object",
6
6
  "properties": {
7
- "browserTarget": {
8
- "type": "string",
9
- "description": "A browser builder target to extract i18n messages in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
10
- "pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$",
11
- "x-deprecated": "Use 'buildTarget' instead."
12
- },
13
7
  "buildTarget": {
14
8
  "type": "string",
15
9
  "description": "A builder target to extract i18n messages in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @license
3
+ * Copyright Google LLC All Rights Reserved.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license that can be
6
+ * found in the LICENSE file at https://angular.dev/license
7
+ */
8
+ import { ResultFile } from '@angular/build/private';
9
+ import { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
10
+ import type { ConfigOptions } from 'karma';
11
+ import { Observable } from 'rxjs';
12
+ import { Configuration } from 'webpack';
13
+ import { ExecutionTransformer } from '../../transforms';
14
+ import { Schema as KarmaBuilderOptions } from './schema';
15
+ export declare function execute(options: KarmaBuilderOptions, context: BuilderContext, karmaOptions: ConfigOptions, transforms?: {
16
+ webpackConfiguration?: ExecutionTransformer<Configuration>;
17
+ karmaOptions?: (options: ConfigOptions) => ConfigOptions;
18
+ }): Observable<BuilderOutput>;
19
+ export declare function writeTestFiles(files: Record<string, ResultFile>, testDir: string): Promise<void>;
@@ -0,0 +1,285 @@
1
+ "use strict";
2
+ /**
3
+ * @license
4
+ * Copyright Google LLC All Rights Reserved.
5
+ *
6
+ * Use of this source code is governed by an MIT-style license that can be
7
+ * found in the LICENSE file at https://angular.dev/license
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || function (mod) {
26
+ if (mod && mod.__esModule) return mod;
27
+ var result = {};
28
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
29
+ __setModuleDefault(result, mod);
30
+ return result;
31
+ };
32
+ var __importDefault = (this && this.__importDefault) || function (mod) {
33
+ return (mod && mod.__esModule) ? mod : { "default": mod };
34
+ };
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.execute = execute;
37
+ exports.writeTestFiles = writeTestFiles;
38
+ const build_1 = require("@angular/build");
39
+ const private_1 = require("@angular/build/private");
40
+ const crypto_1 = require("crypto");
41
+ const fast_glob_1 = __importDefault(require("fast-glob"));
42
+ const fs = __importStar(require("fs/promises"));
43
+ const path = __importStar(require("path"));
44
+ const rxjs_1 = require("rxjs");
45
+ const schema_1 = require("../browser-esbuild/schema");
46
+ const find_tests_1 = require("./find-tests");
47
+ class ApplicationBuildError extends Error {
48
+ constructor(message) {
49
+ super(message);
50
+ this.name = 'ApplicationBuildError';
51
+ }
52
+ }
53
+ function injectKarmaReporter(context, buildOptions, karmaConfig, subscriber) {
54
+ const reporterName = 'angular-progress-notifier';
55
+ class ProgressNotifierReporter {
56
+ emitter;
57
+ static $inject = ['emitter'];
58
+ constructor(emitter) {
59
+ this.emitter = emitter;
60
+ this.startWatchingBuild();
61
+ }
62
+ startWatchingBuild() {
63
+ void (async () => {
64
+ for await (const buildOutput of (0, private_1.buildApplicationInternal)({
65
+ ...buildOptions,
66
+ watch: true,
67
+ }, context)) {
68
+ if (buildOutput.kind === private_1.ResultKind.Failure) {
69
+ subscriber.next({ success: false, message: 'Build failed' });
70
+ }
71
+ else if (buildOutput.kind === private_1.ResultKind.Incremental ||
72
+ buildOutput.kind === private_1.ResultKind.Full) {
73
+ await writeTestFiles(buildOutput.files, buildOptions.outputPath);
74
+ this.emitter.refreshFiles();
75
+ }
76
+ }
77
+ })();
78
+ }
79
+ onRunComplete = function (_browsers, results) {
80
+ if (results.exitCode === 0) {
81
+ subscriber.next({ success: true });
82
+ }
83
+ else {
84
+ subscriber.next({ success: false });
85
+ }
86
+ };
87
+ }
88
+ karmaConfig.reporters ??= [];
89
+ karmaConfig.reporters.push(reporterName);
90
+ karmaConfig.plugins ??= [];
91
+ karmaConfig.plugins.push({
92
+ [`reporter:${reporterName}`]: [
93
+ 'factory',
94
+ Object.assign((...args) => new ProgressNotifierReporter(...args), ProgressNotifierReporter),
95
+ ],
96
+ });
97
+ }
98
+ function execute(options, context, karmaOptions, transforms = {}) {
99
+ return (0, rxjs_1.from)(initializeApplication(options, context, karmaOptions, transforms)).pipe((0, rxjs_1.switchMap)(([karma, karmaConfig, buildOptions]) => new rxjs_1.Observable((subscriber) => {
100
+ if (options.watch) {
101
+ injectKarmaReporter(context, buildOptions, karmaConfig, subscriber);
102
+ }
103
+ // Complete the observable once the Karma server returns.
104
+ const karmaServer = new karma.Server(karmaConfig, (exitCode) => {
105
+ subscriber.next({ success: exitCode === 0 });
106
+ subscriber.complete();
107
+ });
108
+ const karmaStart = karmaServer.start();
109
+ // Cleanup, signal Karma to exit.
110
+ return () => {
111
+ void karmaStart.then(() => karmaServer.stop());
112
+ };
113
+ })), (0, rxjs_1.catchError)((err) => {
114
+ if (err instanceof ApplicationBuildError) {
115
+ return (0, rxjs_1.of)({ success: false, message: err.message });
116
+ }
117
+ throw err;
118
+ }), (0, rxjs_1.defaultIfEmpty)({ success: false }));
119
+ }
120
+ async function getProjectSourceRoot(context) {
121
+ // We have already validated that the project name is set before calling this function.
122
+ const projectName = context.target?.project;
123
+ if (!projectName) {
124
+ return context.workspaceRoot;
125
+ }
126
+ const projectMetadata = await context.getProjectMetadata(projectName);
127
+ const sourceRoot = (projectMetadata.sourceRoot ?? projectMetadata.root ?? '');
128
+ return path.join(context.workspaceRoot, sourceRoot);
129
+ }
130
+ function normalizePolyfills(polyfills) {
131
+ if (typeof polyfills === 'string') {
132
+ return [polyfills];
133
+ }
134
+ return polyfills ?? [];
135
+ }
136
+ async function collectEntrypoints(options, context, projectSourceRoot) {
137
+ // Glob for files to test.
138
+ const testFiles = await (0, find_tests_1.findTests)(options.include ?? [], options.exclude ?? [], context.workspaceRoot, projectSourceRoot);
139
+ return new Set(testFiles);
140
+ }
141
+ async function initializeApplication(options, context, karmaOptions, transforms = {}) {
142
+ if (transforms.webpackConfiguration) {
143
+ context.logger.warn(`This build is using the application builder but transforms.webpackConfiguration was provided. The transform will be ignored.`);
144
+ }
145
+ const outputPath = path.join(context.workspaceRoot, 'dist/test-out', (0, crypto_1.randomUUID)());
146
+ const projectSourceRoot = await getProjectSourceRoot(context);
147
+ const [karma, entryPoints] = await Promise.all([
148
+ Promise.resolve().then(() => __importStar(require('karma'))),
149
+ collectEntrypoints(options, context, projectSourceRoot),
150
+ fs.rm(outputPath, { recursive: true, force: true }),
151
+ ]);
152
+ let mainName = 'init_test_bed';
153
+ if (options.main) {
154
+ entryPoints.add(options.main);
155
+ mainName = path.basename(options.main, path.extname(options.main));
156
+ }
157
+ else {
158
+ entryPoints.add('@angular-devkit/build-angular/src/builders/karma/init_test_bed.js');
159
+ }
160
+ const instrumentForCoverage = options.codeCoverage
161
+ ? createInstrumentationFilter(projectSourceRoot, getInstrumentationExcludedPaths(context.workspaceRoot, options.codeCoverageExclude ?? []))
162
+ : undefined;
163
+ const buildOptions = {
164
+ entryPoints,
165
+ tsConfig: options.tsConfig,
166
+ outputPath,
167
+ aot: false,
168
+ index: false,
169
+ outputHashing: schema_1.OutputHashing.None,
170
+ optimization: false,
171
+ sourceMap: {
172
+ scripts: true,
173
+ styles: true,
174
+ vendor: true,
175
+ },
176
+ instrumentForCoverage,
177
+ styles: options.styles,
178
+ polyfills: normalizePolyfills(options.polyfills),
179
+ webWorkerTsConfig: options.webWorkerTsConfig,
180
+ };
181
+ // Build tests with `application` builder, using test files as entry points.
182
+ const buildOutput = await first((0, private_1.buildApplicationInternal)(buildOptions, context));
183
+ if (buildOutput.kind === private_1.ResultKind.Failure) {
184
+ throw new ApplicationBuildError('Build failed');
185
+ }
186
+ else if (buildOutput.kind !== private_1.ResultKind.Full) {
187
+ throw new ApplicationBuildError('A full build result is required from the application builder.');
188
+ }
189
+ // Write test files
190
+ await writeTestFiles(buildOutput.files, buildOptions.outputPath);
191
+ karmaOptions.files ??= [];
192
+ karmaOptions.files.push(
193
+ // Serve polyfills first.
194
+ { pattern: `${outputPath}/polyfills.js`, type: 'module' },
195
+ // Serve global setup script.
196
+ { pattern: `${outputPath}/${mainName}.js`, type: 'module' },
197
+ // Serve all source maps.
198
+ { pattern: `${outputPath}/*.map`, included: false });
199
+ if (hasChunkOrWorkerFiles(buildOutput.files)) {
200
+ karmaOptions.files.push(
201
+ // Allow loading of chunk-* files but don't include them all on load.
202
+ { pattern: `${outputPath}/{chunk,worker}-*.js`, type: 'module', included: false });
203
+ }
204
+ karmaOptions.files.push(
205
+ // Serve remaining JS on page load, these are the test entrypoints.
206
+ { pattern: `${outputPath}/*.js`, type: 'module' });
207
+ if (options.styles?.length) {
208
+ // Serve CSS outputs on page load, these are the global styles.
209
+ karmaOptions.files.push({ pattern: `${outputPath}/*.css`, type: 'css' });
210
+ }
211
+ const parsedKarmaConfig = await karma.config.parseConfig(options.karmaConfig && path.resolve(context.workspaceRoot, options.karmaConfig), transforms.karmaOptions ? transforms.karmaOptions(karmaOptions) : karmaOptions, { promiseConfig: true, throwErrors: true });
212
+ // Remove the webpack plugin/framework:
213
+ // Alternative would be to make the Karma plugin "smart" but that's a tall order
214
+ // with managing unneeded imports etc..
215
+ const pluginLengthBefore = (parsedKarmaConfig.plugins ?? []).length;
216
+ parsedKarmaConfig.plugins = (parsedKarmaConfig.plugins ?? []).filter((plugin) => {
217
+ if (typeof plugin === 'string') {
218
+ return plugin !== 'framework:@angular-devkit/build-angular';
219
+ }
220
+ return !plugin['framework:@angular-devkit/build-angular'];
221
+ });
222
+ parsedKarmaConfig.frameworks = parsedKarmaConfig.frameworks?.filter((framework) => framework !== '@angular-devkit/build-angular');
223
+ const pluginLengthAfter = (parsedKarmaConfig.plugins ?? []).length;
224
+ if (pluginLengthBefore !== pluginLengthAfter) {
225
+ context.logger.warn(`Ignoring framework "@angular-devkit/build-angular" from karma config file because it's not compatible with the application builder.`);
226
+ }
227
+ // When using code-coverage, auto-add karma-coverage.
228
+ // This was done as part of the karma plugin for webpack.
229
+ if (options.codeCoverage &&
230
+ !parsedKarmaConfig.reporters?.some((r) => r === 'coverage' || r === 'coverage-istanbul')) {
231
+ parsedKarmaConfig.reporters = (parsedKarmaConfig.reporters ?? []).concat(['coverage']);
232
+ }
233
+ return [karma, parsedKarmaConfig, buildOptions];
234
+ }
235
+ function hasChunkOrWorkerFiles(files) {
236
+ return Object.keys(files).some((filename) => {
237
+ return /(?:^|\/)(?:worker|chunk)[^/]+\.js$/.test(filename);
238
+ });
239
+ }
240
+ async function writeTestFiles(files, testDir) {
241
+ const directoryExists = new Set();
242
+ // Writes the test related output files to disk and ensures the containing directories are present
243
+ await (0, private_1.emitFilesToDisk)(Object.entries(files), async ([filePath, file]) => {
244
+ if (file.type !== build_1.BuildOutputFileType.Browser && file.type !== build_1.BuildOutputFileType.Media) {
245
+ return;
246
+ }
247
+ const fullFilePath = path.join(testDir, filePath);
248
+ // Ensure output subdirectories exist
249
+ const fileBasePath = path.dirname(fullFilePath);
250
+ if (fileBasePath && !directoryExists.has(fileBasePath)) {
251
+ await fs.mkdir(fileBasePath, { recursive: true });
252
+ directoryExists.add(fileBasePath);
253
+ }
254
+ if (file.origin === 'memory') {
255
+ // Write file contents
256
+ await fs.writeFile(fullFilePath, file.contents);
257
+ }
258
+ else {
259
+ // Copy file contents
260
+ await fs.copyFile(file.inputPath, fullFilePath, fs.constants.COPYFILE_FICLONE);
261
+ }
262
+ });
263
+ }
264
+ /** Returns the first item yielded by the given generator and cancels the execution. */
265
+ async function first(generator) {
266
+ for await (const value of generator) {
267
+ return value;
268
+ }
269
+ throw new Error('Expected generator to emit at least once.');
270
+ }
271
+ function createInstrumentationFilter(includedBasePath, excludedPaths) {
272
+ return (request) => {
273
+ return (!excludedPaths.has(request) &&
274
+ !/\.(e2e|spec)\.tsx?$|[\\/]node_modules[\\/]/.test(request) &&
275
+ request.startsWith(includedBasePath));
276
+ };
277
+ }
278
+ function getInstrumentationExcludedPaths(root, excludedPaths) {
279
+ const excluded = new Set();
280
+ for (const excludeGlob of excludedPaths) {
281
+ const excludePath = excludeGlob[0] === '/' ? excludeGlob.slice(1) : excludeGlob;
282
+ fast_glob_1.default.sync(excludePath, { cwd: root }).forEach((p) => excluded.add(path.join(root, p)));
283
+ }
284
+ return excluded;
285
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @license
3
+ * Copyright Google LLC All Rights Reserved.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license that can be
6
+ * found in the LICENSE file at https://angular.dev/license
7
+ */
8
+ import { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
9
+ import type { ConfigOptions } from 'karma';
10
+ import { Observable } from 'rxjs';
11
+ import { Configuration } from 'webpack';
12
+ import { ExecutionTransformer } from '../../transforms';
13
+ import { Schema as KarmaBuilderOptions } from './schema';
14
+ export type KarmaConfigOptions = ConfigOptions & {
15
+ buildWebpack?: unknown;
16
+ configFile?: string;
17
+ };
18
+ export declare function execute(options: KarmaBuilderOptions, context: BuilderContext, karmaOptions: KarmaConfigOptions, transforms?: {
19
+ webpackConfiguration?: ExecutionTransformer<Configuration>;
20
+ karmaOptions?: (options: KarmaConfigOptions) => KarmaConfigOptions;
21
+ }): Observable<BuilderOutput>;