@angular/build
Version:
Official build system for Angular
380 lines • 16.8 kB
JavaScript
;
/**
* @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