@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.
@@ -0,0 +1,137 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
33
+ exports.execute = execute;
34
+ const private_1 = require("@angular/build/private");
35
+ const path = __importStar(require("path"));
36
+ const rxjs_1 = require("rxjs");
37
+ const configs_1 = require("../../tools/webpack/configs");
38
+ const webpack_browser_config_1 = require("../../utils/webpack-browser-config");
39
+ const schema_1 = require("../browser/schema");
40
+ const find_tests_plugin_1 = require("./find-tests-plugin");
41
+ function execute(options, context, karmaOptions, transforms = {}) {
42
+ return (0, rxjs_1.from)(initializeBrowser(options, context)).pipe((0, rxjs_1.switchMap)(async ([karma, webpackConfig]) => {
43
+ const projectName = context.target?.project;
44
+ if (!projectName) {
45
+ throw new Error(`The 'karma' builder requires a target to be specified.`);
46
+ }
47
+ const projectMetadata = await context.getProjectMetadata(projectName);
48
+ const sourceRoot = (projectMetadata.sourceRoot ?? projectMetadata.root ?? '');
49
+ if (!options.main) {
50
+ webpackConfig.entry ??= {};
51
+ if (typeof webpackConfig.entry === 'object' && !Array.isArray(webpackConfig.entry)) {
52
+ if (Array.isArray(webpackConfig.entry['main'])) {
53
+ webpackConfig.entry['main'].push(getBuiltInMainFile());
54
+ }
55
+ else {
56
+ webpackConfig.entry['main'] = [getBuiltInMainFile()];
57
+ }
58
+ }
59
+ }
60
+ webpackConfig.plugins ??= [];
61
+ webpackConfig.plugins.push(new find_tests_plugin_1.FindTestsPlugin({
62
+ include: options.include,
63
+ exclude: options.exclude,
64
+ workspaceRoot: context.workspaceRoot,
65
+ projectSourceRoot: path.join(context.workspaceRoot, sourceRoot),
66
+ }));
67
+ karmaOptions.buildWebpack = {
68
+ options,
69
+ webpackConfig,
70
+ logger: context.logger,
71
+ };
72
+ const parsedKarmaConfig = await karma.config.parseConfig(options.karmaConfig && path.resolve(context.workspaceRoot, options.karmaConfig), transforms.karmaOptions ? transforms.karmaOptions(karmaOptions) : karmaOptions, { promiseConfig: true, throwErrors: true });
73
+ return [karma, parsedKarmaConfig];
74
+ }), (0, rxjs_1.switchMap)(([karma, karmaConfig]) => new rxjs_1.Observable((subscriber) => {
75
+ // Pass onto Karma to emit BuildEvents.
76
+ karmaConfig.buildWebpack ??= {};
77
+ if (typeof karmaConfig.buildWebpack === 'object') {
78
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
+ karmaConfig.buildWebpack.failureCb ??= () => subscriber.next({ success: false });
80
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
81
+ karmaConfig.buildWebpack.successCb ??= () => subscriber.next({ success: true });
82
+ }
83
+ // Complete the observable once the Karma server returns.
84
+ const karmaServer = new karma.Server(karmaConfig, (exitCode) => {
85
+ subscriber.next({ success: exitCode === 0 });
86
+ subscriber.complete();
87
+ });
88
+ const karmaStart = karmaServer.start();
89
+ // Cleanup, signal Karma to exit.
90
+ return () => {
91
+ void karmaStart.then(() => karmaServer.stop());
92
+ };
93
+ })), (0, rxjs_1.defaultIfEmpty)({ success: false }));
94
+ }
95
+ async function initializeBrowser(options, context, webpackConfigurationTransformer) {
96
+ // Purge old build disk cache.
97
+ await (0, private_1.purgeStaleBuildCache)(context);
98
+ const karma = await Promise.resolve().then(() => __importStar(require('karma')));
99
+ const { config } = await (0, webpack_browser_config_1.generateBrowserWebpackConfigFromContext)(
100
+ // only two properties are missing:
101
+ // * `outputPath` which is fixed for tests
102
+ // * `budgets` which might be incorrect due to extra dev libs
103
+ {
104
+ ...options,
105
+ outputPath: '',
106
+ budgets: undefined,
107
+ optimization: false,
108
+ buildOptimizer: false,
109
+ aot: false,
110
+ vendorChunk: true,
111
+ namedChunks: true,
112
+ extractLicenses: false,
113
+ outputHashing: schema_1.OutputHashing.None,
114
+ // The webpack tier owns the watch behavior so we want to force it in the config.
115
+ // When not in watch mode, webpack-dev-middleware will call `compiler.watch` anyway.
116
+ // https://github.com/webpack/webpack-dev-middleware/blob/698c9ae5e9bb9a013985add6189ff21c1a1ec185/src/index.js#L65
117
+ // https://github.com/webpack/webpack/blob/cde1b73e12eb8a77eb9ba42e7920c9ec5d29c2c9/lib/Compiler.js#L379-L388
118
+ watch: true,
119
+ }, context, (wco) => [(0, configs_1.getCommonConfig)(wco), (0, configs_1.getStylesConfig)(wco)]);
120
+ return [karma, (await webpackConfigurationTransformer?.(config)) ?? config];
121
+ }
122
+ function getBuiltInMainFile() {
123
+ const content = Buffer.from(`
124
+ import { getTestBed } from '@angular/core/testing';
125
+ import {
126
+ BrowserDynamicTestingModule,
127
+ platformBrowserDynamicTesting,
128
+ } from '@angular/platform-browser-dynamic/testing';
129
+
130
+ // Initialize the Angular testing environment.
131
+ getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
132
+ errorOnUnknownElements: true,
133
+ errorOnUnknownProperties: true
134
+ });
135
+ `).toString('base64');
136
+ return `ng-virtual-main.js!=!data:text/javascript;base64,${content}`;
137
+ }
@@ -6,39 +6,14 @@
6
6
  * Use of this source code is governed by an MIT-style license that can be
7
7
  * found in the LICENSE file at https://angular.dev/license
8
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
9
  var __importDefault = (this && this.__importDefault) || function (mod) {
33
10
  return (mod && mod.__esModule) ? mod : { "default": mod };
34
11
  };
35
12
  Object.defineProperty(exports, "__esModule", { value: true });
36
13
  exports.FindTestsPlugin = void 0;
37
14
  const assert_1 = __importDefault(require("assert"));
38
- const fast_glob_1 = __importStar(require("fast-glob"));
39
- const fs_1 = require("fs");
40
15
  const mini_css_extract_plugin_1 = require("mini-css-extract-plugin");
41
- const path_1 = require("path");
16
+ const find_tests_1 = require("./find-tests");
42
17
  /**
43
18
  * The name of the plugin provided to Webpack when tapping Webpack compiler hooks.
44
19
  */
@@ -56,7 +31,7 @@ class FindTestsPlugin {
56
31
  let originalImport;
57
32
  // Add tests files are part of the entry-point.
58
33
  webpackOptions.entry = async () => {
59
- const specFiles = await findTests(include, exclude, workspaceRoot, projectSourceRoot);
34
+ const specFiles = await (0, find_tests_1.findTests)(include, exclude, workspaceRoot, projectSourceRoot);
60
35
  const entrypoints = await entry;
61
36
  const entrypoint = entrypoints['main'];
62
37
  if (!entrypoint.import) {
@@ -81,72 +56,3 @@ class FindTestsPlugin {
81
56
  }
82
57
  }
83
58
  exports.FindTestsPlugin = FindTestsPlugin;
84
- // go through all patterns and find unique list of files
85
- async function findTests(include, exclude, workspaceRoot, projectSourceRoot) {
86
- const matchingTestsPromises = include.map((pattern) => findMatchingTests(pattern, exclude, workspaceRoot, projectSourceRoot));
87
- const files = await Promise.all(matchingTestsPromises);
88
- // Unique file names
89
- return [...new Set(files.flat())];
90
- }
91
- const normalizePath = (path) => path.replace(/\\/g, '/');
92
- const removeLeadingSlash = (pattern) => {
93
- if (pattern.charAt(0) === '/') {
94
- return pattern.substring(1);
95
- }
96
- return pattern;
97
- };
98
- const removeRelativeRoot = (path, root) => {
99
- if (path.startsWith(root)) {
100
- return path.substring(root.length);
101
- }
102
- return path;
103
- };
104
- async function findMatchingTests(pattern, ignore, workspaceRoot, projectSourceRoot) {
105
- // normalize pattern, glob lib only accepts forward slashes
106
- let normalizedPattern = normalizePath(pattern);
107
- normalizedPattern = removeLeadingSlash(normalizedPattern);
108
- const relativeProjectRoot = normalizePath((0, path_1.relative)(workspaceRoot, projectSourceRoot) + '/');
109
- // remove relativeProjectRoot to support relative paths from root
110
- // such paths are easy to get when running scripts via IDEs
111
- normalizedPattern = removeRelativeRoot(normalizedPattern, relativeProjectRoot);
112
- // special logic when pattern does not look like a glob
113
- if (!(0, fast_glob_1.isDynamicPattern)(normalizedPattern)) {
114
- if (await isDirectory((0, path_1.join)(projectSourceRoot, normalizedPattern))) {
115
- normalizedPattern = `${normalizedPattern}/**/*.spec.@(ts|tsx)`;
116
- }
117
- else {
118
- // see if matching spec file exists
119
- const fileExt = (0, path_1.extname)(normalizedPattern);
120
- // Replace extension to `.spec.ext`. Example: `src/app/app.component.ts`-> `src/app/app.component.spec.ts`
121
- const potentialSpec = (0, path_1.join)(projectSourceRoot, (0, path_1.dirname)(normalizedPattern), `${(0, path_1.basename)(normalizedPattern, fileExt)}.spec${fileExt}`);
122
- if (await exists(potentialSpec)) {
123
- return [potentialSpec];
124
- }
125
- }
126
- }
127
- // normalize the patterns in the ignore list
128
- const normalizedIgnorePatternList = ignore.map((pattern) => removeRelativeRoot(removeLeadingSlash(normalizePath(pattern)), relativeProjectRoot));
129
- return (0, fast_glob_1.default)(normalizedPattern, {
130
- cwd: projectSourceRoot,
131
- absolute: true,
132
- ignore: ['**/node_modules/**', ...normalizedIgnorePatternList],
133
- });
134
- }
135
- async function isDirectory(path) {
136
- try {
137
- const stats = await fs_1.promises.stat(path);
138
- return stats.isDirectory();
139
- }
140
- catch {
141
- return false;
142
- }
143
- }
144
- async function exists(path) {
145
- try {
146
- await fs_1.promises.access(path, fs_1.constants.F_OK);
147
- return true;
148
- }
149
- catch {
150
- return false;
151
- }
152
- }
@@ -0,0 +1,8 @@
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
+ export declare function findTests(include: string[], exclude: string[], workspaceRoot: string, projectSourceRoot: string): Promise<string[]>;
@@ -0,0 +1,105 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
33
+ exports.findTests = findTests;
34
+ const fast_glob_1 = __importStar(require("fast-glob"));
35
+ const fs_1 = require("fs");
36
+ const path_1 = require("path");
37
+ /* Go through all patterns and find unique list of files */
38
+ async function findTests(include, exclude, workspaceRoot, projectSourceRoot) {
39
+ const matchingTestsPromises = include.map((pattern) => findMatchingTests(pattern, exclude, workspaceRoot, projectSourceRoot));
40
+ const files = await Promise.all(matchingTestsPromises);
41
+ // Unique file names
42
+ return [...new Set(files.flat())];
43
+ }
44
+ const normalizePath = (path) => path.replace(/\\/g, '/');
45
+ const removeLeadingSlash = (pattern) => {
46
+ if (pattern.charAt(0) === '/') {
47
+ return pattern.substring(1);
48
+ }
49
+ return pattern;
50
+ };
51
+ const removeRelativeRoot = (path, root) => {
52
+ if (path.startsWith(root)) {
53
+ return path.substring(root.length);
54
+ }
55
+ return path;
56
+ };
57
+ async function findMatchingTests(pattern, ignore, workspaceRoot, projectSourceRoot) {
58
+ // normalize pattern, glob lib only accepts forward slashes
59
+ let normalizedPattern = normalizePath(pattern);
60
+ normalizedPattern = removeLeadingSlash(normalizedPattern);
61
+ const relativeProjectRoot = normalizePath((0, path_1.relative)(workspaceRoot, projectSourceRoot) + '/');
62
+ // remove relativeProjectRoot to support relative paths from root
63
+ // such paths are easy to get when running scripts via IDEs
64
+ normalizedPattern = removeRelativeRoot(normalizedPattern, relativeProjectRoot);
65
+ // special logic when pattern does not look like a glob
66
+ if (!(0, fast_glob_1.isDynamicPattern)(normalizedPattern)) {
67
+ if (await isDirectory((0, path_1.join)(projectSourceRoot, normalizedPattern))) {
68
+ normalizedPattern = `${normalizedPattern}/**/*.spec.@(ts|tsx)`;
69
+ }
70
+ else {
71
+ // see if matching spec file exists
72
+ const fileExt = (0, path_1.extname)(normalizedPattern);
73
+ // Replace extension to `.spec.ext`. Example: `src/app/app.component.ts`-> `src/app/app.component.spec.ts`
74
+ const potentialSpec = (0, path_1.join)(projectSourceRoot, (0, path_1.dirname)(normalizedPattern), `${(0, path_1.basename)(normalizedPattern, fileExt)}.spec${fileExt}`);
75
+ if (await exists(potentialSpec)) {
76
+ return [potentialSpec];
77
+ }
78
+ }
79
+ }
80
+ // normalize the patterns in the ignore list
81
+ const normalizedIgnorePatternList = ignore.map((pattern) => removeRelativeRoot(removeLeadingSlash(normalizePath(pattern)), relativeProjectRoot));
82
+ return (0, fast_glob_1.default)(normalizedPattern, {
83
+ cwd: projectSourceRoot,
84
+ absolute: true,
85
+ ignore: ['**/node_modules/**', ...normalizedIgnorePatternList],
86
+ });
87
+ }
88
+ async function isDirectory(path) {
89
+ try {
90
+ const stats = await fs_1.promises.stat(path);
91
+ return stats.isDirectory();
92
+ }
93
+ catch {
94
+ return false;
95
+ }
96
+ }
97
+ async function exists(path) {
98
+ try {
99
+ await fs_1.promises.access(path, fs_1.constants.F_OK);
100
+ return true;
101
+ }
102
+ catch {
103
+ return false;
104
+ }
105
+ }
@@ -37,122 +37,56 @@ const core_1 = require("@angular-devkit/core");
37
37
  const module_1 = require("module");
38
38
  const path = __importStar(require("path"));
39
39
  const rxjs_1 = require("rxjs");
40
- const configs_1 = require("../../tools/webpack/configs");
41
- const webpack_browser_config_1 = require("../../utils/webpack-browser-config");
42
- const schema_1 = require("../browser/schema");
43
- const find_tests_plugin_1 = require("./find-tests-plugin");
44
- async function initialize(options, context, webpackConfigurationTransformer) {
45
- // Purge old build disk cache.
46
- await (0, private_1.purgeStaleBuildCache)(context);
47
- const { config } = await (0, webpack_browser_config_1.generateBrowserWebpackConfigFromContext)(
48
- // only two properties are missing:
49
- // * `outputPath` which is fixed for tests
50
- // * `budgets` which might be incorrect due to extra dev libs
51
- {
52
- ...options,
53
- outputPath: '',
54
- budgets: undefined,
55
- optimization: false,
56
- buildOptimizer: false,
57
- aot: false,
58
- vendorChunk: true,
59
- namedChunks: true,
60
- extractLicenses: false,
61
- outputHashing: schema_1.OutputHashing.None,
62
- // The webpack tier owns the watch behavior so we want to force it in the config.
63
- // When not in watch mode, webpack-dev-middleware will call `compiler.watch` anyway.
64
- // https://github.com/webpack/webpack-dev-middleware/blob/698c9ae5e9bb9a013985add6189ff21c1a1ec185/src/index.js#L65
65
- // https://github.com/webpack/webpack/blob/cde1b73e12eb8a77eb9ba42e7920c9ec5d29c2c9/lib/Compiler.js#L379-L388
66
- watch: true,
67
- }, context, (wco) => [(0, configs_1.getCommonConfig)(wco), (0, configs_1.getStylesConfig)(wco)]);
68
- const karma = await Promise.resolve().then(() => __importStar(require('karma')));
69
- return [karma, (await webpackConfigurationTransformer?.(config)) ?? config];
70
- }
40
+ const schema_1 = require("./schema");
71
41
  /**
72
42
  * @experimental Direct usage of this function is considered experimental.
73
43
  */
74
44
  function execute(options, context, transforms = {}) {
75
45
  // Check Angular version.
76
46
  (0, private_1.assertCompatibleAngularVersion)(context.workspaceRoot);
47
+ return (0, rxjs_1.from)(getExecuteWithBuilder(options, context)).pipe((0, rxjs_1.mergeMap)(([useEsbuild, executeWithBuilder]) => {
48
+ const karmaOptions = getBaseKarmaOptions(options, context, useEsbuild);
49
+ return executeWithBuilder.execute(options, context, karmaOptions, transforms);
50
+ }));
51
+ }
52
+ function getBaseKarmaOptions(options, context, useEsbuild) {
77
53
  let singleRun;
78
54
  if (options.watch !== undefined) {
79
55
  singleRun = !options.watch;
80
56
  }
81
- return (0, rxjs_1.from)(initialize(options, context, transforms.webpackConfiguration)).pipe((0, rxjs_1.switchMap)(async ([karma, webpackConfig]) => {
82
- // Determine project name from builder context target
83
- const projectName = context.target?.project;
84
- if (!projectName) {
85
- throw new Error(`The 'karma' builder requires a target to be specified.`);
86
- }
87
- const karmaOptions = options.karmaConfig
88
- ? {}
89
- : getBuiltInKarmaConfig(context.workspaceRoot, projectName);
90
- karmaOptions.singleRun = singleRun;
91
- // Convert browsers from a string to an array
92
- if (typeof options.browsers === 'string' && options.browsers) {
93
- karmaOptions.browsers = options.browsers.split(',');
94
- }
95
- else if (options.browsers === false) {
96
- karmaOptions.browsers = [];
97
- }
98
- if (options.reporters) {
99
- // Split along commas to make it more natural, and remove empty strings.
100
- const reporters = options.reporters
101
- .reduce((acc, curr) => acc.concat(curr.split(',')), [])
102
- .filter((x) => !!x);
103
- if (reporters.length > 0) {
104
- karmaOptions.reporters = reporters;
105
- }
106
- }
107
- if (!options.main) {
108
- webpackConfig.entry ??= {};
109
- if (typeof webpackConfig.entry === 'object' && !Array.isArray(webpackConfig.entry)) {
110
- if (Array.isArray(webpackConfig.entry['main'])) {
111
- webpackConfig.entry['main'].push(getBuiltInMainFile());
112
- }
113
- else {
114
- webpackConfig.entry['main'] = [getBuiltInMainFile()];
115
- }
116
- }
117
- }
118
- const projectMetadata = await context.getProjectMetadata(projectName);
119
- const sourceRoot = (projectMetadata.sourceRoot ?? projectMetadata.root ?? '');
120
- webpackConfig.plugins ??= [];
121
- webpackConfig.plugins.push(new find_tests_plugin_1.FindTestsPlugin({
122
- include: options.include,
123
- exclude: options.exclude,
124
- workspaceRoot: context.workspaceRoot,
125
- projectSourceRoot: path.join(context.workspaceRoot, sourceRoot),
126
- }));
127
- karmaOptions.buildWebpack = {
128
- options,
129
- webpackConfig,
130
- logger: context.logger,
131
- };
132
- const parsedKarmaConfig = await karma.config.parseConfig(options.karmaConfig && path.resolve(context.workspaceRoot, options.karmaConfig), transforms.karmaOptions ? transforms.karmaOptions(karmaOptions) : karmaOptions, { promiseConfig: true, throwErrors: true });
133
- return [karma, parsedKarmaConfig];
134
- }), (0, rxjs_1.switchMap)(([karma, karmaConfig]) => new rxjs_1.Observable((subscriber) => {
135
- // Pass onto Karma to emit BuildEvents.
136
- karmaConfig.buildWebpack ??= {};
137
- if (typeof karmaConfig.buildWebpack === 'object') {
138
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
139
- karmaConfig.buildWebpack.failureCb ??= () => subscriber.next({ success: false });
140
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
141
- karmaConfig.buildWebpack.successCb ??= () => subscriber.next({ success: true });
57
+ // Determine project name from builder context target
58
+ const projectName = context.target?.project;
59
+ if (!projectName) {
60
+ throw new Error(`The 'karma' builder requires a target to be specified.`);
61
+ }
62
+ const karmaOptions = options.karmaConfig
63
+ ? {}
64
+ : getBuiltInKarmaConfig(context.workspaceRoot, projectName, useEsbuild);
65
+ karmaOptions.singleRun = singleRun;
66
+ // Workaround https://github.com/angular/angular-cli/issues/28271, by clearing context by default
67
+ // for single run executions. Not clearing context for multi-run (watched) builds allows the
68
+ // Jasmine Spec Runner to be visible in the browser after test execution.
69
+ karmaOptions.client ??= {};
70
+ karmaOptions.client.clearContext ??= singleRun ?? false; // `singleRun` defaults to `false` per Karma docs.
71
+ // Convert browsers from a string to an array
72
+ if (typeof options.browsers === 'string' && options.browsers) {
73
+ karmaOptions.browsers = options.browsers.split(',');
74
+ }
75
+ else if (options.browsers === false) {
76
+ karmaOptions.browsers = [];
77
+ }
78
+ if (options.reporters) {
79
+ // Split along commas to make it more natural, and remove empty strings.
80
+ const reporters = options.reporters
81
+ .reduce((acc, curr) => acc.concat(curr.split(',')), [])
82
+ .filter((x) => !!x);
83
+ if (reporters.length > 0) {
84
+ karmaOptions.reporters = reporters;
142
85
  }
143
- // Complete the observable once the Karma server returns.
144
- const karmaServer = new karma.Server(karmaConfig, (exitCode) => {
145
- subscriber.next({ success: exitCode === 0 });
146
- subscriber.complete();
147
- });
148
- const karmaStart = karmaServer.start();
149
- // Cleanup, signal Karma to exit.
150
- return () => {
151
- void karmaStart.then(() => karmaServer.stop());
152
- };
153
- })), (0, rxjs_1.defaultIfEmpty)({ success: false }));
86
+ }
87
+ return karmaOptions;
154
88
  }
155
- function getBuiltInKarmaConfig(workspaceRoot, projectName) {
89
+ function getBuiltInKarmaConfig(workspaceRoot, projectName, useEsbuild) {
156
90
  let coverageFolderName = projectName.charAt(0) === '@' ? projectName.slice(1) : projectName;
157
91
  if (/[A-Z]/.test(coverageFolderName)) {
158
92
  coverageFolderName = core_1.strings.dasherize(coverageFolderName);
@@ -161,17 +95,14 @@ function getBuiltInKarmaConfig(workspaceRoot, projectName) {
161
95
  // Any changes to the config here need to be synced to: packages/schematics/angular/config/files/karma.conf.js.template
162
96
  return {
163
97
  basePath: '',
164
- frameworks: ['jasmine', '@angular-devkit/build-angular'],
98
+ frameworks: ['jasmine', ...(useEsbuild ? [] : ['@angular-devkit/build-angular'])],
165
99
  plugins: [
166
100
  'karma-jasmine',
167
101
  'karma-chrome-launcher',
168
102
  'karma-jasmine-html-reporter',
169
103
  'karma-coverage',
170
- '@angular-devkit/build-angular/plugins/karma',
104
+ ...(useEsbuild ? [] : ['@angular-devkit/build-angular/plugins/karma']),
171
105
  ].map((p) => workspaceRootRequire(p)),
172
- client: {
173
- clearContext: false, // leave Jasmine Spec Runner output visible in browser
174
- },
175
106
  jasmineHtmlReporter: {
176
107
  suppressAll: true, // removes the duplicated traces
177
108
  },
@@ -198,19 +129,36 @@ function getBuiltInKarmaConfig(workspaceRoot, projectName) {
198
129
  };
199
130
  }
200
131
  exports.default = (0, architect_1.createBuilder)(execute);
201
- function getBuiltInMainFile() {
202
- const content = Buffer.from(`
203
- import { getTestBed } from '@angular/core/testing';
204
- import {
205
- BrowserDynamicTestingModule,
206
- platformBrowserDynamicTesting,
207
- } from '@angular/platform-browser-dynamic/testing';
208
-
209
- // Initialize the Angular testing environment.
210
- getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
211
- errorOnUnknownElements: true,
212
- errorOnUnknownProperties: true
213
- });
214
- `).toString('base64');
215
- return `ng-virtual-main.js!=!data:text/javascript;base64,${content}`;
132
+ async function getExecuteWithBuilder(options, context) {
133
+ const useEsbuild = await checkForEsbuild(options, context);
134
+ const executeWithBuilderModule = useEsbuild
135
+ ? Promise.resolve().then(() => __importStar(require('./application_builder'))) : Promise.resolve().then(() => __importStar(require('./browser_builder')));
136
+ return [useEsbuild, await executeWithBuilderModule];
137
+ }
138
+ async function checkForEsbuild(options, context) {
139
+ if (options.builderMode !== schema_1.BuilderMode.Detect) {
140
+ return options.builderMode === schema_1.BuilderMode.Application;
141
+ }
142
+ // Look up the current project's build target using a development configuration.
143
+ const buildTargetSpecifier = `::development`;
144
+ const buildTarget = (0, architect_1.targetFromTargetString)(buildTargetSpecifier, context.target?.project, 'build');
145
+ try {
146
+ const developmentBuilderName = await context.getBuilderNameForTarget(buildTarget);
147
+ return isEsbuildBased(developmentBuilderName);
148
+ }
149
+ catch (e) {
150
+ if (!(e instanceof Error) || e.message !== 'Project target does not exist.') {
151
+ throw e;
152
+ }
153
+ // If we can't find a development builder, we can't use 'detect'.
154
+ throw new Error('Failed to detect the builder used by the application. Please set builderMode explicitly.');
155
+ }
156
+ }
157
+ function isEsbuildBased(builderName) {
158
+ if (builderName === '@angular/build:application' ||
159
+ builderName === '@angular-devkit/build-angular:application' ||
160
+ builderName === '@angular-devkit/build-angular:browser-esbuild') {
161
+ return true;
162
+ }
163
+ return false;
216
164
  }
@@ -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
+
9
+ import { getTestBed } from '@angular/core/testing';
10
+ import {
11
+ BrowserDynamicTestingModule,
12
+ platformBrowserDynamicTesting,
13
+ } from '@angular/platform-browser-dynamic/testing';
14
+
15
+ // Initialize the Angular testing environment.
16
+ getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
17
+ errorOnUnknownElements: true,
18
+ errorOnUnknownProperties: true,
19
+ });
@@ -10,6 +10,11 @@ export interface Schema {
10
10
  * Override which browsers tests are run against. Set to `false` to not use any browser.
11
11
  */
12
12
  browsers?: Browsers;
13
+ /**
14
+ * Determines how to build the code under test. If set to 'detect', attempts to follow the
15
+ * development builder.
16
+ */
17
+ builderMode?: BuilderMode;
13
18
  /**
14
19
  * Output a code coverage report.
15
20
  */
@@ -121,6 +126,15 @@ export interface AssetPatternClass {
121
126
  * Override which browsers tests are run against. Set to `false` to not use any browser.
122
127
  */
123
128
  export type Browsers = boolean | string;
129
+ /**
130
+ * Determines how to build the code under test. If set to 'detect', attempts to follow the
131
+ * development builder.
132
+ */
133
+ export declare enum BuilderMode {
134
+ Application = "application",
135
+ Browser = "browser",
136
+ Detect = "detect"
137
+ }
124
138
  export interface FileReplacement {
125
139
  replace?: string;
126
140
  replaceWith?: string;