fix(compiler-cli): ensure ngcc can handle wildcard base-paths (#41033)

Ngcc uses the `paths` property to compute the potential base-paths
for packages that are being processed. If the `paths` contain a wildcard
`*` within a path segment, ngcc was not finding the base-path correctly.

Now when a wildcard is found, there is an additional search to look for
paths that might match the wildcard.

Fixes #41014

PR Close #41033
This commit is contained in:
Pete Bacon Darwin 2021-02-28 18:19:43 +00:00 committed by Zach Arend
parent e12d9dec64
commit 0b69fabcf5
2 changed files with 72 additions and 18 deletions

View File

@ -5,7 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AbsoluteFsPath, getFileSystem, PathManipulation} from '../../../src/ngtsc/file_system'; import {AbsoluteFsPath, getFileSystem, PathManipulation, ReadonlyFileSystem} from '../../../src/ngtsc/file_system';
import {Logger} from '../../../src/ngtsc/logging'; import {Logger} from '../../../src/ngtsc/logging';
import {PathMappings} from '../path_mappings'; import {PathMappings} from '../path_mappings';
@ -41,21 +41,52 @@ export function getBasePaths(
`This is likely to mess up how ngcc finds entry-points and is probably not correct.\n` + `This is likely to mess up how ngcc finds entry-points and is probably not correct.\n` +
`Please check your path mappings configuration such as in the tsconfig.json file.`); `Please check your path mappings configuration such as in the tsconfig.json file.`);
} }
Object.values(pathMappings.paths).forEach(paths => paths.forEach(path => { for (const paths of Object.values(pathMappings.paths)) {
// We only want base paths that exist and are not files for (const path of paths) {
let basePath = fs.resolve(baseUrl, extractPathPrefix(path)); let foundMatch = false;
if (fs.exists(basePath) && fs.stat(basePath).isFile()) {
basePath = fs.dirname(basePath); // We only want base paths that exist and are not files
const {prefix, hasWildcard} = extractPathPrefix(path);
let basePath = fs.resolve(baseUrl, prefix);
if (fs.exists(basePath) && fs.stat(basePath).isFile()) {
basePath = fs.dirname(basePath);
}
if (fs.exists(basePath)) {
// The `basePath` is itself a directory
basePaths.push(basePath);
foundMatch = true;
}
if (hasWildcard) {
// The path contains a wildcard (`*`) so also try searching for directories that start
// with the wildcard prefix path segment.
const wildcardContainer = fs.dirname(basePath);
const wildcardPrefix = fs.basename(basePath);
if (isExistingDirectory(fs, wildcardContainer)) {
const candidates = fs.readdir(wildcardContainer);
for (const candidate of candidates) {
if (candidate.startsWith(wildcardPrefix)) {
const candidatePath = fs.resolve(wildcardContainer, candidate);
if (isExistingDirectory(fs, candidatePath)) {
foundMatch = true;
basePaths.push(candidatePath);
}
}
}
}
}
if (!foundMatch) {
// We neither found a direct match (i.e. `basePath` is an existing directory) nor a
// directory that starts with a wildcard prefix.
logger.debug(
`The basePath "${basePath}" computed from baseUrl "${baseUrl}" and path mapping "${
path}" does not exist in the file-system.\n` +
`It will not be scanned for entry-points.`);
}
} }
if (fs.exists(basePath)) { }
basePaths.push(basePath);
} else {
logger.debug(
`The basePath "${basePath}" computed from baseUrl "${baseUrl}" and path mapping "${
path}" does not exist in the file-system.\n` +
`It will not be scanned for entry-points.`);
}
}));
} }
const dedupedBasePaths = dedupePaths(fs, basePaths); const dedupedBasePaths = dedupePaths(fs, basePaths);
@ -70,13 +101,18 @@ export function getBasePaths(
return dedupedBasePaths; return dedupedBasePaths;
} }
function isExistingDirectory(fs: ReadonlyFileSystem, path: AbsoluteFsPath): boolean {
return fs.exists(path) && fs.stat(path).isDirectory();
}
/** /**
* Extract everything in the `path` up to the first `*`. * Extract everything in the `path` up to the first `*`.
* @param path The path to parse. * @param path The path to parse.
* @returns The extracted prefix. * @returns The extracted prefix and a flag to indicate whether there was a wildcard `*`.
*/ */
function extractPathPrefix(path: string) { function extractPathPrefix(path: string): {prefix: string, hasWildcard: boolean} {
return path.split('*', 1)[0]; const [prefix, rest] = path.split('*', 2);
return {prefix, hasWildcard: rest !== undefined};
} }
/** /**

View File

@ -48,6 +48,24 @@ runInEachFileSystem(() => {
]); ]);
}); });
it('should find base-paths that start with a wildcard prefix', () => {
const projectDirectory = _('/path/to/project');
const fs = getFileSystem();
fs.ensureDir(fs.resolve(projectDirectory, 'dist'));
fs.ensureDir(fs.resolve(projectDirectory, 'dist-a'));
fs.ensureDir(fs.resolve(projectDirectory, 'dist-b'));
const sourceDirectory = _('/path/to/project/node_modules');
const pathMappings = {baseUrl: projectDirectory, paths: {'@dist*': ['dist*']}};
const basePaths = getBasePaths(logger, sourceDirectory, pathMappings);
expect(basePaths).toEqual([
sourceDirectory,
fs.resolve(projectDirectory, 'dist'),
fs.resolve(projectDirectory, 'dist-a'),
fs.resolve(projectDirectory, 'dist-b'),
]);
});
it('should not be confused by folders that have the same starting string', () => { it('should not be confused by folders that have the same starting string', () => {
const projectDirectory = _('/path/to/project'); const projectDirectory = _('/path/to/project');
const fs = getFileSystem(); const fs = getFileSystem();