@nx/angular
Version:
433 lines (432 loc) • 20.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createNodesV2 = void 0;
const tslib_1 = require("tslib");
const devkit_1 = require("@nx/devkit");
const calculate_hash_for_create_nodes_1 = require("@nx/devkit/src/utils/calculate-hash-for-create-nodes");
const get_named_inputs_1 = require("@nx/devkit/src/utils/get-named-inputs");
const js_1 = require("@nx/js");
const node_fs_1 = require("node:fs");
const node_path_1 = require("node:path");
const posix = tslib_1.__importStar(require("node:path/posix"));
const devkit_internals_1 = require("nx/src/devkit-internals");
const cache_directory_1 = require("nx/src/utils/cache-directory");
const knownExecutors = {
appShell: new Set(['@angular-devkit/build-angular:app-shell']),
build: new Set([
'@angular-devkit/build-angular:application',
'@angular/build:application',
'@angular-devkit/build-angular:browser-esbuild',
'@angular-devkit/build-angular:browser',
'@angular-devkit/build-angular:ng-packagr',
'@angular/build:ng-packagr',
]),
devServer: new Set([
'@angular-devkit/build-angular:dev-server',
'@angular/build:dev-server',
]),
extractI18n: new Set([
'@angular-devkit/build-angular:extract-i18n',
'@angular/build:extract-i18n',
]),
prerender: new Set([
'@angular-devkit/build-angular:prerender',
'@nguniversal/builders:prerender',
]),
server: new Set(['@angular-devkit/build-angular:server']),
serveSsr: new Set([
'@angular-devkit/build-angular:ssr-dev-server',
'@nguniversal/builders:ssr-dev-server',
]),
test: new Set([
'@angular-devkit/build-angular:karma',
'@angular/build:karma',
]),
};
const pmc = (0, devkit_1.getPackageManagerCommand)();
function readProjectsCache(cachePath) {
return (0, node_fs_1.existsSync)(cachePath) ? (0, devkit_1.readJsonFile)(cachePath) : {};
}
function writeProjectsToCache(cachePath, results) {
(0, devkit_1.writeJsonFile)(cachePath, results);
}
exports.createNodesV2 = [
'**/angular.json',
async (configFiles, options, context) => {
const optionsHash = (0, devkit_internals_1.hashObject)(options);
const cachePath = (0, node_path_1.join)(cache_directory_1.workspaceDataDirectory, `angular-${optionsHash}.hash`);
const projectsCache = readProjectsCache(cachePath);
try {
return await (0, devkit_1.createNodesFromFiles)((configFile, options, context) => createNodesInternal(configFile, options, context, projectsCache), configFiles, options, context);
}
finally {
writeProjectsToCache(cachePath, projectsCache);
}
},
];
async function createNodesInternal(configFilePath, options, context, projectsCache) {
const angularWorkspaceRoot = (0, node_path_1.dirname)(configFilePath);
// Do not create a project if package.json isn't there
const siblingFiles = (0, node_fs_1.readdirSync)((0, node_path_1.join)(context.workspaceRoot, angularWorkspaceRoot));
if (!siblingFiles.includes('package.json')) {
return {};
}
const hash = await (0, calculate_hash_for_create_nodes_1.calculateHashForCreateNodes)(angularWorkspaceRoot, options, context, [(0, js_1.getLockFileName)((0, devkit_1.detectPackageManager)(context.workspaceRoot))]);
projectsCache[hash] ??= await buildAngularProjects(configFilePath, options, angularWorkspaceRoot, context);
return { projects: projectsCache[hash] };
}
async function buildAngularProjects(configFilePath, options, angularWorkspaceRoot, context) {
const projects = {};
const absoluteConfigFilePath = (0, node_path_1.join)(context.workspaceRoot, configFilePath);
const angularJson = (0, devkit_1.readJsonFile)(absoluteConfigFilePath);
const appShellTargets = [];
const prerenderTargets = [];
for (const [projectName, project] of Object.entries(angularJson.projects ?? {})) {
const targets = {};
const projectTargets = getAngularJsonProjectTargets(project);
if (!projectTargets) {
continue;
}
const namedInputs = (0, get_named_inputs_1.getNamedInputs)(project.root, context);
for (const [angularTargetName, angularTarget] of Object.entries(projectTargets)) {
const nxTargetName = options?.targetNamePrefix
? `${options.targetNamePrefix}${angularTargetName}`
: angularTargetName;
const externalDependencies = ['@angular/cli'];
targets[nxTargetName] = {
command:
// For targets that are also Angular CLI commands, infer the simplified form.
// Otherwise, use `ng run` to support non-command targets so that they will run.
angularTargetName === 'build' ||
angularTargetName === 'deploy' ||
angularTargetName === 'extract-i18n' ||
angularTargetName === 'e2e' ||
angularTargetName === 'lint' ||
angularTargetName === 'serve' ||
angularTargetName === 'test'
? `ng ${angularTargetName}`
: `ng run ${projectName}:${angularTargetName}`,
options: { cwd: angularWorkspaceRoot },
metadata: {
technologies: ['angular'],
description: `Run the "${angularTargetName}" target for "${projectName}".`,
help: {
command: `${pmc.exec} ng run ${projectName}:${angularTargetName} --help`,
example: {},
},
},
};
if (knownExecutors.appShell.has(angularTarget.builder)) {
appShellTargets.push({ target: nxTargetName, project: projectName });
}
else if (knownExecutors.build.has(angularTarget.builder)) {
await updateBuildTarget(projectName, nxTargetName, targets[nxTargetName], angularTarget, context, angularWorkspaceRoot, project.root, namedInputs);
}
else if (knownExecutors.devServer.has(angularTarget.builder)) {
targets[nxTargetName].continuous = true;
targets[nxTargetName].metadata.help.example.options = { port: 4201 };
}
else if (knownExecutors.extractI18n.has(angularTarget.builder)) {
targets[nxTargetName].metadata.help.example.options = {
format: 'json',
};
}
else if (knownExecutors.test.has(angularTarget.builder)) {
updateTestTarget(targets[nxTargetName], angularTarget, context, angularWorkspaceRoot, project.root, namedInputs, externalDependencies);
}
else if (knownExecutors.server.has(angularTarget.builder)) {
updateServerTarget(targets[nxTargetName], angularTarget, context, angularWorkspaceRoot, project.root, namedInputs);
}
else if (knownExecutors.serveSsr.has(angularTarget.builder)) {
targets[nxTargetName].continuous = true;
targets[nxTargetName].metadata.help.example.options = { port: 4201 };
}
else if (knownExecutors.prerender.has(angularTarget.builder)) {
prerenderTargets.push({ target: nxTargetName, project: projectName });
}
if (targets[nxTargetName].inputs?.length) {
targets[nxTargetName].inputs.push({ externalDependencies });
}
if (angularTarget.configurations) {
for (const configurationName of Object.keys(angularTarget.configurations)) {
targets[nxTargetName].configurations = {
...targets[nxTargetName].configurations,
[configurationName]: {
command: `ng run ${projectName}:${angularTargetName}:${configurationName}`,
},
};
}
}
if (angularTarget.defaultConfiguration) {
targets[nxTargetName].defaultConfiguration =
angularTarget.defaultConfiguration;
}
}
projects[projectName] = {
projectType: project.projectType,
root: posix.join(angularWorkspaceRoot, project.root),
sourceRoot: project.sourceRoot
? posix.join(angularWorkspaceRoot, project.sourceRoot)
: undefined,
targets,
};
}
for (const { project, target } of appShellTargets) {
updateAppShellTarget(project, target, projects, angularJson, angularWorkspaceRoot, context);
}
for (const { project, target } of prerenderTargets) {
updatePrerenderTarget(project, target, projects, angularJson);
}
return Object.entries(projects).reduce((acc, [projectName, project]) => {
acc[project.root] = {
projectType: project.projectType,
sourceRoot: project.sourceRoot,
targets: project.targets,
};
return acc;
}, {});
}
function updateAppShellTarget(projectName, targetName, projects, angularJson, angularWorkspaceRoot, context) {
// it must exist since we collected it when processing it
const target = projects[projectName].targets[targetName];
target.metadata.help.example.options = { route: '/some/route' };
const { inputs, outputs } = getBrowserAndServerTargetInputsAndOutputs(projectName, targetName, projects, angularJson);
const outputIndexPath = getAngularJsonProjectTargets(angularJson.projects[projectName])[targetName].options?.outputIndexPath;
if (outputIndexPath) {
const fullOutputIndexPath = (0, node_path_1.join)(context.workspaceRoot, angularWorkspaceRoot, outputIndexPath);
outputs.push(getOutput(fullOutputIndexPath, context.workspaceRoot, angularWorkspaceRoot, angularJson.projects[projectName].root));
}
if (!outputs.length) {
// no outputs were identified for the build or server target, so we don't
// set any Nx cache options
return;
}
target.cache = true;
target.inputs = inputs;
target.outputs = outputs;
}
async function updateBuildTarget(projectName, targetName, target, angularTarget, context, angularWorkspaceRoot, projectRoot, namedInputs) {
target.dependsOn = [`^${targetName}`];
if (angularTarget.builder === '@angular-devkit/build-angular:ng-packagr' ||
angularTarget.builder === '@angular/build:ng-packagr') {
const outputs = await getNgPackagrOutputs(angularTarget, angularWorkspaceRoot, projectRoot, context);
if (outputs.length) {
target.outputs = outputs;
}
}
else {
const fullOutputPath = (0, node_path_1.join)(context.workspaceRoot, angularWorkspaceRoot, angularTarget.options?.outputPath ?? posix.join('dist', projectName));
target.outputs = [
getOutput(fullOutputPath, context.workspaceRoot, angularWorkspaceRoot, projectRoot),
];
}
if (target.outputs?.length) {
// make it cacheable if we were able to identify outputs
target.cache = true;
target.inputs =
'production' in namedInputs
? ['production', '^production']
: ['default', '^default'];
}
if (angularTarget.builder === '@angular-devkit/build-angular:ng-packagr' ||
angularTarget.builder === '@angular/build:ng-packagr') {
target.metadata.help.example.options = { watch: true };
}
else {
target.metadata.help.example.options = { localize: true };
}
}
function updateTestTarget(target, angularTarget, context, angularWorkspaceRoot, projectRoot, namedInputs, externalDependencies) {
target.cache = true;
target.inputs =
'production' in namedInputs
? ['default', '^production']
: ['default', '^default'];
target.outputs = getKarmaTargetOutputs(angularTarget, angularWorkspaceRoot, projectRoot, context);
externalDependencies.push('karma');
target.metadata.help.example.options = { codeCoverage: true };
}
function updateServerTarget(target, angularTarget, context, angularWorkspaceRoot, projectRoot, namedInputs) {
target.metadata.help.example.options = { localize: true };
if (!angularTarget.options?.outputPath) {
// only make it cacheable if we were able to identify outputs
return;
}
target.cache = true;
target.inputs =
'production' in namedInputs
? ['production', '^production']
: ['default', '^default'];
const fullOutputPath = (0, node_path_1.join)(context.workspaceRoot, angularWorkspaceRoot, angularTarget.options.outputPath);
target.outputs = [
getOutput(fullOutputPath, context.workspaceRoot, angularWorkspaceRoot, projectRoot),
];
}
function updatePrerenderTarget(projectName, targetName, projects, angularJson) {
// it must exist since we collected it when processing it
const target = projects[projectName].targets[targetName];
target.metadata.help.example.options =
getAngularJsonProjectTargets(angularJson.projects[projectName])[targetName]
.builder === '@angular-devkit/build-angular:prerender'
? { discoverRoutes: false }
: { guessRoutes: false };
const { inputs, outputs } = getBrowserAndServerTargetInputsAndOutputs(projectName, targetName, projects, angularJson);
if (!outputs.length) {
// no outputs were identified for the build or server target, so we don't
// set any Nx cache options
return;
}
target.cache = true;
target.inputs = inputs;
target.outputs = outputs;
}
async function getNgPackagrOutputs(target, angularWorkspaceRoot, projectRoot, context) {
let ngPackageJsonPath = (0, node_path_1.join)(context.workspaceRoot, angularWorkspaceRoot, target.options?.project ?? (0, node_path_1.join)(projectRoot, 'ng-package.json'));
const readConfig = async (configPath) => {
if (!(0, node_fs_1.existsSync)(configPath)) {
return undefined;
}
try {
if (configPath.endsWith('.js')) {
const result = await Promise.resolve(`${configPath}`).then(s => tslib_1.__importStar(require(s)));
return result['default'] ?? result;
}
return (0, devkit_1.readJsonFile)(configPath);
}
catch { }
return undefined;
};
let ngPackageJson;
let basePath;
if ((0, node_fs_1.statSync)(ngPackageJsonPath).isDirectory()) {
basePath = ngPackageJsonPath;
ngPackageJson = await readConfig((0, node_path_1.join)(ngPackageJsonPath, 'ng-package.json'));
if (!ngPackageJson) {
ngPackageJson = await readConfig((0, node_path_1.join)(ngPackageJsonPath, 'ng-package.js'));
}
}
else {
basePath = (0, node_path_1.dirname)(ngPackageJsonPath);
ngPackageJson = await readConfig(ngPackageJsonPath);
}
if (!ngPackageJson) {
return [];
}
const destination = ngPackageJson.dest
? (0, node_path_1.join)(basePath, ngPackageJson.dest)
: (0, node_path_1.join)(basePath, 'dist');
return [
getOutput(destination, context.workspaceRoot, angularWorkspaceRoot, projectRoot),
];
}
function getKarmaTargetOutputs(target, angularWorkspaceRoot, projectRoot, context) {
const defaultOutput = posix.join('{workspaceRoot}', angularWorkspaceRoot, 'coverage/{projectName}');
if (!target.options?.karmaConfig) {
return [defaultOutput];
}
try {
const { parseConfig } = require('karma/lib/config');
const karmaConfigPath = (0, node_path_1.join)(context.workspaceRoot, angularWorkspaceRoot, projectRoot, target.options.karmaConfig);
const config = parseConfig(karmaConfigPath);
if (config.coverageReporter.dir) {
return [
getOutput(config.coverageReporter.dir, context.workspaceRoot, angularWorkspaceRoot, projectRoot),
];
}
}
catch {
// we silently ignore any error here and fall back to the default output
}
return [defaultOutput];
}
function getBrowserAndServerTargetInputsAndOutputs(projectName, targetName, projects, angularJson) {
const { browserTarget, serverTarget } = extractBrowserAndServerTargets(angularJson, projectName, targetName);
if (!browserTarget || !serverTarget) {
// if any of these are missing, the target is invalid so we return empty values
return { inputs: [], outputs: [] };
}
const browserTargetInputs = projects[browserTarget.project]?.targets?.[browserTarget.target]?.inputs ??
[];
const serverTargetInputs = projects[serverTarget.project]?.targets?.[serverTarget.target]?.inputs ??
[];
const browserTargetOutputs = projects[browserTarget.project]?.targets?.[browserTarget.target]?.outputs ??
[];
const serverTargetOutputs = projects[serverTarget.project]?.targets?.[serverTarget.target]?.outputs ??
[];
return {
inputs: mergeInputs(...browserTargetInputs, ...serverTargetInputs),
outputs: Array.from(new Set([...browserTargetOutputs, ...serverTargetOutputs])),
};
}
function extractBrowserAndServerTargets(angularJson, projectName, targetName) {
let browserTarget;
let serverTarget;
try {
const targets = getAngularJsonProjectTargets(angularJson.projects[projectName]);
const target = targets[targetName];
let browserTargetSpecifier = target.options?.browserTarget;
if (!browserTargetSpecifier) {
const configuration = Object.values(target.configurations ?? {}).find((config) => !!config.browserTarget);
browserTargetSpecifier = configuration?.browserTarget;
}
if (browserTargetSpecifier) {
browserTarget = targetFromTargetString(browserTargetSpecifier, projectName, targetName);
}
let serverTargetSpecifier = target.options?.serverTarget;
if (!serverTargetSpecifier) {
serverTargetSpecifier = Object.values(target.configurations ?? {}).find((config) => !!config.serverTarget)?.serverTarget;
}
if (serverTargetSpecifier) {
serverTarget = targetFromTargetString(serverTargetSpecifier, projectName, targetName);
}
}
catch { }
return { browserTarget: browserTarget, serverTarget };
}
function mergeInputs(...inputs) {
const stringInputs = new Set();
const externalDependencies = new Set();
for (const input of inputs) {
if (typeof input === 'string') {
stringInputs.add(input);
}
else if ('externalDependencies' in input) {
// we only infer external dependencies, so we don't need to handle the other input definitions
for (const externalDependency of input.externalDependencies) {
externalDependencies.add(externalDependency);
}
}
}
return [
...stringInputs,
...(externalDependencies.size
? [{ externalDependencies: Array.from(externalDependencies) }]
: []),
];
}
// angular support abbreviated target specifiers, this is adapter from:
// https://github.com/angular/angular-cli/blob/7d9ce246a33c60ec96eb4bf99520f5475716a910/packages/angular_devkit/architect/src/api.ts#L336
function targetFromTargetString(specifier, abbreviatedProjectName, abbreviatedTargetName) {
const tuple = specifier.split(':', 3);
if (tuple.length < 2) {
// invalid target, ignore
return undefined;
}
// we only care about project and target
return {
project: tuple[0] || abbreviatedProjectName || '',
target: tuple[1] || abbreviatedTargetName || '',
};
}
function getOutput(path, workspaceRoot, angularWorkspaceRoot, projectRoot) {
const relativePath = (0, node_path_1.relative)((0, node_path_1.join)(workspaceRoot, angularWorkspaceRoot, projectRoot), path);
if (relativePath.startsWith('..')) {
return posix.join('{workspaceRoot}', (0, node_path_1.join)(angularWorkspaceRoot, projectRoot, relativePath));
}
else {
return posix.join('{projectRoot}', relativePath);
}
}
function getAngularJsonProjectTargets(project) {
return project.architect ?? project.targets;
}