@angular/build 20.2.0 → 21.0.0-next.0
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/.browserslistrc +5 -5
- package/package.json +15 -15
- package/src/builders/dev-server/builder.js +2 -2
- package/src/builders/dev-server/vite/hmr.d.ts +25 -0
- package/src/builders/dev-server/vite/hmr.js +113 -0
- package/src/builders/dev-server/vite/index.d.ts +21 -0
- package/src/builders/dev-server/{vite-server.js → vite/index.js} +19 -359
- package/src/builders/dev-server/vite/server.d.ts +15 -0
- package/src/builders/dev-server/vite/server.js +229 -0
- package/src/builders/dev-server/vite/utils.d.ts +36 -0
- package/src/builders/dev-server/vite/utils.js +76 -0
- package/src/builders/unit-test/builder.d.ts +1 -1
- package/src/builders/unit-test/builder.js +187 -289
- package/src/builders/unit-test/options.d.ts +1 -0
- package/src/builders/unit-test/options.js +2 -1
- package/src/builders/unit-test/runners/api.d.ts +47 -0
- package/src/builders/unit-test/runners/api.js +9 -0
- package/src/builders/unit-test/runners/karma/executor.d.ts +17 -0
- package/src/builders/unit-test/runners/karma/executor.js +93 -0
- package/src/builders/unit-test/runners/karma/index.d.ts +13 -0
- package/src/builders/unit-test/runners/karma/index.js +26 -0
- package/src/builders/unit-test/runners/vitest/browser-provider.d.ts +11 -0
- package/src/builders/unit-test/runners/vitest/browser-provider.js +69 -0
- package/src/builders/unit-test/runners/vitest/build-options.d.ts +11 -0
- package/src/builders/unit-test/runners/vitest/build-options.js +87 -0
- package/src/builders/unit-test/runners/vitest/executor.d.ts +22 -0
- package/src/builders/unit-test/runners/vitest/executor.js +160 -0
- package/src/builders/unit-test/runners/vitest/index.d.ts +13 -0
- package/src/builders/unit-test/runners/vitest/index.js +30 -0
- package/src/builders/unit-test/schema.d.ts +4 -0
- package/src/builders/unit-test/schema.json +4 -0
- package/src/builders/unit-test/test-discovery.d.ts +8 -0
- package/src/builders/unit-test/test-discovery.js +14 -0
- package/src/private.d.ts +1 -1
- package/src/private.js +2 -2
- package/src/utils/environment-options.d.ts +43 -0
- package/src/utils/environment-options.js +84 -30
- package/src/utils/normalize-cache.js +1 -1
- package/src/utils/version.js +1 -1
- package/src/builders/dev-server/vite-server.d.ts +0 -42
- package/src/builders/unit-test/karma-bridge.d.ts +0 -10
- package/src/builders/unit-test/karma-bridge.js +0 -82
|
@@ -6,320 +6,218 @@
|
|
|
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 () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
43
|
+
if (value !== null && value !== void 0) {
|
|
44
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
45
|
+
var dispose, inner;
|
|
46
|
+
if (async) {
|
|
47
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
48
|
+
dispose = value[Symbol.asyncDispose];
|
|
49
|
+
}
|
|
50
|
+
if (dispose === void 0) {
|
|
51
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
52
|
+
dispose = value[Symbol.dispose];
|
|
53
|
+
if (async) inner = dispose;
|
|
54
|
+
}
|
|
55
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
56
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
57
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
58
|
+
}
|
|
59
|
+
else if (async) {
|
|
60
|
+
env.stack.push({ async: true });
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
};
|
|
64
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
65
|
+
return function (env) {
|
|
66
|
+
function fail(e) {
|
|
67
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
68
|
+
env.hasError = true;
|
|
69
|
+
}
|
|
70
|
+
var r, s = 0;
|
|
71
|
+
function next() {
|
|
72
|
+
while (r = env.stack.pop()) {
|
|
73
|
+
try {
|
|
74
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
75
|
+
if (r.dispose) {
|
|
76
|
+
var result = r.dispose.call(r.value);
|
|
77
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
78
|
+
}
|
|
79
|
+
else s |= 1;
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
fail(e);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
86
|
+
if (env.hasError) throw env.error;
|
|
87
|
+
}
|
|
88
|
+
return next();
|
|
89
|
+
};
|
|
90
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
91
|
+
var e = new Error(message);
|
|
92
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
93
|
+
});
|
|
9
94
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10
95
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11
96
|
};
|
|
12
97
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
98
|
exports.execute = execute;
|
|
99
|
+
const architect_1 = require("@angular-devkit/architect");
|
|
14
100
|
const node_assert_1 = __importDefault(require("node:assert"));
|
|
15
|
-
const node_crypto_1 = require("node:crypto");
|
|
16
|
-
const node_module_1 = require("node:module");
|
|
17
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
18
101
|
const virtual_module_plugin_1 = require("../../tools/esbuild/virtual-module-plugin");
|
|
19
102
|
const error_1 = require("../../utils/error");
|
|
20
|
-
const load_esm_1 = require("../../utils/load-esm");
|
|
21
|
-
const path_1 = require("../../utils/path");
|
|
22
103
|
const application_1 = require("../application");
|
|
23
104
|
const results_1 = require("../application/results");
|
|
24
|
-
const schema_1 = require("../application/schema");
|
|
25
|
-
const application_builder_1 = require("../karma/application_builder");
|
|
26
|
-
const find_tests_1 = require("../karma/find-tests");
|
|
27
|
-
const karma_bridge_1 = require("./karma-bridge");
|
|
28
105
|
const options_1 = require("./options");
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
async function* execute(options, context, extensions = {}) {
|
|
34
|
-
// Determine project name from builder context target
|
|
35
|
-
const projectName = context.target?.project;
|
|
36
|
-
if (!projectName) {
|
|
37
|
-
context.logger.error(`The "${context.builder.builderName}" builder requires a target to be specified.`);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
context.logger.warn(`NOTE: The "${context.builder.builderName}" builder is currently EXPERIMENTAL and not ready for production use.`);
|
|
41
|
-
const normalizedOptions = await (0, options_1.normalizeOptions)(context, projectName, options);
|
|
42
|
-
const { projectSourceRoot, workspaceRoot, runnerName } = normalizedOptions;
|
|
43
|
-
// Translate options and use karma builder directly if specified
|
|
44
|
-
if (runnerName === 'karma') {
|
|
45
|
-
const karmaBridge = await (0, karma_bridge_1.useKarmaBuilder)(context, normalizedOptions);
|
|
46
|
-
yield* karmaBridge;
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
if (runnerName !== 'vitest') {
|
|
50
|
-
context.logger.error('Unknown test runner: ' + runnerName);
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
// Find test files
|
|
54
|
-
const testFiles = await (0, find_tests_1.findTests)(normalizedOptions.include, normalizedOptions.exclude, workspaceRoot, projectSourceRoot);
|
|
55
|
-
if (testFiles.length === 0) {
|
|
56
|
-
context.logger.error('No tests found.');
|
|
57
|
-
return { success: false };
|
|
106
|
+
async function loadTestRunner(runnerName) {
|
|
107
|
+
// Harden against directory traversal
|
|
108
|
+
if (!/^[a-zA-Z0-9-]+$/.test(runnerName)) {
|
|
109
|
+
throw new Error(`Invalid runner name "${runnerName}". Runner names can only contain alphanumeric characters and hyphens.`);
|
|
58
110
|
}
|
|
59
|
-
|
|
60
|
-
entryPoints.set('init-testbed', 'angular:test-bed-init');
|
|
61
|
-
let vitestNodeModule;
|
|
111
|
+
let runnerModule;
|
|
62
112
|
try {
|
|
63
|
-
|
|
113
|
+
runnerModule = await Promise.resolve(`${`./runners/${runnerName}/index`}`).then(s => __importStar(require(s)));
|
|
64
114
|
}
|
|
65
|
-
catch (
|
|
66
|
-
(0, error_1.assertIsError)(
|
|
67
|
-
if (
|
|
68
|
-
throw
|
|
115
|
+
catch (e) {
|
|
116
|
+
(0, error_1.assertIsError)(e);
|
|
117
|
+
if (e.code === 'ERR_MODULE_NOT_FOUND') {
|
|
118
|
+
throw new Error(`Unknown test runner "${runnerName}".`);
|
|
69
119
|
}
|
|
70
|
-
|
|
71
|
-
|
|
120
|
+
throw new Error(`Failed to load the '${runnerName}' test runner. The package may be corrupted or improperly installed.\n` +
|
|
121
|
+
`Error: ${e.message}`);
|
|
72
122
|
}
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const buildOptions = {
|
|
79
|
-
...buildTargetOptions,
|
|
80
|
-
watch: normalizedOptions.watch,
|
|
81
|
-
incrementalResults: normalizedOptions.watch,
|
|
82
|
-
outputPath,
|
|
83
|
-
index: false,
|
|
84
|
-
browser: undefined,
|
|
85
|
-
server: undefined,
|
|
86
|
-
outputMode: undefined,
|
|
87
|
-
localize: false,
|
|
88
|
-
budgets: [],
|
|
89
|
-
serviceWorker: false,
|
|
90
|
-
appShell: false,
|
|
91
|
-
ssr: false,
|
|
92
|
-
prerender: false,
|
|
93
|
-
sourceMap: { scripts: true, vendor: false, styles: false },
|
|
94
|
-
outputHashing: schema_1.OutputHashing.None,
|
|
95
|
-
optimization: false,
|
|
96
|
-
tsConfig: normalizedOptions.tsConfig,
|
|
97
|
-
entryPoints,
|
|
98
|
-
externalDependencies: [
|
|
99
|
-
'vitest',
|
|
100
|
-
'@vitest/browser/context',
|
|
101
|
-
...(buildTargetOptions.externalDependencies ?? []),
|
|
102
|
-
],
|
|
103
|
-
};
|
|
104
|
-
extensions ??= {};
|
|
105
|
-
extensions.codePlugins ??= [];
|
|
106
|
-
const virtualTestBedInit = (0, virtual_module_plugin_1.createVirtualModulePlugin)({
|
|
107
|
-
namespace: 'angular:test-bed-init',
|
|
108
|
-
loadContent: async () => {
|
|
109
|
-
const contents = [
|
|
110
|
-
// Initialize the Angular testing environment
|
|
111
|
-
`import { NgModule } from '@angular/core';`,
|
|
112
|
-
`import { getTestBed, ɵgetCleanupHook as getCleanupHook } from '@angular/core/testing';`,
|
|
113
|
-
`import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';`,
|
|
114
|
-
'',
|
|
115
|
-
normalizedOptions.providersFile
|
|
116
|
-
? `import providers from './${(0, path_1.toPosixPath)(node_path_1.default
|
|
117
|
-
.relative(projectSourceRoot, normalizedOptions.providersFile)
|
|
118
|
-
.replace(/.[mc]?ts$/, ''))}'`
|
|
119
|
-
: 'const providers = [];',
|
|
120
|
-
'',
|
|
121
|
-
// Same as https://github.com/angular/angular/blob/05a03d3f975771bb59c7eefd37c01fa127ee2229/packages/core/testing/src/test_hooks.ts#L21-L29
|
|
122
|
-
`beforeEach(getCleanupHook(false));`,
|
|
123
|
-
`afterEach(getCleanupHook(true));`,
|
|
124
|
-
'',
|
|
125
|
-
`@NgModule({`,
|
|
126
|
-
` providers,`,
|
|
127
|
-
`})`,
|
|
128
|
-
`export class TestModule {}`,
|
|
129
|
-
'',
|
|
130
|
-
`getTestBed().initTestEnvironment([BrowserTestingModule, TestModule], platformBrowserTesting(), {`,
|
|
131
|
-
` errorOnUnknownElements: true,`,
|
|
132
|
-
` errorOnUnknownProperties: true,`,
|
|
133
|
-
'});',
|
|
134
|
-
];
|
|
135
|
-
return {
|
|
136
|
-
contents: contents.join('\n'),
|
|
137
|
-
loader: 'js',
|
|
138
|
-
resolveDir: projectSourceRoot,
|
|
139
|
-
};
|
|
140
|
-
},
|
|
141
|
-
});
|
|
142
|
-
extensions.codePlugins.unshift(virtualTestBedInit);
|
|
143
|
-
let instance;
|
|
144
|
-
// Setup vitest browser options if configured
|
|
145
|
-
const { browser, errors } = setupBrowserConfiguration(normalizedOptions.browsers, normalizedOptions.debug, projectSourceRoot);
|
|
146
|
-
if (errors?.length) {
|
|
147
|
-
errors.forEach((error) => context.logger.error(error));
|
|
148
|
-
return { success: false };
|
|
123
|
+
const runner = runnerModule.default;
|
|
124
|
+
if (!runner ||
|
|
125
|
+
typeof runner.getBuildOptions !== 'function' ||
|
|
126
|
+
typeof runner.createExecutor !== 'function') {
|
|
127
|
+
throw new Error(`The loaded test runner '${runnerName}' does not appear to be a valid TestRunner implementation.`);
|
|
149
128
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
setupFiles.unshift('polyfills.js');
|
|
129
|
+
return runner;
|
|
130
|
+
}
|
|
131
|
+
function prepareBuildExtensions(virtualFiles, projectSourceRoot, extensions) {
|
|
132
|
+
if (!virtualFiles) {
|
|
133
|
+
return extensions;
|
|
156
134
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
project: ['base', projectName],
|
|
179
|
-
name: 'base',
|
|
180
|
-
include: [],
|
|
181
|
-
reporters: normalizedOptions.reporters ?? ['default'],
|
|
182
|
-
watch: normalizedOptions.watch,
|
|
183
|
-
coverage: generateCoverageOption(normalizedOptions.codeCoverage, workspaceRoot, outputPath),
|
|
184
|
-
...debugOptions,
|
|
185
|
-
}, {
|
|
186
|
-
plugins: [
|
|
187
|
-
{
|
|
188
|
-
name: 'angular:project-init',
|
|
189
|
-
async configureVitest(context) {
|
|
190
|
-
// Create a subproject that can be configured with plugins for browser mode.
|
|
191
|
-
// Plugins defined directly in the vite overrides will not be present in the
|
|
192
|
-
// browser specific Vite instance.
|
|
193
|
-
const [project] = await context.injectTestProjects({
|
|
194
|
-
test: {
|
|
195
|
-
name: projectName,
|
|
196
|
-
root: outputPath,
|
|
197
|
-
globals: true,
|
|
198
|
-
setupFiles,
|
|
199
|
-
// Use `jsdom` if no browsers are explicitly configured.
|
|
200
|
-
// `node` is effectively no "environment" and the default.
|
|
201
|
-
environment: browser ? 'node' : 'jsdom',
|
|
202
|
-
browser,
|
|
203
|
-
},
|
|
204
|
-
plugins: [
|
|
205
|
-
{
|
|
206
|
-
name: 'angular:html-index',
|
|
207
|
-
transformIndexHtml() {
|
|
208
|
-
// Add all global stylesheets
|
|
209
|
-
return (Object.entries(result.files)
|
|
210
|
-
// TODO: Expand this to all configured global stylesheets
|
|
211
|
-
.filter(([file]) => file === 'styles.css')
|
|
212
|
-
.map(([styleUrl]) => ({
|
|
213
|
-
tag: 'link',
|
|
214
|
-
attrs: {
|
|
215
|
-
'href': styleUrl,
|
|
216
|
-
'rel': 'stylesheet',
|
|
217
|
-
},
|
|
218
|
-
injectTo: 'head',
|
|
219
|
-
})));
|
|
220
|
-
},
|
|
221
|
-
},
|
|
222
|
-
],
|
|
223
|
-
});
|
|
224
|
-
// Adjust coverage excludes to not include the otherwise automatically inserted included unit tests.
|
|
225
|
-
// Vite does this as a convenience but is problematic for the bundling strategy employed by the
|
|
226
|
-
// builder's test setup. To workaround this, the excludes are adjusted here to only automaticallyAdd commentMore actions
|
|
227
|
-
// exclude the TypeScript source test files.
|
|
228
|
-
project.config.coverage.exclude = [
|
|
229
|
-
...(normalizedOptions.codeCoverage?.exclude ?? []),
|
|
230
|
-
'**/*.{test,spec}.?(c|m)ts',
|
|
231
|
-
];
|
|
232
|
-
},
|
|
233
|
-
},
|
|
234
|
-
],
|
|
235
|
-
});
|
|
236
|
-
// Check if all the tests pass to calculate the result
|
|
237
|
-
const testModules = instance.state.getTestModules();
|
|
238
|
-
yield { success: testModules.every((testModule) => testModule.ok()) };
|
|
135
|
+
extensions ??= {};
|
|
136
|
+
extensions.codePlugins ??= [];
|
|
137
|
+
for (const [namespace, contents] of Object.entries(virtualFiles)) {
|
|
138
|
+
extensions.codePlugins.push((0, virtual_module_plugin_1.createVirtualModulePlugin)({
|
|
139
|
+
namespace,
|
|
140
|
+
loadContent: () => {
|
|
141
|
+
return {
|
|
142
|
+
contents,
|
|
143
|
+
loader: 'js',
|
|
144
|
+
resolveDir: projectSourceRoot,
|
|
145
|
+
};
|
|
146
|
+
},
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
return extensions;
|
|
150
|
+
}
|
|
151
|
+
async function* runBuildAndTest(executor, applicationBuildOptions, context, extensions) {
|
|
152
|
+
for await (const buildResult of (0, application_1.buildApplicationInternal)(applicationBuildOptions, context, extensions)) {
|
|
153
|
+
if (buildResult.kind === results_1.ResultKind.Failure) {
|
|
154
|
+
yield { success: false };
|
|
155
|
+
continue;
|
|
239
156
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
// Vitest will automatically close if not using watch mode
|
|
244
|
-
await instance?.close();
|
|
157
|
+
else if (buildResult.kind !== results_1.ResultKind.Full &&
|
|
158
|
+
buildResult.kind !== results_1.ResultKind.Incremental) {
|
|
159
|
+
node_assert_1.default.fail('A full and/or incremental build result is required from the application builder.');
|
|
245
160
|
}
|
|
161
|
+
(0, node_assert_1.default)(buildResult.files, 'Builder did not provide result files.');
|
|
162
|
+
// Pass the build artifacts to the executor
|
|
163
|
+
yield* executor.execute(buildResult);
|
|
246
164
|
}
|
|
247
165
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
166
|
+
/**
|
|
167
|
+
* @experimental Direct usage of this function is considered experimental.
|
|
168
|
+
*/
|
|
169
|
+
async function* execute(options, context, extensions) {
|
|
170
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
171
|
+
try {
|
|
172
|
+
// Determine project name from builder context target
|
|
173
|
+
const projectName = context.target?.project;
|
|
174
|
+
if (!projectName) {
|
|
175
|
+
context.logger.error(`The builder requires a target to be specified.`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
context.logger.warn(`NOTE: The "unit-test" builder is currently EXPERIMENTAL and not ready for production use.`);
|
|
179
|
+
const normalizedOptions = await (0, options_1.normalizeOptions)(context, projectName, options);
|
|
180
|
+
const runner = await loadTestRunner(normalizedOptions.runnerName);
|
|
181
|
+
const executor = __addDisposableResource(env_1, await runner.createExecutor(context, normalizedOptions), true);
|
|
182
|
+
if (runner.isStandalone) {
|
|
183
|
+
yield* executor.execute({
|
|
184
|
+
kind: results_1.ResultKind.Full,
|
|
185
|
+
files: {},
|
|
186
|
+
});
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
// Get base build options from the buildTarget
|
|
190
|
+
let buildTargetOptions;
|
|
252
191
|
try {
|
|
253
|
-
|
|
254
|
-
return providerName;
|
|
192
|
+
buildTargetOptions = (await context.validateOptions(await context.getTargetOptions(normalizedOptions.buildTarget), await context.getBuilderNameForTarget(normalizedOptions.buildTarget)));
|
|
255
193
|
}
|
|
256
|
-
catch {
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
catch {
|
|
276
|
-
errors ??= [];
|
|
277
|
-
errors.push('The "browsers" option requires the "@vitest/browser" package to be installed within the project.' +
|
|
278
|
-
' Please install this package and rerun the test command.');
|
|
279
|
-
}
|
|
280
|
-
const provider = findBrowserProvider(projectResolver);
|
|
281
|
-
if (!provider) {
|
|
282
|
-
errors ??= [];
|
|
283
|
-
errors.push('The "browsers" option requires either "playwright" or "webdriverio" to be installed within the project.' +
|
|
284
|
-
' Please install one of these packages and rerun the test command.');
|
|
285
|
-
}
|
|
286
|
-
// Vitest current requires the playwright browser provider to use the inspect-brk option used by "debug"
|
|
287
|
-
if (debug && provider !== 'playwright') {
|
|
288
|
-
errors ??= [];
|
|
289
|
-
errors.push('Debugging browser mode tests currently requires the use of "playwright".' +
|
|
290
|
-
' Please install this package and rerun the test command.');
|
|
194
|
+
catch (e) {
|
|
195
|
+
(0, error_1.assertIsError)(e);
|
|
196
|
+
context.logger.error(`Could not load build target options for "${(0, architect_1.targetStringFromTarget)(normalizedOptions.buildTarget)}".\n` +
|
|
197
|
+
`Please check your 'angular.json' configuration.\n` +
|
|
198
|
+
`Error: ${e.message}`);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
// Get runner-specific build options from the hook
|
|
202
|
+
const { buildOptions: runnerBuildOptions, virtualFiles } = await runner.getBuildOptions(normalizedOptions, buildTargetOptions);
|
|
203
|
+
const finalExtensions = prepareBuildExtensions(virtualFiles, normalizedOptions.projectSourceRoot, extensions);
|
|
204
|
+
// Prepare and run the application build
|
|
205
|
+
const applicationBuildOptions = {
|
|
206
|
+
...buildTargetOptions,
|
|
207
|
+
...runnerBuildOptions,
|
|
208
|
+
watch: normalizedOptions.watch,
|
|
209
|
+
tsConfig: normalizedOptions.tsConfig,
|
|
210
|
+
progress: normalizedOptions.buildProgress ?? buildTargetOptions.progress,
|
|
211
|
+
};
|
|
212
|
+
yield* runBuildAndTest(executor, applicationBuildOptions, context, finalExtensions);
|
|
291
213
|
}
|
|
292
|
-
|
|
293
|
-
|
|
214
|
+
catch (e_1) {
|
|
215
|
+
env_1.error = e_1;
|
|
216
|
+
env_1.hasError = true;
|
|
294
217
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
instances: browsers.map((browserName) => ({
|
|
300
|
-
browser: normalizeBrowserName(browserName),
|
|
301
|
-
})),
|
|
302
|
-
};
|
|
303
|
-
return { browser };
|
|
304
|
-
}
|
|
305
|
-
function generateOutputPath() {
|
|
306
|
-
const datePrefix = new Date().toISOString().replaceAll(/[-:.]/g, '');
|
|
307
|
-
const uuidSuffix = (0, node_crypto_1.randomUUID)().slice(0, 8);
|
|
308
|
-
return node_path_1.default.join('dist', 'test-out', `${datePrefix}-${uuidSuffix}`);
|
|
309
|
-
}
|
|
310
|
-
function generateCoverageOption(codeCoverage, workspaceRoot, outputPath) {
|
|
311
|
-
if (!codeCoverage) {
|
|
312
|
-
return {
|
|
313
|
-
enabled: false,
|
|
314
|
-
};
|
|
218
|
+
finally {
|
|
219
|
+
const result_1 = __disposeResources(env_1);
|
|
220
|
+
if (result_1)
|
|
221
|
+
await result_1;
|
|
315
222
|
}
|
|
316
|
-
return {
|
|
317
|
-
enabled: true,
|
|
318
|
-
excludeAfterRemap: true,
|
|
319
|
-
include: [`${(0, path_1.toPosixPath)(node_path_1.default.relative(workspaceRoot, outputPath))}/**`],
|
|
320
|
-
// Special handling for `reporter` due to an undefined value causing upstream failures
|
|
321
|
-
...(codeCoverage.reporters
|
|
322
|
-
? { reporter: codeCoverage.reporters }
|
|
323
|
-
: {}),
|
|
324
|
-
};
|
|
325
223
|
}
|
|
@@ -22,6 +22,7 @@ export declare function normalizeOptions(context: BuilderContext, projectName: s
|
|
|
22
22
|
reporters: [string, Record<string, unknown>][] | undefined;
|
|
23
23
|
} | undefined;
|
|
24
24
|
tsConfig: string;
|
|
25
|
+
buildProgress: boolean | undefined;
|
|
25
26
|
reporters: string[] | undefined;
|
|
26
27
|
browsers: string[] | undefined;
|
|
27
28
|
watch: boolean;
|
|
@@ -28,7 +28,7 @@ async function normalizeOptions(context, projectName, options) {
|
|
|
28
28
|
// Target specifier defaults to the current project's build target using a development configuration
|
|
29
29
|
const buildTargetSpecifier = options.buildTarget ?? `::development`;
|
|
30
30
|
const buildTarget = (0, architect_1.targetFromTargetString)(buildTargetSpecifier, projectName, 'build');
|
|
31
|
-
const { tsConfig, runner, reporters, browsers } = options;
|
|
31
|
+
const { tsConfig, runner, reporters, browsers, progress } = options;
|
|
32
32
|
return {
|
|
33
33
|
// Project/workspace information
|
|
34
34
|
workspaceRoot,
|
|
@@ -49,6 +49,7 @@ async function normalizeOptions(context, projectName, options) {
|
|
|
49
49
|
}
|
|
50
50
|
: undefined,
|
|
51
51
|
tsConfig,
|
|
52
|
+
buildProgress: progress,
|
|
52
53
|
reporters,
|
|
53
54
|
browsers,
|
|
54
55
|
watch: options.watch ?? (0, tty_1.isTTY)(),
|
|
@@ -0,0 +1,47 @@
|
|
|
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 type { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
|
|
9
|
+
import type { ApplicationBuilderInternalOptions } from '../../application/options';
|
|
10
|
+
import type { FullResult, IncrementalResult } from '../../application/results';
|
|
11
|
+
import type { NormalizedUnitTestBuilderOptions } from '../options';
|
|
12
|
+
export interface RunnerOptions {
|
|
13
|
+
buildOptions: Partial<ApplicationBuilderInternalOptions>;
|
|
14
|
+
virtualFiles?: Record<string, string>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Represents a stateful test execution session.
|
|
18
|
+
* An instance of this is created for each `ng test` command.
|
|
19
|
+
*/
|
|
20
|
+
export interface TestExecutor {
|
|
21
|
+
/**
|
|
22
|
+
* Executes tests using the artifacts from a specific build.
|
|
23
|
+
* This method can be called multiple times in watch mode.
|
|
24
|
+
*
|
|
25
|
+
* @param buildResult The output from the application builder.
|
|
26
|
+
* @returns An async iterable builder output stream.
|
|
27
|
+
*/
|
|
28
|
+
execute(buildResult: FullResult | IncrementalResult): AsyncIterable<BuilderOutput>;
|
|
29
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Represents the metadata and hooks for a specific test runner.
|
|
33
|
+
*/
|
|
34
|
+
export interface TestRunner {
|
|
35
|
+
readonly name: string;
|
|
36
|
+
readonly isStandalone?: boolean;
|
|
37
|
+
getBuildOptions(options: NormalizedUnitTestBuilderOptions, baseBuildOptions: Partial<ApplicationBuilderInternalOptions>): RunnerOptions | Promise<RunnerOptions>;
|
|
38
|
+
/**
|
|
39
|
+
* Creates a stateful executor for a test session.
|
|
40
|
+
* This is called once at the start of the `ng test` command.
|
|
41
|
+
*
|
|
42
|
+
* @param context The Architect builder context.
|
|
43
|
+
* @param options The normalized unit test options.
|
|
44
|
+
* @returns A TestExecutor instance that will handle the test runs.
|
|
45
|
+
*/
|
|
46
|
+
createExecutor(context: BuilderContext, options: NormalizedUnitTestBuilderOptions): Promise<TestExecutor>;
|
|
47
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,17 @@
|
|
|
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 type { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
|
|
9
|
+
import { NormalizedUnitTestBuilderOptions } from '../../options';
|
|
10
|
+
import type { TestExecutor } from '../api';
|
|
11
|
+
export declare class KarmaExecutor implements TestExecutor {
|
|
12
|
+
private context;
|
|
13
|
+
private options;
|
|
14
|
+
constructor(context: BuilderContext, options: NormalizedUnitTestBuilderOptions);
|
|
15
|
+
execute(): AsyncIterable<BuilderOutput>;
|
|
16
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
17
|
+
}
|