@angular-devkit/build-angular 17.1.0-next.2 → 17.1.0-rc.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/builders.json +5 -0
- package/package.json +24 -20
- package/src/builders/app-shell/index.js +7 -0
- package/src/builders/application/build-action.d.ts +4 -3
- package/src/builders/application/build-action.js +8 -5
- package/src/builders/application/execute-build.js +10 -4
- package/src/builders/application/index.d.ts +20 -10
- package/src/builders/application/index.js +38 -26
- package/src/builders/application/options.d.ts +11 -3
- package/src/builders/application/options.js +42 -30
- package/src/builders/application/schema.d.ts +32 -2
- package/src/builders/application/schema.json +40 -2
- package/src/builders/browser-esbuild/index.js +8 -4
- package/src/builders/dev-server/vite-server.js +7 -13
- package/src/builders/jest/index.js +2 -2
- package/src/builders/prerender/index.js +7 -0
- package/src/builders/ssr-dev-server/index.js +17 -31
- package/src/builders/web-test-runner/builder-status-warnings.d.ts +11 -0
- package/src/builders/web-test-runner/builder-status-warnings.js +46 -0
- package/src/builders/web-test-runner/index.d.ts +10 -0
- package/src/builders/web-test-runner/index.js +151 -0
- package/src/builders/web-test-runner/jasmine_runner.js +88 -0
- package/src/builders/web-test-runner/options.d.ts +24 -0
- package/src/builders/web-test-runner/options.js +26 -0
- package/src/builders/web-test-runner/schema.d.ts +188 -0
- package/src/builders/web-test-runner/schema.js +15 -0
- package/src/builders/web-test-runner/schema.json +291 -0
- package/src/builders/web-test-runner/test_page.html +40 -0
- package/src/tools/esbuild/angular/angular-host.js +1 -1
- package/src/tools/esbuild/angular/compiler-plugin.js +10 -26
- package/src/tools/esbuild/angular/component-stylesheets.d.ts +3 -6
- package/src/tools/esbuild/angular/component-stylesheets.js +46 -60
- package/src/tools/esbuild/angular/jit-plugin-callbacks.js +2 -2
- package/src/tools/esbuild/bundler-context.d.ts +1 -1
- package/src/tools/esbuild/bundler-context.js +18 -2
- package/src/tools/esbuild/cache.d.ts +88 -0
- package/src/tools/esbuild/cache.js +92 -0
- package/src/tools/esbuild/compiler-plugin-options.js +1 -1
- package/src/tools/esbuild/index-html-generator.js +3 -1
- package/src/tools/esbuild/javascript-transformer-worker.d.ts +2 -2
- package/src/tools/esbuild/javascript-transformer-worker.js +12 -5
- package/src/tools/esbuild/javascript-transformer.d.ts +3 -1
- package/src/tools/esbuild/javascript-transformer.js +42 -17
- package/src/tools/esbuild/stylesheets/bundle-options.d.ts +1 -1
- package/src/tools/esbuild/stylesheets/sass-language.js +3 -12
- package/src/tools/esbuild/stylesheets/stylesheet-plugin-factory.js +9 -1
- package/src/tools/esbuild/utils.d.ts +2 -2
- package/src/tools/esbuild/utils.js +61 -74
- package/src/tools/sass/lexer.d.ts +0 -11
- package/src/tools/sass/lexer.js +1 -87
- package/src/utils/index-file/index-html-generator.js +15 -28
- package/src/utils/index.d.ts +1 -0
- package/src/utils/index.js +1 -0
- package/src/{builders/dev-server → utils}/load-proxy-config.js +2 -2
- package/src/{builders/jest → utils}/test-files.d.ts +1 -2
- package/src/{builders/jest → utils}/test-files.js +3 -3
- /package/src/{builders/dev-server → utils}/load-proxy-config.d.ts +0 -0
|
@@ -220,8 +220,41 @@
|
|
|
220
220
|
"default": []
|
|
221
221
|
},
|
|
222
222
|
"outputPath": {
|
|
223
|
-
"
|
|
224
|
-
"
|
|
223
|
+
"description": "Specify the output path relative to workspace root.",
|
|
224
|
+
"oneOf": [
|
|
225
|
+
{
|
|
226
|
+
"type": "object",
|
|
227
|
+
"properties": {
|
|
228
|
+
"base": {
|
|
229
|
+
"type": "string",
|
|
230
|
+
"description": "Specify the output path relative to workspace root."
|
|
231
|
+
},
|
|
232
|
+
"browser": {
|
|
233
|
+
"type": "string",
|
|
234
|
+
"pattern": "^[-\\w\\.]*$",
|
|
235
|
+
"default": "browser",
|
|
236
|
+
"description": "The output directory name of your browser build within the output path base. Defaults to 'browser'."
|
|
237
|
+
},
|
|
238
|
+
"server": {
|
|
239
|
+
"type": "string",
|
|
240
|
+
"pattern": "^[-\\w\\.]*$",
|
|
241
|
+
"default": "server",
|
|
242
|
+
"description": "The output directory name of your server build within the output path base. Defaults to 'server'."
|
|
243
|
+
},
|
|
244
|
+
"media": {
|
|
245
|
+
"type": "string",
|
|
246
|
+
"pattern": "^[-\\w\\.]+$",
|
|
247
|
+
"default": "media",
|
|
248
|
+
"description": "The output directory name of your media files within the output browser directory. Defaults to 'media'."
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
"required": ["base"],
|
|
252
|
+
"additionalProperties": false
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"type": "string"
|
|
256
|
+
}
|
|
257
|
+
]
|
|
225
258
|
},
|
|
226
259
|
"aot": {
|
|
227
260
|
"type": "boolean",
|
|
@@ -383,6 +416,11 @@
|
|
|
383
416
|
"minLength": 1,
|
|
384
417
|
"default": "index.html",
|
|
385
418
|
"description": "The output path of the application's generated HTML index file. The full provided path will be used and will be considered relative to the application's configured output path."
|
|
419
|
+
},
|
|
420
|
+
"preloadInitial": {
|
|
421
|
+
"type": "boolean",
|
|
422
|
+
"default": true,
|
|
423
|
+
"description": "Generates 'preload', 'modulepreload', and 'preconnect' link elements for initial application files and resources."
|
|
386
424
|
}
|
|
387
425
|
},
|
|
388
426
|
"required": ["input"]
|
|
@@ -31,13 +31,13 @@ async function* buildEsbuildBrowser(userOptions, context, infrastructureSettings
|
|
|
31
31
|
(0, builder_status_warnings_1.logBuilderStatusWarnings)(userOptions, context);
|
|
32
32
|
const normalizedOptions = normalizeOptions(userOptions);
|
|
33
33
|
const { deleteOutputPath, outputPath } = normalizedOptions;
|
|
34
|
-
const fullOutputPath = node_path_1.default.join(context.workspaceRoot, outputPath);
|
|
34
|
+
const fullOutputPath = node_path_1.default.join(context.workspaceRoot, outputPath.base);
|
|
35
35
|
if (deleteOutputPath && infrastructureSettings?.write !== false) {
|
|
36
|
-
await (0, utils_2.deleteOutputDir)(context.workspaceRoot, outputPath);
|
|
36
|
+
await (0, utils_2.deleteOutputDir)(context.workspaceRoot, outputPath.base);
|
|
37
37
|
}
|
|
38
38
|
for await (const result of (0, application_1.buildApplicationInternal)(normalizedOptions, context, {
|
|
39
39
|
write: false,
|
|
40
|
-
}, plugins)) {
|
|
40
|
+
}, plugins && { codePlugins: plugins })) {
|
|
41
41
|
if (infrastructureSettings?.write !== false && result.outputFiles) {
|
|
42
42
|
// Write output files
|
|
43
43
|
await writeResultFiles(result.outputFiles, result.assetFiles, fullOutputPath);
|
|
@@ -55,11 +55,15 @@ async function* buildEsbuildBrowser(userOptions, context, infrastructureSettings
|
|
|
55
55
|
}
|
|
56
56
|
exports.buildEsbuildBrowser = buildEsbuildBrowser;
|
|
57
57
|
function normalizeOptions(options) {
|
|
58
|
-
const { main: browser, ngswConfigPath, serviceWorker, polyfills, ...otherOptions } = options;
|
|
58
|
+
const { main: browser, outputPath, ngswConfigPath, serviceWorker, polyfills, ...otherOptions } = options;
|
|
59
59
|
return {
|
|
60
60
|
browser,
|
|
61
61
|
serviceWorker: serviceWorker ? ngswConfigPath : false,
|
|
62
62
|
polyfills: typeof polyfills === 'string' ? [polyfills] : polyfills,
|
|
63
|
+
outputPath: {
|
|
64
|
+
base: outputPath,
|
|
65
|
+
browser: '',
|
|
66
|
+
},
|
|
63
67
|
...otherOptions,
|
|
64
68
|
};
|
|
65
69
|
}
|
|
@@ -51,7 +51,6 @@ const supported_browsers_1 = require("../../utils/supported-browsers");
|
|
|
51
51
|
const webpack_browser_config_1 = require("../../utils/webpack-browser-config");
|
|
52
52
|
const application_1 = require("../application");
|
|
53
53
|
const browser_esbuild_1 = require("../browser-esbuild");
|
|
54
|
-
const load_proxy_config_1 = require("./load-proxy-config");
|
|
55
54
|
// eslint-disable-next-line max-lines-per-function
|
|
56
55
|
async function* serveWithVite(serverOptions, builderName, context, transformers, extensions) {
|
|
57
56
|
// Get the browser configuration from the target name.
|
|
@@ -97,7 +96,7 @@ async function* serveWithVite(serverOptions, builderName, context, transformers,
|
|
|
97
96
|
// Always enable JIT linking to support applications built with and without AOT.
|
|
98
97
|
// In a development environment the additional scope information does not
|
|
99
98
|
// have a negative effect unlike production where final output size is relevant.
|
|
100
|
-
{ sourcemap: true, jit: true, thirdPartySourcemaps }, 1
|
|
99
|
+
{ sourcemap: true, jit: true, thirdPartySourcemaps }, 1);
|
|
101
100
|
// Extract output index from options
|
|
102
101
|
// TODO: Provide this info from the build results
|
|
103
102
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -122,14 +121,10 @@ async function* serveWithVite(serverOptions, builderName, context, transformers,
|
|
|
122
121
|
deferred?.();
|
|
123
122
|
});
|
|
124
123
|
const build = builderName === '@angular-devkit/build-angular:browser-esbuild'
|
|
125
|
-
? browser_esbuild_1.buildEsbuildBrowser
|
|
126
|
-
: application_1.buildApplicationInternal;
|
|
124
|
+
? browser_esbuild_1.buildEsbuildBrowser.bind(undefined, browserOptions, context, { write: false }, extensions?.buildPlugins)
|
|
125
|
+
: application_1.buildApplicationInternal.bind(undefined, browserOptions, context, { write: false }, { codePlugins: extensions?.buildPlugins });
|
|
127
126
|
// TODO: Switch this to an architect schedule call when infrastructure settings are supported
|
|
128
|
-
for await (const result of build(
|
|
129
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
130
|
-
browserOptions, context, {
|
|
131
|
-
write: false,
|
|
132
|
-
}, extensions?.buildPlugins)) {
|
|
127
|
+
for await (const result of build()) {
|
|
133
128
|
(0, node_assert_1.default)(result.outputFiles, 'Builder did not provide result files.');
|
|
134
129
|
// If build failed, nothing to serve
|
|
135
130
|
if (!result.success) {
|
|
@@ -331,7 +326,7 @@ function analyzeResultFiles(normalizePath, htmlIndexPath, resultFiles, generated
|
|
|
331
326
|
}
|
|
332
327
|
// eslint-disable-next-line max-lines-per-function
|
|
333
328
|
async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks, externalMetadata, ssr, prebundleTransformer, target, prebundleLoaderExtensions, extensionMiddleware, indexHtmlTransformer, thirdPartySourcemaps = false) {
|
|
334
|
-
const proxy = await (0,
|
|
329
|
+
const proxy = await (0, utils_2.loadProxyConfiguration)(serverOptions.workspaceRoot, serverOptions.proxyConfig, true);
|
|
335
330
|
// dynamically import Vite for ESM compatibility
|
|
336
331
|
const { normalizePath } = await (0, load_esm_1.loadEsmModule)('vite');
|
|
337
332
|
// Path will not exist on disk and only used to provide separate path for Vite requests
|
|
@@ -532,11 +527,9 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
|
|
|
532
527
|
return;
|
|
533
528
|
}
|
|
534
529
|
transformIndexHtmlAndAddHeaders(url, rawHtml, res, next, async (html) => {
|
|
535
|
-
const protocol = serverOptions.ssl ? 'https' : 'http';
|
|
536
|
-
const route = `${protocol}://${req.headers.host}${req.originalUrl}`;
|
|
537
530
|
const { content } = await (0, render_page_1.renderPage)({
|
|
538
531
|
document: html,
|
|
539
|
-
route,
|
|
532
|
+
route: new URL(req.originalUrl ?? '/', server.resolvedUrls?.local[0]).toString(),
|
|
540
533
|
serverContext: 'ssr',
|
|
541
534
|
loadBundle: (uri) =>
|
|
542
535
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -622,6 +615,7 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
|
|
|
622
615
|
}
|
|
623
616
|
else {
|
|
624
617
|
const { default: basicSslPlugin } = await Promise.resolve().then(() => __importStar(require('@vitejs/plugin-basic-ssl')));
|
|
618
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
625
619
|
configuration.plugins ??= [];
|
|
626
620
|
configuration.plugins.push(basicSslPlugin());
|
|
627
621
|
}
|
|
@@ -35,10 +35,10 @@ const child_process_1 = require("child_process");
|
|
|
35
35
|
const path = __importStar(require("path"));
|
|
36
36
|
const util_1 = require("util");
|
|
37
37
|
const color_1 = require("../../utils/color");
|
|
38
|
+
const test_files_1 = require("../../utils/test-files");
|
|
38
39
|
const application_1 = require("../application");
|
|
39
40
|
const schema_1 = require("../browser-esbuild/schema");
|
|
40
41
|
const options_1 = require("./options");
|
|
41
|
-
const test_files_1 = require("./test-files");
|
|
42
42
|
const execFile = (0, util_1.promisify)(child_process_1.execFile);
|
|
43
43
|
/** Main execution function for the Jest builder. */
|
|
44
44
|
exports.default = (0, architect_1.createBuilder)(async (schema, context) => {
|
|
@@ -66,7 +66,7 @@ exports.default = (0, architect_1.createBuilder)(async (schema, context) => {
|
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
68
|
// Build all the test files.
|
|
69
|
-
const testFiles = await (0, test_files_1.findTestFiles)(options, context.workspaceRoot);
|
|
69
|
+
const testFiles = await (0, test_files_1.findTestFiles)(options.include, options.exclude, context.workspaceRoot);
|
|
70
70
|
const jestGlobal = path.join(__dirname, 'jest-global.mjs');
|
|
71
71
|
const initTestBed = path.join(__dirname, 'init-test-bed.mjs');
|
|
72
72
|
const buildResult = await build(context, {
|
|
@@ -93,6 +93,13 @@ async function _scheduleBuilds(options, context) {
|
|
|
93
93
|
serviceWorker: false,
|
|
94
94
|
// todo: handle service worker augmentation
|
|
95
95
|
});
|
|
96
|
+
if (browserTargetRun.info.builderName === '@angular-devkit/build-angular:application') {
|
|
97
|
+
return {
|
|
98
|
+
success: false,
|
|
99
|
+
error: '"@angular-devkit/build-angular:application" has built-in prerendering capabilities. ' +
|
|
100
|
+
'The "prerender" option should be used instead.',
|
|
101
|
+
};
|
|
102
|
+
}
|
|
96
103
|
const serverTargetRun = await context.scheduleTarget(serverTarget, {
|
|
97
104
|
watch: false,
|
|
98
105
|
});
|
|
@@ -36,8 +36,8 @@ const core_1 = require("@angular-devkit/core");
|
|
|
36
36
|
const path_1 = require("path");
|
|
37
37
|
const rxjs_1 = require("rxjs");
|
|
38
38
|
const url = __importStar(require("url"));
|
|
39
|
-
const
|
|
40
|
-
const
|
|
39
|
+
const utils_1 = require("../../utils");
|
|
40
|
+
const utils_2 = require("./utils");
|
|
41
41
|
/** Log messages to ignore and not rely to the logger */
|
|
42
42
|
const IGNORED_STDOUT_MESSAGES = [
|
|
43
43
|
'server listening on',
|
|
@@ -78,7 +78,7 @@ function execute(options, context) {
|
|
|
78
78
|
DON'T USE IT FOR PRODUCTION!
|
|
79
79
|
****************************************************************************************
|
|
80
80
|
`);
|
|
81
|
-
return (0, rxjs_1.zip)(browserTargetRun, serverTargetRun, (0,
|
|
81
|
+
return (0, rxjs_1.zip)(browserTargetRun, serverTargetRun, (0, utils_2.getAvailablePort)()).pipe((0, rxjs_1.switchMap)(([br, sr, nodeServerPort]) => {
|
|
82
82
|
return (0, rxjs_1.combineLatest)([br.output, sr.output]).pipe(
|
|
83
83
|
// This is needed so that if both server and browser emit close to each other
|
|
84
84
|
// we only emit once. This typically happens on the first build.
|
|
@@ -101,7 +101,7 @@ function execute(options, context) {
|
|
|
101
101
|
context.logger.info('\nCompiled successfully.');
|
|
102
102
|
}
|
|
103
103
|
}), (0, rxjs_1.debounce)(([builderOutput]) => builderOutput.success && !options.inspect
|
|
104
|
-
? (0,
|
|
104
|
+
? (0, utils_2.waitUntilServerIsListening)(nodeServerPort)
|
|
105
105
|
: rxjs_1.EMPTY), (0, rxjs_1.finalize)(() => {
|
|
106
106
|
void br.stop();
|
|
107
107
|
void sr.stop();
|
|
@@ -162,7 +162,7 @@ function startNodeServer(serverOutput, port, logger, inspectMode = false) {
|
|
|
162
162
|
args.unshift('--inspect-brk');
|
|
163
163
|
}
|
|
164
164
|
return (0, rxjs_1.of)(null).pipe((0, rxjs_1.delay)(0), // Avoid EADDRINUSE error since it will cause the kill event to be finish.
|
|
165
|
-
(0, rxjs_1.switchMap)(() => (0,
|
|
165
|
+
(0, rxjs_1.switchMap)(() => (0, utils_2.spawnAsObservable)('node', args, { env, shell: true })), (0, rxjs_1.tap)((res) => log({ stderr: res.stderr, stdout: res.stdout }, logger)), (0, rxjs_1.ignoreElements)(),
|
|
166
166
|
// Emit a signal after the process has been started
|
|
167
167
|
(0, rxjs_1.startWith)(undefined));
|
|
168
168
|
}
|
|
@@ -171,7 +171,7 @@ async function initBrowserSync(browserSyncInstance, nodeServerPort, options, con
|
|
|
171
171
|
return browserSyncInstance;
|
|
172
172
|
}
|
|
173
173
|
const { port: browserSyncPort, open, host, publicHost, proxyConfig } = options;
|
|
174
|
-
const bsPort = browserSyncPort || (await (0,
|
|
174
|
+
const bsPort = browserSyncPort || (await (0, utils_2.getAvailablePort)());
|
|
175
175
|
const bsOptions = {
|
|
176
176
|
proxy: {
|
|
177
177
|
target: `localhost:${nodeServerPort}`,
|
|
@@ -283,33 +283,19 @@ function getSslConfig(root, options) {
|
|
|
283
283
|
return ssl;
|
|
284
284
|
}
|
|
285
285
|
async function getProxyConfig(root, proxyConfig) {
|
|
286
|
-
const
|
|
287
|
-
let proxySettings;
|
|
288
|
-
try {
|
|
289
|
-
proxySettings = require(proxyPath);
|
|
290
|
-
}
|
|
291
|
-
catch (error) {
|
|
292
|
-
(0, error_1.assertIsError)(error);
|
|
293
|
-
if (error.code === 'MODULE_NOT_FOUND') {
|
|
294
|
-
throw new Error(`Proxy config file ${proxyPath} does not exist.`);
|
|
295
|
-
}
|
|
296
|
-
throw error;
|
|
297
|
-
}
|
|
298
|
-
const proxies = Array.isArray(proxySettings) ? proxySettings : [proxySettings];
|
|
286
|
+
const proxy = await (0, utils_1.loadProxyConfiguration)(root, proxyConfig, false);
|
|
299
287
|
const createdProxies = [];
|
|
300
288
|
const { createProxyMiddleware } = await Promise.resolve().then(() => __importStar(require('http-proxy-middleware')));
|
|
301
|
-
for (const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
createProxyMiddleware(key, context));
|
|
312
|
-
}
|
|
289
|
+
for (const [key, context] of Object.entries(proxy)) {
|
|
290
|
+
if (typeof key === 'string') {
|
|
291
|
+
createdProxies.push(createProxyMiddleware(key.replace(/^\*$/, '**').replace(/\/\*$/, ''),
|
|
292
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
293
|
+
context));
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
createdProxies.push(
|
|
297
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
298
|
+
createProxyMiddleware(key, context));
|
|
313
299
|
}
|
|
314
300
|
}
|
|
315
301
|
return createdProxies;
|
|
@@ -0,0 +1,11 @@
|
|
|
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.io/license
|
|
7
|
+
*/
|
|
8
|
+
import { BuilderContext } from '@angular-devkit/architect';
|
|
9
|
+
import { Schema as WtrBuilderOptions } from './schema';
|
|
10
|
+
/** Logs a warning for any unsupported options specified. */
|
|
11
|
+
export declare function logBuilderStatusWarnings(options: WtrBuilderOptions, ctx: BuilderContext): void;
|
|
@@ -0,0 +1,46 @@
|
|
|
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.io/license
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.logBuilderStatusWarnings = void 0;
|
|
11
|
+
const UNSUPPORTED_OPTIONS = [
|
|
12
|
+
'main',
|
|
13
|
+
'assets',
|
|
14
|
+
'scripts',
|
|
15
|
+
'styles',
|
|
16
|
+
'inlineStyleLanguage',
|
|
17
|
+
'stylePreprocessorOptions',
|
|
18
|
+
'sourceMap',
|
|
19
|
+
'progress',
|
|
20
|
+
'poll',
|
|
21
|
+
'preserveSymlinks',
|
|
22
|
+
'browsers',
|
|
23
|
+
'codeCoverage',
|
|
24
|
+
'codeCoverageExclude',
|
|
25
|
+
'fileReplacements',
|
|
26
|
+
'webWorkerTsConfig',
|
|
27
|
+
'watch',
|
|
28
|
+
];
|
|
29
|
+
/** Logs a warning for any unsupported options specified. */
|
|
30
|
+
function logBuilderStatusWarnings(options, ctx) {
|
|
31
|
+
// Validate supported options
|
|
32
|
+
for (const unsupportedOption of UNSUPPORTED_OPTIONS) {
|
|
33
|
+
const value = options[unsupportedOption];
|
|
34
|
+
if (value === undefined || value === false) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (Array.isArray(value) && value.length === 0) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (typeof value === 'object' && Object.keys(value).length === 0) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
ctx.logger.warn(`The '${unsupportedOption}' option is not yet supported by this builder.`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exports.logBuilderStatusWarnings = logBuilderStatusWarnings;
|
|
@@ -0,0 +1,10 @@
|
|
|
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.io/license
|
|
7
|
+
*/
|
|
8
|
+
import { Schema } from './schema';
|
|
9
|
+
declare const _default: import("../../../../architect/src/internal").Builder<Schema & import("../../../../core/src").JsonObject>;
|
|
10
|
+
export default _default;
|
|
@@ -0,0 +1,151 @@
|
|
|
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.io/license
|
|
8
|
+
*/
|
|
9
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
const architect_1 = require("@angular-devkit/architect");
|
|
14
|
+
const node_fs_1 = require("node:fs");
|
|
15
|
+
const node_module_1 = require("node:module");
|
|
16
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
17
|
+
const test_files_1 = require("../../utils/test-files");
|
|
18
|
+
const application_1 = require("../application");
|
|
19
|
+
const schema_1 = require("../browser-esbuild/schema");
|
|
20
|
+
const builder_status_warnings_1 = require("./builder-status-warnings");
|
|
21
|
+
const options_1 = require("./options");
|
|
22
|
+
exports.default = (0, architect_1.createBuilder)(async (schema, ctx) => {
|
|
23
|
+
ctx.logger.warn('NOTE: The Web Test Runner builder is currently EXPERIMENTAL and not ready for production use.');
|
|
24
|
+
(0, builder_status_warnings_1.logBuilderStatusWarnings)(schema, ctx);
|
|
25
|
+
// Dynamic import `@web/test-runner` from the user's workspace. As an optional peer dep, it may not be installed
|
|
26
|
+
// and may not be resolvable from `@angular-devkit/build-angular`.
|
|
27
|
+
const require = (0, node_module_1.createRequire)(`${ctx.workspaceRoot}/`);
|
|
28
|
+
let wtr;
|
|
29
|
+
try {
|
|
30
|
+
wtr = require('@web/test-runner');
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return {
|
|
34
|
+
success: false,
|
|
35
|
+
// TODO(dgp1130): Display a more accurate message for non-NPM users.
|
|
36
|
+
error: 'Web Test Runner is not installed, most likely you need to run `npm install @web/test-runner --save-dev` in your project.',
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const options = (0, options_1.normalizeOptions)(schema);
|
|
40
|
+
const testDir = 'dist/test-out';
|
|
41
|
+
// Parallelize startup work.
|
|
42
|
+
const [testFiles] = await Promise.all([
|
|
43
|
+
// Glob for files to test.
|
|
44
|
+
(0, test_files_1.findTestFiles)(options.include, options.exclude, ctx.workspaceRoot).then((files) => Array.from(files).map((file) => node_path_1.default.relative(process.cwd(), file))),
|
|
45
|
+
// Clean build output path.
|
|
46
|
+
node_fs_1.promises.rm(testDir, { recursive: true, force: true }),
|
|
47
|
+
]);
|
|
48
|
+
// Build the tests and abort on any build failure.
|
|
49
|
+
const buildOutput = await buildTests(testFiles, testDir, options, ctx);
|
|
50
|
+
if (!buildOutput.success) {
|
|
51
|
+
return buildOutput;
|
|
52
|
+
}
|
|
53
|
+
// Run the built tests.
|
|
54
|
+
return await runTests(wtr, `${testDir}/browser`, options);
|
|
55
|
+
});
|
|
56
|
+
/** Build all the given test files and write the result to the given output path. */
|
|
57
|
+
async function buildTests(testFiles, outputPath, options, ctx) {
|
|
58
|
+
const entryPoints = new Set([
|
|
59
|
+
...testFiles,
|
|
60
|
+
'jasmine-core/lib/jasmine-core/jasmine.js',
|
|
61
|
+
'@angular-devkit/build-angular/src/builders/web-test-runner/jasmine_runner.js',
|
|
62
|
+
]);
|
|
63
|
+
// Extract `zone.js/testing` to a separate entry point because it needs to be loaded after Jasmine.
|
|
64
|
+
const [polyfills, hasZoneTesting] = extractZoneTesting(options.polyfills);
|
|
65
|
+
if (hasZoneTesting) {
|
|
66
|
+
entryPoints.add('zone.js/testing');
|
|
67
|
+
}
|
|
68
|
+
// Build tests with `application` builder, using test files as entry points.
|
|
69
|
+
// Also bundle in Jasmine and the Jasmine runner script, which need to share chunked dependencies.
|
|
70
|
+
const buildOutput = await first((0, application_1.buildApplicationInternal)({
|
|
71
|
+
entryPoints,
|
|
72
|
+
tsConfig: options.tsConfig,
|
|
73
|
+
outputPath,
|
|
74
|
+
aot: false,
|
|
75
|
+
index: false,
|
|
76
|
+
outputHashing: schema_1.OutputHashing.None,
|
|
77
|
+
optimization: false,
|
|
78
|
+
externalDependencies: [
|
|
79
|
+
// Resolved by `@web/test-runner` at runtime with dynamically generated code.
|
|
80
|
+
'@web/test-runner-core',
|
|
81
|
+
],
|
|
82
|
+
sourceMap: {
|
|
83
|
+
scripts: true,
|
|
84
|
+
styles: true,
|
|
85
|
+
vendor: true,
|
|
86
|
+
},
|
|
87
|
+
polyfills,
|
|
88
|
+
}, ctx));
|
|
89
|
+
return buildOutput;
|
|
90
|
+
}
|
|
91
|
+
function extractZoneTesting(polyfills) {
|
|
92
|
+
const polyfillsWithoutZoneTesting = polyfills.filter((polyfill) => polyfill !== 'zone.js/testing');
|
|
93
|
+
const hasZoneTesting = polyfills.length !== polyfillsWithoutZoneTesting.length;
|
|
94
|
+
return [polyfillsWithoutZoneTesting, hasZoneTesting];
|
|
95
|
+
}
|
|
96
|
+
/** Run Web Test Runner on the given directory of bundled JavaScript tests. */
|
|
97
|
+
async function runTests(wtr, testDir, options) {
|
|
98
|
+
const testPagePath = node_path_1.default.resolve(__dirname, 'test_page.html');
|
|
99
|
+
const testPage = await node_fs_1.promises.readFile(testPagePath, 'utf8');
|
|
100
|
+
const runner = await wtr.startTestRunner({
|
|
101
|
+
config: {
|
|
102
|
+
rootDir: testDir,
|
|
103
|
+
files: [
|
|
104
|
+
`${testDir}/**/*.js`,
|
|
105
|
+
`!${testDir}/polyfills.js`,
|
|
106
|
+
`!${testDir}/chunk-*.js`,
|
|
107
|
+
`!${testDir}/jasmine.js`,
|
|
108
|
+
`!${testDir}/jasmine_runner.js`,
|
|
109
|
+
`!${testDir}/testing.js`, // `zone.js/testing`
|
|
110
|
+
],
|
|
111
|
+
testFramework: {
|
|
112
|
+
config: {
|
|
113
|
+
defaultTimeoutInterval: 5000,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
nodeResolve: true,
|
|
117
|
+
port: 9876,
|
|
118
|
+
watch: options.watch ?? false,
|
|
119
|
+
testRunnerHtml: (_testFramework, _config) => testPage,
|
|
120
|
+
},
|
|
121
|
+
readCliArgs: false,
|
|
122
|
+
readFileConfig: false,
|
|
123
|
+
autoExitProcess: false,
|
|
124
|
+
});
|
|
125
|
+
if (!runner) {
|
|
126
|
+
throw new Error('Failed to start Web Test Runner.');
|
|
127
|
+
}
|
|
128
|
+
// Wait for the tests to complete and stop the runner.
|
|
129
|
+
const passed = (await once(runner, 'finished'));
|
|
130
|
+
await runner.stop();
|
|
131
|
+
// No need to return error messages because Web Test Runner already printed them to the console.
|
|
132
|
+
return { success: passed };
|
|
133
|
+
}
|
|
134
|
+
/** Returns the first item yielded by the given generator and cancels the execution. */
|
|
135
|
+
async function first(generator) {
|
|
136
|
+
for await (const value of generator) {
|
|
137
|
+
return value;
|
|
138
|
+
}
|
|
139
|
+
throw new Error('Expected generator to emit at least once.');
|
|
140
|
+
}
|
|
141
|
+
/** Listens for a single emission of an event and returns the value emitted. */
|
|
142
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
143
|
+
function once(emitter, event) {
|
|
144
|
+
return new Promise((resolve) => {
|
|
145
|
+
const onEmit = (arg) => {
|
|
146
|
+
emitter.off(event, onEmit);
|
|
147
|
+
resolve(arg);
|
|
148
|
+
};
|
|
149
|
+
emitter.on(event, onEmit);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
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.io/license
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { getTestBed } from '@angular/core/testing';
|
|
10
|
+
import {
|
|
11
|
+
BrowserDynamicTestingModule,
|
|
12
|
+
platformBrowserDynamicTesting,
|
|
13
|
+
} from '@angular/platform-browser-dynamic/testing';
|
|
14
|
+
import {
|
|
15
|
+
getConfig,
|
|
16
|
+
sessionFailed,
|
|
17
|
+
sessionFinished,
|
|
18
|
+
sessionStarted,
|
|
19
|
+
} from '@web/test-runner-core/browser/session.js';
|
|
20
|
+
|
|
21
|
+
/** Executes Angular Jasmine tests in the given environment and reports the results to Web Test Runner. */
|
|
22
|
+
export async function runJasmineTests(jasmineEnv) {
|
|
23
|
+
const allSpecs = [];
|
|
24
|
+
const failedSpecs = [];
|
|
25
|
+
|
|
26
|
+
jasmineEnv.addReporter({
|
|
27
|
+
specDone(result) {
|
|
28
|
+
const expectations = [...result.passedExpectations, ...result.failedExpectations];
|
|
29
|
+
allSpecs.push(...expectations.map((e) => ({ name: e.fullName, passed: e.passed })));
|
|
30
|
+
|
|
31
|
+
for (const e of result.failedExpectations) {
|
|
32
|
+
const message = `${result.fullName}\n${e.message}\n${e.stack}`;
|
|
33
|
+
// eslint-disable-next-line no-console
|
|
34
|
+
console.error(message);
|
|
35
|
+
failedSpecs.push({
|
|
36
|
+
message,
|
|
37
|
+
name: e.fullName,
|
|
38
|
+
stack: e.stack,
|
|
39
|
+
expected: e.expected,
|
|
40
|
+
actual: e.actual,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
async jasmineDone(result) {
|
|
46
|
+
// eslint-disable-next-line no-console
|
|
47
|
+
console.log(`Tests ${result.overallStatus}!`);
|
|
48
|
+
await sessionFinished({
|
|
49
|
+
passed: result.overallStatus === 'passed',
|
|
50
|
+
errors: failedSpecs,
|
|
51
|
+
testResults: {
|
|
52
|
+
name: '',
|
|
53
|
+
suites: [],
|
|
54
|
+
tests: allSpecs,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
await sessionStarted();
|
|
61
|
+
|
|
62
|
+
// Web Test Runner uses a different HTML page for every test, so we only get one `testFile` for the single `*.js` file we need to execute.
|
|
63
|
+
const { testFile, testFrameworkConfig } = await getConfig();
|
|
64
|
+
const config = { defaultTimeoutInterval: 60_000, ...(testFrameworkConfig ?? {}) };
|
|
65
|
+
|
|
66
|
+
// eslint-disable-next-line no-undef
|
|
67
|
+
jasmine.DEFAULT_TIMEOUT_INTERVAL = config.defaultTimeoutInterval;
|
|
68
|
+
|
|
69
|
+
// Initialize `TestBed` automatically for users. This assumes we already evaluated `zone.js/testing`.
|
|
70
|
+
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
|
|
71
|
+
errorOnUnknownElements: true,
|
|
72
|
+
errorOnUnknownProperties: true,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Load the test file and evaluate it.
|
|
76
|
+
try {
|
|
77
|
+
// eslint-disable-next-line no-undef
|
|
78
|
+
await import(new URL(testFile, document.baseURI).href);
|
|
79
|
+
|
|
80
|
+
// Execute the test functions.
|
|
81
|
+
// eslint-disable-next-line no-undef
|
|
82
|
+
jasmineEnv.execute();
|
|
83
|
+
} catch (err) {
|
|
84
|
+
// eslint-disable-next-line no-console
|
|
85
|
+
console.error(err);
|
|
86
|
+
await sessionFailed(err);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
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.io/license
|
|
7
|
+
*/
|
|
8
|
+
import { Schema as WtrBuilderSchema } from './schema';
|
|
9
|
+
/**
|
|
10
|
+
* Options supported for the Web Test Runner builder. The schema is an approximate
|
|
11
|
+
* representation of the options type, but this is a more precise version.
|
|
12
|
+
*/
|
|
13
|
+
export type WtrBuilderOptions = Overwrite<WtrBuilderSchema, {
|
|
14
|
+
include: string[];
|
|
15
|
+
exclude: string[];
|
|
16
|
+
polyfills: string[];
|
|
17
|
+
}>;
|
|
18
|
+
type Overwrite<Obj extends {}, Overrides extends {}> = Omit<Obj, keyof Overrides> & Overrides;
|
|
19
|
+
/**
|
|
20
|
+
* Normalizes input options validated by the schema to a more precise and useful
|
|
21
|
+
* options type in {@link WtrBuilderOptions}.
|
|
22
|
+
*/
|
|
23
|
+
export declare function normalizeOptions(schema: WtrBuilderSchema): WtrBuilderOptions;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
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.io/license
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.normalizeOptions = void 0;
|
|
11
|
+
/**
|
|
12
|
+
* Normalizes input options validated by the schema to a more precise and useful
|
|
13
|
+
* options type in {@link WtrBuilderOptions}.
|
|
14
|
+
*/
|
|
15
|
+
function normalizeOptions(schema) {
|
|
16
|
+
return {
|
|
17
|
+
...schema,
|
|
18
|
+
// Options with default values can't actually be null, even if the types say so.
|
|
19
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
20
|
+
include: schema.include,
|
|
21
|
+
exclude: schema.exclude,
|
|
22
|
+
/* eslint-enable @typescript-eslint/no-non-null-assertion */
|
|
23
|
+
polyfills: typeof schema.polyfills === 'string' ? [schema.polyfills] : schema.polyfills ?? [],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
exports.normalizeOptions = normalizeOptions;
|