Jan Martin 54594b5abf feat(@angular-devkit/build-angular): support karma with esbuild
Adds a new "builderMode" setting for Karma that can be used to switch
between webpack ("browser") and esbuild ("application"). It supports a
third value "detect" that will use the same bundler that's also used for
development builds.

The detect mode is modelled after the logic used for the dev-server builder.

This initial implementation doesn't properly support `--watch` mode or code
coverage.
2024-09-26 09:35:18 -07:00

114 lines
3.2 KiB
TypeScript

/**
* @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
*/
import glob, { isDynamicPattern } from 'fast-glob';
import { PathLike, constants, promises as fs } from 'fs';
import { basename, dirname, extname, join, relative } from 'path';
/* Go through all patterns and find unique list of files */
export async function findTests(
include: string[],
exclude: string[],
workspaceRoot: string,
projectSourceRoot: string,
): Promise<string[]> {
const matchingTestsPromises = include.map((pattern) =>
findMatchingTests(pattern, exclude, workspaceRoot, projectSourceRoot),
);
const files = await Promise.all(matchingTestsPromises);
// Unique file names
return [...new Set(files.flat())];
}
const normalizePath = (path: string): string => path.replace(/\\/g, '/');
const removeLeadingSlash = (pattern: string): string => {
if (pattern.charAt(0) === '/') {
return pattern.substring(1);
}
return pattern;
};
const removeRelativeRoot = (path: string, root: string): string => {
if (path.startsWith(root)) {
return path.substring(root.length);
}
return path;
};
async function findMatchingTests(
pattern: string,
ignore: string[],
workspaceRoot: string,
projectSourceRoot: string,
): Promise<string[]> {
// normalize pattern, glob lib only accepts forward slashes
let normalizedPattern = normalizePath(pattern);
normalizedPattern = removeLeadingSlash(normalizedPattern);
const relativeProjectRoot = normalizePath(relative(workspaceRoot, projectSourceRoot) + '/');
// remove relativeProjectRoot to support relative paths from root
// such paths are easy to get when running scripts via IDEs
normalizedPattern = removeRelativeRoot(normalizedPattern, relativeProjectRoot);
// special logic when pattern does not look like a glob
if (!isDynamicPattern(normalizedPattern)) {
if (await isDirectory(join(projectSourceRoot, normalizedPattern))) {
normalizedPattern = `${normalizedPattern}/**/*.spec.@(ts|tsx)`;
} else {
// see if matching spec file exists
const fileExt = extname(normalizedPattern);
// Replace extension to `.spec.ext`. Example: `src/app/app.component.ts`-> `src/app/app.component.spec.ts`
const potentialSpec = join(
projectSourceRoot,
dirname(normalizedPattern),
`${basename(normalizedPattern, fileExt)}.spec${fileExt}`,
);
if (await exists(potentialSpec)) {
return [potentialSpec];
}
}
}
// normalize the patterns in the ignore list
const normalizedIgnorePatternList = ignore.map((pattern: string) =>
removeRelativeRoot(removeLeadingSlash(normalizePath(pattern)), relativeProjectRoot),
);
return glob(normalizedPattern, {
cwd: projectSourceRoot,
absolute: true,
ignore: ['**/node_modules/**', ...normalizedIgnorePatternList],
});
}
async function isDirectory(path: PathLike): Promise<boolean> {
try {
const stats = await fs.stat(path);
return stats.isDirectory();
} catch {
return false;
}
}
async function exists(path: PathLike): Promise<boolean> {
try {
await fs.access(path, constants.F_OK);
return true;
} catch {
return false;
}
}