@angular-devkit/build-angular 19.0.0-next.6 → 19.0.0-next.8
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/package.json +17 -17
- package/src/builders/karma/application_builder.d.ts +19 -0
- package/src/builders/karma/application_builder.js +217 -0
- package/src/builders/karma/browser_builder.d.ts +21 -0
- package/src/builders/karma/browser_builder.js +137 -0
- package/src/builders/karma/find-tests-plugin.js +2 -96
- package/src/builders/karma/find-tests.d.ts +8 -0
- package/src/builders/karma/find-tests.js +105 -0
- package/src/builders/karma/index.js +72 -126
- package/src/builders/karma/init_test_bed.js +19 -0
- package/src/builders/karma/schema.d.ts +14 -0
- package/src/builders/karma/schema.js +11 -1
- package/src/builders/karma/schema.json +6 -0
- package/src/utils/normalize-cache.js +1 -1
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular-devkit/build-angular",
|
|
3
|
-
"version": "19.0.0-next.
|
|
3
|
+
"version": "19.0.0-next.8",
|
|
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.
|
|
11
|
-
"@angular-devkit/build-webpack": "0.1900.0-next.
|
|
12
|
-
"@angular-devkit/core": "19.0.0-next.
|
|
13
|
-
"@angular/build": "19.0.0-next.
|
|
10
|
+
"@angular-devkit/architect": "0.1900.0-next.8",
|
|
11
|
+
"@angular-devkit/build-webpack": "0.1900.0-next.8",
|
|
12
|
+
"@angular-devkit/core": "19.0.0-next.8",
|
|
13
|
+
"@angular/build": "19.0.0-next.8",
|
|
14
14
|
"@babel/core": "7.25.2",
|
|
15
15
|
"@babel/generator": "7.25.6",
|
|
16
16
|
"@babel/helper-annotate-as-pure": "7.24.7",
|
|
@@ -21,16 +21,16 @@
|
|
|
21
21
|
"@babel/preset-env": "7.25.4",
|
|
22
22
|
"@babel/runtime": "7.25.6",
|
|
23
23
|
"@discoveryjs/json-ext": "0.6.1",
|
|
24
|
-
"@ngtools/webpack": "19.0.0-next.
|
|
24
|
+
"@ngtools/webpack": "19.0.0-next.8",
|
|
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
|
|
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.
|
|
33
|
+
"esbuild-wasm": "0.24.0",
|
|
34
34
|
"fast-glob": "3.3.2",
|
|
35
35
|
"http-proxy-middleware": "3.0.2",
|
|
36
36
|
"https-proxy-agent": "7.0.5",
|
|
@@ -48,20 +48,20 @@
|
|
|
48
48
|
"ora": "5.4.1",
|
|
49
49
|
"parse5-html-rewriting-stream": "7.0.0",
|
|
50
50
|
"picomatch": "4.0.2",
|
|
51
|
-
"piscina": "4.
|
|
52
|
-
"postcss": "8.4.
|
|
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.
|
|
57
|
-
"sass-loader": "16.0.
|
|
56
|
+
"sass": "1.79.3",
|
|
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.
|
|
61
|
+
"terser": "5.33.0",
|
|
62
62
|
"tree-kill": "1.2.2",
|
|
63
63
|
"tslib": "2.7.0",
|
|
64
|
-
"vite": "5.4.
|
|
64
|
+
"vite": "5.4.8",
|
|
65
65
|
"watchpack": "2.4.2",
|
|
66
66
|
"webpack": "5.94.0",
|
|
67
67
|
"webpack-dev-middleware": "7.4.2",
|
|
@@ -70,14 +70,14 @@
|
|
|
70
70
|
"webpack-subresource-integrity": "5.1.0"
|
|
71
71
|
},
|
|
72
72
|
"optionalDependencies": {
|
|
73
|
-
"esbuild": "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.
|
|
80
|
+
"@angular/ssr": "^19.0.0-next.8",
|
|
81
81
|
"@web/test-runner": "^0.19.0",
|
|
82
82
|
"browser-sync": "^3.0.2",
|
|
83
83
|
"jest": "^29.5.0",
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
"devkit",
|
|
134
134
|
"sdk"
|
|
135
135
|
],
|
|
136
|
-
"packageManager": "yarn@4.
|
|
136
|
+
"packageManager": "yarn@4.5.0",
|
|
137
137
|
"repository": {
|
|
138
138
|
"type": "git",
|
|
139
139
|
"url": "https://github.com/angular/angular-cli.git"
|
|
@@ -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,217 @@
|
|
|
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
|
+
exports.writeTestFiles = writeTestFiles;
|
|
35
|
+
const build_1 = require("@angular/build");
|
|
36
|
+
const private_1 = require("@angular/build/private");
|
|
37
|
+
const crypto_1 = require("crypto");
|
|
38
|
+
const fs = __importStar(require("fs/promises"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const rxjs_1 = require("rxjs");
|
|
41
|
+
const read_tsconfig_1 = require("../../utils/read-tsconfig");
|
|
42
|
+
const schema_1 = require("../browser-esbuild/schema");
|
|
43
|
+
const find_tests_1 = require("./find-tests");
|
|
44
|
+
class ApplicationBuildError extends Error {
|
|
45
|
+
constructor(message) {
|
|
46
|
+
super(message);
|
|
47
|
+
this.name = 'ApplicationBuildError';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function execute(options, context, karmaOptions, transforms = {}) {
|
|
51
|
+
return (0, rxjs_1.from)(initializeApplication(options, context, karmaOptions, transforms)).pipe((0, rxjs_1.switchMap)(([karma, karmaConfig]) => new rxjs_1.Observable((subscriber) => {
|
|
52
|
+
// Complete the observable once the Karma server returns.
|
|
53
|
+
const karmaServer = new karma.Server(karmaConfig, (exitCode) => {
|
|
54
|
+
subscriber.next({ success: exitCode === 0 });
|
|
55
|
+
subscriber.complete();
|
|
56
|
+
});
|
|
57
|
+
const karmaStart = karmaServer.start();
|
|
58
|
+
// Cleanup, signal Karma to exit.
|
|
59
|
+
return () => {
|
|
60
|
+
void karmaStart.then(() => karmaServer.stop());
|
|
61
|
+
};
|
|
62
|
+
})), (0, rxjs_1.catchError)((err) => {
|
|
63
|
+
if (err instanceof ApplicationBuildError) {
|
|
64
|
+
return (0, rxjs_1.of)({ success: false, message: err.message });
|
|
65
|
+
}
|
|
66
|
+
throw err;
|
|
67
|
+
}), (0, rxjs_1.defaultIfEmpty)({ success: false }));
|
|
68
|
+
}
|
|
69
|
+
async function getProjectSourceRoot(context) {
|
|
70
|
+
// We have already validated that the project name is set before calling this function.
|
|
71
|
+
const projectName = context.target?.project;
|
|
72
|
+
if (!projectName) {
|
|
73
|
+
return context.workspaceRoot;
|
|
74
|
+
}
|
|
75
|
+
const projectMetadata = await context.getProjectMetadata(projectName);
|
|
76
|
+
const sourceRoot = (projectMetadata.sourceRoot ?? projectMetadata.root ?? '');
|
|
77
|
+
return path.join(context.workspaceRoot, sourceRoot);
|
|
78
|
+
}
|
|
79
|
+
async function collectEntrypoints(options, context) {
|
|
80
|
+
const projectSourceRoot = await getProjectSourceRoot(context);
|
|
81
|
+
// Glob for files to test.
|
|
82
|
+
const testFiles = await (0, find_tests_1.findTests)(options.include ?? [], options.exclude ?? [], context.workspaceRoot, projectSourceRoot);
|
|
83
|
+
const entryPoints = new Set([
|
|
84
|
+
...testFiles,
|
|
85
|
+
'@angular-devkit/build-angular/src/builders/karma/init_test_bed.js',
|
|
86
|
+
]);
|
|
87
|
+
// Extract `zone.js/testing` to a separate entry point because it needs to be loaded after Jasmine.
|
|
88
|
+
const [polyfills, hasZoneTesting] = extractZoneTesting(options.polyfills);
|
|
89
|
+
if (hasZoneTesting) {
|
|
90
|
+
entryPoints.add('zone.js/testing');
|
|
91
|
+
}
|
|
92
|
+
const tsConfigPath = path.resolve(context.workspaceRoot, options.tsConfig);
|
|
93
|
+
const tsConfig = await (0, read_tsconfig_1.readTsconfig)(tsConfigPath);
|
|
94
|
+
const localizePackageInitEntryPoint = '@angular/localize/init';
|
|
95
|
+
const hasLocalizeType = tsConfig.options.types?.some((t) => t === '@angular/localize' || t === localizePackageInitEntryPoint);
|
|
96
|
+
if (hasLocalizeType) {
|
|
97
|
+
polyfills.push(localizePackageInitEntryPoint);
|
|
98
|
+
}
|
|
99
|
+
return [entryPoints, polyfills];
|
|
100
|
+
}
|
|
101
|
+
async function initializeApplication(options, context, karmaOptions, transforms = {}) {
|
|
102
|
+
if (transforms.webpackConfiguration) {
|
|
103
|
+
context.logger.warn(`This build is using the application builder but transforms.webpackConfiguration was provided. The transform will be ignored.`);
|
|
104
|
+
}
|
|
105
|
+
const testDir = path.join(context.workspaceRoot, 'dist/test-out', (0, crypto_1.randomUUID)());
|
|
106
|
+
const [karma, [entryPoints, polyfills]] = await Promise.all([
|
|
107
|
+
Promise.resolve().then(() => __importStar(require('karma'))),
|
|
108
|
+
collectEntrypoints(options, context),
|
|
109
|
+
fs.rm(testDir, { recursive: true, force: true }),
|
|
110
|
+
]);
|
|
111
|
+
const outputPath = testDir;
|
|
112
|
+
// Build tests with `application` builder, using test files as entry points.
|
|
113
|
+
const buildOutput = await first((0, private_1.buildApplicationInternal)({
|
|
114
|
+
entryPoints,
|
|
115
|
+
tsConfig: options.tsConfig,
|
|
116
|
+
outputPath,
|
|
117
|
+
aot: false,
|
|
118
|
+
index: false,
|
|
119
|
+
outputHashing: schema_1.OutputHashing.None,
|
|
120
|
+
optimization: false,
|
|
121
|
+
sourceMap: {
|
|
122
|
+
scripts: true,
|
|
123
|
+
styles: true,
|
|
124
|
+
vendor: true,
|
|
125
|
+
},
|
|
126
|
+
styles: options.styles,
|
|
127
|
+
polyfills,
|
|
128
|
+
webWorkerTsConfig: options.webWorkerTsConfig,
|
|
129
|
+
}, context));
|
|
130
|
+
if (buildOutput.kind === private_1.ResultKind.Failure) {
|
|
131
|
+
throw new ApplicationBuildError('Build failed');
|
|
132
|
+
}
|
|
133
|
+
else if (buildOutput.kind !== private_1.ResultKind.Full) {
|
|
134
|
+
throw new ApplicationBuildError('A full build result is required from the application builder.');
|
|
135
|
+
}
|
|
136
|
+
// Write test files
|
|
137
|
+
await writeTestFiles(buildOutput.files, testDir);
|
|
138
|
+
karmaOptions.files ??= [];
|
|
139
|
+
karmaOptions.files.push(
|
|
140
|
+
// Serve polyfills first.
|
|
141
|
+
{ pattern: `${testDir}/polyfills.js`, type: 'module' },
|
|
142
|
+
// Allow loading of chunk-* files but don't include them all on load.
|
|
143
|
+
{ pattern: `${testDir}/chunk-*.js`, type: 'module', included: false },
|
|
144
|
+
// Allow loading of worker-* files but don't include them all on load.
|
|
145
|
+
{ pattern: `${testDir}/worker-*.js`, type: 'module', included: false },
|
|
146
|
+
// `zone.js/testing`, served but not included on page load.
|
|
147
|
+
{ pattern: `${testDir}/testing.js`, type: 'module', included: false },
|
|
148
|
+
// Serve remaining JS on page load, these are the test entrypoints.
|
|
149
|
+
{ pattern: `${testDir}/*.js`, type: 'module' });
|
|
150
|
+
if (options.styles?.length) {
|
|
151
|
+
// Serve CSS outputs on page load, these are the global styles.
|
|
152
|
+
karmaOptions.files.push({ pattern: `${testDir}/*.css`, type: 'css' });
|
|
153
|
+
}
|
|
154
|
+
const parsedKarmaConfig = await karma.config.parseConfig(options.karmaConfig && path.resolve(context.workspaceRoot, options.karmaConfig), transforms.karmaOptions ? transforms.karmaOptions(karmaOptions) : karmaOptions, { promiseConfig: true, throwErrors: true });
|
|
155
|
+
// Remove the webpack plugin/framework:
|
|
156
|
+
// Alternative would be to make the Karma plugin "smart" but that's a tall order
|
|
157
|
+
// with managing unneeded imports etc..
|
|
158
|
+
const pluginLengthBefore = (parsedKarmaConfig.plugins ?? []).length;
|
|
159
|
+
parsedKarmaConfig.plugins = (parsedKarmaConfig.plugins ?? []).filter((plugin) => {
|
|
160
|
+
if (typeof plugin === 'string') {
|
|
161
|
+
return plugin !== 'framework:@angular-devkit/build-angular';
|
|
162
|
+
}
|
|
163
|
+
return !plugin['framework:@angular-devkit/build-angular'];
|
|
164
|
+
});
|
|
165
|
+
parsedKarmaConfig.frameworks = parsedKarmaConfig.frameworks?.filter((framework) => framework !== '@angular-devkit/build-angular');
|
|
166
|
+
const pluginLengthAfter = (parsedKarmaConfig.plugins ?? []).length;
|
|
167
|
+
if (pluginLengthBefore !== pluginLengthAfter) {
|
|
168
|
+
context.logger.warn(`Ignoring framework "@angular-devkit/build-angular" from karma config file because it's not compatible with the application builder.`);
|
|
169
|
+
}
|
|
170
|
+
// When using code-coverage, auto-add karma-coverage.
|
|
171
|
+
// This was done as part of the karma plugin for webpack.
|
|
172
|
+
if (options.codeCoverage &&
|
|
173
|
+
!parsedKarmaConfig.reporters?.some((r) => r === 'coverage' || r === 'coverage-istanbul')) {
|
|
174
|
+
parsedKarmaConfig.reporters = (parsedKarmaConfig.reporters ?? []).concat(['coverage']);
|
|
175
|
+
}
|
|
176
|
+
return [karma, parsedKarmaConfig];
|
|
177
|
+
}
|
|
178
|
+
async function writeTestFiles(files, testDir) {
|
|
179
|
+
const directoryExists = new Set();
|
|
180
|
+
// Writes the test related output files to disk and ensures the containing directories are present
|
|
181
|
+
await (0, private_1.emitFilesToDisk)(Object.entries(files), async ([filePath, file]) => {
|
|
182
|
+
if (file.type !== build_1.BuildOutputFileType.Browser && file.type !== build_1.BuildOutputFileType.Media) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const fullFilePath = path.join(testDir, filePath);
|
|
186
|
+
// Ensure output subdirectories exist
|
|
187
|
+
const fileBasePath = path.dirname(fullFilePath);
|
|
188
|
+
if (fileBasePath && !directoryExists.has(fileBasePath)) {
|
|
189
|
+
await fs.mkdir(fileBasePath, { recursive: true });
|
|
190
|
+
directoryExists.add(fileBasePath);
|
|
191
|
+
}
|
|
192
|
+
if (file.origin === 'memory') {
|
|
193
|
+
// Write file contents
|
|
194
|
+
await fs.writeFile(fullFilePath, file.contents);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
// Copy file contents
|
|
198
|
+
await fs.copyFile(file.inputPath, fullFilePath, fs.constants.COPYFILE_FICLONE);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
function extractZoneTesting(polyfills) {
|
|
203
|
+
if (typeof polyfills === 'string') {
|
|
204
|
+
polyfills = [polyfills];
|
|
205
|
+
}
|
|
206
|
+
polyfills ??= [];
|
|
207
|
+
const polyfillsWithoutZoneTesting = polyfills.filter((polyfill) => polyfill !== 'zone.js/testing');
|
|
208
|
+
const hasZoneTesting = polyfills.length !== polyfillsWithoutZoneTesting.length;
|
|
209
|
+
return [polyfillsWithoutZoneTesting, hasZoneTesting];
|
|
210
|
+
}
|
|
211
|
+
/** Returns the first item yielded by the given generator and cancels the execution. */
|
|
212
|
+
async function first(generator) {
|
|
213
|
+
for await (const value of generator) {
|
|
214
|
+
return value;
|
|
215
|
+
}
|
|
216
|
+
throw new Error('Expected generator to emit at least once.');
|
|
217
|
+
}
|
|
@@ -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>;
|
|
@@ -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
|
|
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,127 +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
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
karmaOptions.reporters = reporters;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
if (!options.main) {
|
|
113
|
-
webpackConfig.entry ??= {};
|
|
114
|
-
if (typeof webpackConfig.entry === 'object' && !Array.isArray(webpackConfig.entry)) {
|
|
115
|
-
if (Array.isArray(webpackConfig.entry['main'])) {
|
|
116
|
-
webpackConfig.entry['main'].push(getBuiltInMainFile());
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
webpackConfig.entry['main'] = [getBuiltInMainFile()];
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
const projectMetadata = await context.getProjectMetadata(projectName);
|
|
124
|
-
const sourceRoot = (projectMetadata.sourceRoot ?? projectMetadata.root ?? '');
|
|
125
|
-
webpackConfig.plugins ??= [];
|
|
126
|
-
webpackConfig.plugins.push(new find_tests_plugin_1.FindTestsPlugin({
|
|
127
|
-
include: options.include,
|
|
128
|
-
exclude: options.exclude,
|
|
129
|
-
workspaceRoot: context.workspaceRoot,
|
|
130
|
-
projectSourceRoot: path.join(context.workspaceRoot, sourceRoot),
|
|
131
|
-
}));
|
|
132
|
-
karmaOptions.buildWebpack = {
|
|
133
|
-
options,
|
|
134
|
-
webpackConfig,
|
|
135
|
-
logger: context.logger,
|
|
136
|
-
};
|
|
137
|
-
const parsedKarmaConfig = await karma.config.parseConfig(options.karmaConfig && path.resolve(context.workspaceRoot, options.karmaConfig), transforms.karmaOptions ? transforms.karmaOptions(karmaOptions) : karmaOptions, { promiseConfig: true, throwErrors: true });
|
|
138
|
-
return [karma, parsedKarmaConfig];
|
|
139
|
-
}), (0, rxjs_1.switchMap)(([karma, karmaConfig]) => new rxjs_1.Observable((subscriber) => {
|
|
140
|
-
// Pass onto Karma to emit BuildEvents.
|
|
141
|
-
karmaConfig.buildWebpack ??= {};
|
|
142
|
-
if (typeof karmaConfig.buildWebpack === 'object') {
|
|
143
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
144
|
-
karmaConfig.buildWebpack.failureCb ??= () => subscriber.next({ success: false });
|
|
145
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
146
|
-
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;
|
|
147
85
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
subscriber.next({ success: exitCode === 0 });
|
|
151
|
-
subscriber.complete();
|
|
152
|
-
});
|
|
153
|
-
const karmaStart = karmaServer.start();
|
|
154
|
-
// Cleanup, signal Karma to exit.
|
|
155
|
-
return () => {
|
|
156
|
-
void karmaStart.then(() => karmaServer.stop());
|
|
157
|
-
};
|
|
158
|
-
})), (0, rxjs_1.defaultIfEmpty)({ success: false }));
|
|
86
|
+
}
|
|
87
|
+
return karmaOptions;
|
|
159
88
|
}
|
|
160
|
-
function getBuiltInKarmaConfig(workspaceRoot, projectName) {
|
|
89
|
+
function getBuiltInKarmaConfig(workspaceRoot, projectName, useEsbuild) {
|
|
161
90
|
let coverageFolderName = projectName.charAt(0) === '@' ? projectName.slice(1) : projectName;
|
|
162
91
|
if (/[A-Z]/.test(coverageFolderName)) {
|
|
163
92
|
coverageFolderName = core_1.strings.dasherize(coverageFolderName);
|
|
@@ -166,13 +95,13 @@ function getBuiltInKarmaConfig(workspaceRoot, projectName) {
|
|
|
166
95
|
// Any changes to the config here need to be synced to: packages/schematics/angular/config/files/karma.conf.js.template
|
|
167
96
|
return {
|
|
168
97
|
basePath: '',
|
|
169
|
-
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
|
98
|
+
frameworks: ['jasmine', ...(useEsbuild ? [] : ['@angular-devkit/build-angular'])],
|
|
170
99
|
plugins: [
|
|
171
100
|
'karma-jasmine',
|
|
172
101
|
'karma-chrome-launcher',
|
|
173
102
|
'karma-jasmine-html-reporter',
|
|
174
103
|
'karma-coverage',
|
|
175
|
-
'@angular-devkit/build-angular/plugins/karma',
|
|
104
|
+
...(useEsbuild ? [] : ['@angular-devkit/build-angular/plugins/karma']),
|
|
176
105
|
].map((p) => workspaceRootRequire(p)),
|
|
177
106
|
jasmineHtmlReporter: {
|
|
178
107
|
suppressAll: true, // removes the duplicated traces
|
|
@@ -200,19 +129,36 @@ function getBuiltInKarmaConfig(workspaceRoot, projectName) {
|
|
|
200
129
|
};
|
|
201
130
|
}
|
|
202
131
|
exports.default = (0, architect_1.createBuilder)(execute);
|
|
203
|
-
function
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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;
|
|
218
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;
|
|
@@ -2,7 +2,17 @@
|
|
|
2
2
|
// THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
|
|
3
3
|
// CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
-
exports.InlineStyleLanguage = void 0;
|
|
5
|
+
exports.InlineStyleLanguage = exports.BuilderMode = void 0;
|
|
6
|
+
/**
|
|
7
|
+
* Determines how to build the code under test. If set to 'detect', attempts to follow the
|
|
8
|
+
* development builder.
|
|
9
|
+
*/
|
|
10
|
+
var BuilderMode;
|
|
11
|
+
(function (BuilderMode) {
|
|
12
|
+
BuilderMode["Application"] = "application";
|
|
13
|
+
BuilderMode["Browser"] = "browser";
|
|
14
|
+
BuilderMode["Detect"] = "detect";
|
|
15
|
+
})(BuilderMode || (exports.BuilderMode = BuilderMode = {}));
|
|
6
16
|
/**
|
|
7
17
|
* The stylesheet language to use for the application's inline component styles.
|
|
8
18
|
*/
|
|
@@ -267,6 +267,12 @@
|
|
|
267
267
|
"type": "string"
|
|
268
268
|
}
|
|
269
269
|
},
|
|
270
|
+
"builderMode": {
|
|
271
|
+
"type": "string",
|
|
272
|
+
"description": "Determines how to build the code under test. If set to 'detect', attempts to follow the development builder.",
|
|
273
|
+
"enum": ["detect", "browser", "application"],
|
|
274
|
+
"default": "browser"
|
|
275
|
+
},
|
|
270
276
|
"webWorkerTsConfig": {
|
|
271
277
|
"type": "string",
|
|
272
278
|
"description": "TypeScript configuration for Web Worker modules."
|
|
@@ -10,7 +10,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
10
10
|
exports.normalizeCacheOptions = normalizeCacheOptions;
|
|
11
11
|
const node_path_1 = require("node:path");
|
|
12
12
|
/** Version placeholder is replaced during the build process with actual package version */
|
|
13
|
-
const VERSION = '19.0.0-next.
|
|
13
|
+
const VERSION = '19.0.0-next.8';
|
|
14
14
|
function hasCacheMetadata(value) {
|
|
15
15
|
return (!!value &&
|
|
16
16
|
typeof value === 'object' &&
|