UNPKG

@angular/build

Version:

Official build system for Angular

380 lines 16.8 kB
"use strict"; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { if (value !== null && value !== void 0) { if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected."); var dispose, inner; if (async) { if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined."); dispose = value[Symbol.asyncDispose]; } if (dispose === void 0) { if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined."); dispose = value[Symbol.dispose]; if (async) inner = dispose; } if (typeof dispose !== "function") throw new TypeError("Object not disposable."); if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } }; env.stack.push({ value: value, dispose: dispose, async: async }); } else if (async) { env.stack.push({ async: true }); } return value; }; var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) { return function (env) { function fail(e) { env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e; env.hasError = true; } var r, s = 0; function next() { while (r = env.stack.pop()) { try { if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next); if (r.dispose) { var result = r.dispose.call(r.value); if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); }); } else s |= 1; } catch (e) { fail(e); } } if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve(); if (env.hasError) throw env.error; } return next(); }; })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.execute = execute; const architect_1 = require("@angular-devkit/architect"); const node_assert_1 = __importDefault(require("node:assert")); const promises_1 = require("node:fs/promises"); const node_path_1 = __importDefault(require("node:path")); const virtual_module_plugin_1 = require("../../tools/esbuild/virtual-module-plugin"); const error_1 = require("../../utils/error"); const test_files_1 = require("../../utils/test-files"); const application_1 = require("../application"); const results_1 = require("../application/results"); const options_1 = require("./options"); const dependency_checker_1 = require("./runners/dependency-checker"); const test_discovery_1 = require("./test-discovery"); async function loadTestRunner(runnerName) { // Harden against directory traversal if (!/^[a-zA-Z0-9-]+$/.test(runnerName)) { throw new Error(`Invalid runner name "${runnerName}". Runner names can only contain alphanumeric characters and hyphens.`); } let runnerModule; try { runnerModule = await Promise.resolve(`${`./runners/${runnerName}/index`}`).then(s => __importStar(require(s))); } catch (e) { (0, error_1.assertIsError)(e); if (e.code === 'ERR_MODULE_NOT_FOUND') { throw new Error(`Unknown test runner "${runnerName}".`); } throw new Error(`Failed to load the '${runnerName}' test runner. The package may be corrupted or improperly installed.\n` + `Error: ${e.message}`); } const runner = runnerModule.default; if (!runner || typeof runner.getBuildOptions !== 'function' || typeof runner.createExecutor !== 'function') { throw new Error(`The loaded test runner '${runnerName}' does not appear to be a valid TestRunner implementation.`); } return runner; } function prepareBuildExtensions(virtualFiles, projectSourceRoot, extensions) { if (!virtualFiles) { return extensions; } extensions ??= {}; extensions.codePlugins ??= []; for (const [namespace, contents] of Object.entries(virtualFiles)) { extensions.codePlugins.push((0, virtual_module_plugin_1.createVirtualModulePlugin)({ namespace, loadContent: () => { return { contents, loader: 'js', resolveDir: projectSourceRoot, }; }, })); } return extensions; } async function* runBuildAndTest(executor, applicationBuildOptions, context, dumpDirectory, extensions) { let consecutiveErrorCount = 0; for await (const buildResult of (0, application_1.buildApplicationInternal)(applicationBuildOptions, context, extensions)) { if (buildResult.kind === results_1.ResultKind.Failure) { yield { success: false }; continue; } else if (buildResult.kind !== results_1.ResultKind.Full && buildResult.kind !== results_1.ResultKind.Incremental) { node_assert_1.default.fail('A full and/or incremental build result is required from the application builder.'); } (0, node_assert_1.default)(buildResult.files, 'Builder did not provide result files.'); if (dumpDirectory) { if (buildResult.kind === results_1.ResultKind.Full) { // Full build, so clean the directory await (0, promises_1.rm)(dumpDirectory, { recursive: true, force: true }); } else { // Incremental build, so delete removed files for (const file of buildResult.removed) { await (0, promises_1.rm)(node_path_1.default.join(dumpDirectory, file.path), { force: true }); } } await (0, test_files_1.writeTestFiles)(buildResult.files, dumpDirectory); context.logger.info(`Build output files successfully dumped to '${dumpDirectory}'.`); } // Pass the build artifacts to the executor try { yield* executor.execute(buildResult); // Successful execution resets the failure counter consecutiveErrorCount = 0; } catch (e) { (0, error_1.assertIsError)(e); context.logger.error(`An exception occurred during test execution:\n${e.stack ?? e.message}`); if (e instanceof AggregateError) { e.errors.forEach((inner) => { (0, error_1.assertIsError)(inner); context.logger.error(inner.stack ?? inner.message); }); } yield { success: false }; consecutiveErrorCount++; } if (consecutiveErrorCount >= 3) { context.logger.error('Test runner process has failed multiple times in a row. Please fix the configuration and restart the process.'); return; } } } /** * @experimental Direct usage of this function is considered experimental. */ async function* execute(options, context, extensions) { // Determine project name from builder context target const projectName = context.target?.project; if (!projectName) { context.logger.error(`The builder requires a target to be specified.`); return; } // Initialize the test runner and normalize options let runner; let normalizedOptions; try { normalizedOptions = await (0, options_1.normalizeOptions)(context, projectName, options); runner = await loadTestRunner(normalizedOptions.runnerName); await runner.validateDependencies?.(normalizedOptions); } catch (e) { (0, error_1.assertIsError)(e); if (e instanceof dependency_checker_1.MissingDependenciesError) { context.logger.error(e.message); } else { context.logger.error(`An exception occurred during initialization of the test runner:\n${e.stack ?? e.message}`); } yield { success: false }; return; } if (normalizedOptions.listTests) { const testFiles = await (0, test_discovery_1.findTests)(normalizedOptions.include, normalizedOptions.exclude ?? [], normalizedOptions.workspaceRoot, normalizedOptions.projectSourceRoot); context.logger.info('Discovered test files:'); for (const file of testFiles) { context.logger.info(` ${node_path_1.default.relative(normalizedOptions.workspaceRoot, file)}`); } yield { success: true }; return; } if (runner.isStandalone) { try { const env_1 = { stack: [], error: void 0, hasError: false }; try { const executor = __addDisposableResource(env_1, await runner.createExecutor(context, normalizedOptions, undefined), true); yield* executor.execute({ kind: results_1.ResultKind.Full, files: {}, }); } catch (e_1) { env_1.error = e_1; env_1.hasError = true; } finally { const result_1 = __disposeResources(env_1); if (result_1) await result_1; } } catch (e) { (0, error_1.assertIsError)(e); context.logger.error(`An exception occurred during standalone test execution:\n${e.stack ?? e.message}`); yield { success: false }; } return; } // Get base build options from the buildTarget let buildTargetOptions; try { const builderName = await context.getBuilderNameForTarget(normalizedOptions.buildTarget); if (builderName === '@angular/build:application') { buildTargetOptions = (await context.validateOptions(await context.getTargetOptions(normalizedOptions.buildTarget), builderName)); } else if (builderName === '@angular/build:ng-packagr') { const ngPackagrOptions = await context.validateOptions(await context.getTargetOptions(normalizedOptions.buildTarget), builderName); buildTargetOptions = await transformNgPackagrOptions(context, ngPackagrOptions, normalizedOptions.projectRoot); } else { context.logger.warn(`The 'buildTarget' is configured to use '${builderName}', which is not supported. ` + `The 'unit-test' builder is designed to work with '@angular/build:application' or '@angular/build:ng-packagr'. ` + 'Unexpected behavior or build failures may occur.'); buildTargetOptions = (await context.validateOptions(await context.getTargetOptions(normalizedOptions.buildTarget), builderName)); } } catch (e) { (0, error_1.assertIsError)(e); context.logger.error(`Could not load build target options for "${(0, architect_1.targetStringFromTarget)(normalizedOptions.buildTarget)}".\n` + `Please check your 'angular.json' configuration.\n` + `Error: ${e.message}`); yield { success: false }; return; } // Get runner-specific build options let runnerBuildOptions; let virtualFiles; let testEntryPointMappings; try { ({ buildOptions: runnerBuildOptions, virtualFiles, testEntryPointMappings, } = await runner.getBuildOptions(normalizedOptions, buildTargetOptions)); } catch (e) { (0, error_1.assertIsError)(e); context.logger.error(`An exception occurred while getting runner-specific build options:\n${e.stack ?? e.message}`); yield { success: false }; return; } try { const env_2 = { stack: [], error: void 0, hasError: false }; try { const executor = __addDisposableResource(env_2, await runner.createExecutor(context, normalizedOptions, testEntryPointMappings), true); const finalExtensions = prepareBuildExtensions(virtualFiles, normalizedOptions.projectSourceRoot, extensions); // Prepare and run the application build const applicationBuildOptions = { ...buildTargetOptions, ...runnerBuildOptions, watch: normalizedOptions.watch, progress: normalizedOptions.buildProgress ?? buildTargetOptions.progress, ...(normalizedOptions.tsConfig ? { tsConfig: normalizedOptions.tsConfig } : {}), }; const dumpDirectory = normalizedOptions.dumpVirtualFiles ? node_path_1.default.join(normalizedOptions.cacheOptions.path, 'unit-test', 'output-files') : undefined; yield* runBuildAndTest(executor, applicationBuildOptions, context, dumpDirectory, finalExtensions); } catch (e_2) { env_2.error = e_2; env_2.hasError = true; } finally { const result_2 = __disposeResources(env_2); if (result_2) await result_2; } } catch (e) { (0, error_1.assertIsError)(e); context.logger.error(`An exception occurred while creating the test executor:\n${e.stack ?? e.message}`); yield { success: false }; } } async function transformNgPackagrOptions(context, options, projectRoot) { const projectPath = options['project']; let ngPackagePath; if (projectPath) { if (typeof projectPath !== 'string') { throw new Error('ng-packagr builder options "project" property must be a string.'); } ngPackagePath = node_path_1.default.join(context.workspaceRoot, projectPath); } else { ngPackagePath = node_path_1.default.join(projectRoot, 'ng-package.json'); } let ngPackageJson; try { ngPackageJson = JSON.parse(await (0, promises_1.readFile)(ngPackagePath, 'utf-8')); } catch (e) { (0, error_1.assertIsError)(e); throw new Error(`Could not read ng-package.json at ${ngPackagePath}: ${e.message}`); } const lib = ngPackageJson['lib'] || {}; const styleIncludePaths = lib['styleIncludePaths'] || []; const assets = ngPackageJson['assets'] || []; const inlineStyleLanguage = ngPackageJson['inlineStyleLanguage']; return { stylePreprocessorOptions: styleIncludePaths.length ? { includePaths: styleIncludePaths } : undefined, assets: assets.length ? assets : undefined, inlineStyleLanguage: typeof inlineStyleLanguage === 'string' ? inlineStyleLanguage : undefined, }; } //# sourceMappingURL=builder.js.map