From 0b69fabcf5590497cda099651b3a69f265bb329c Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Sun, 28 Feb 2021 18:19:43 +0000 Subject: [PATCH] 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 --- .../ngcc/src/entry_point_finder/utils.ts | 72 ++++++++++++++----- .../test/entry_point_finder/utils_spec.ts | 18 +++++ 2 files changed, 72 insertions(+), 18 deletions(-) diff --git a/packages/compiler-cli/ngcc/src/entry_point_finder/utils.ts b/packages/compiler-cli/ngcc/src/entry_point_finder/utils.ts index d32498f350..c74f5ce1de 100644 --- a/packages/compiler-cli/ngcc/src/entry_point_finder/utils.ts +++ b/packages/compiler-cli/ngcc/src/entry_point_finder/utils.ts @@ -5,7 +5,7 @@ * 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 */ -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 {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` + `Please check your path mappings configuration such as in the tsconfig.json file.`); } - Object.values(pathMappings.paths).forEach(paths => paths.forEach(path => { - // We only want base paths that exist and are not files - let basePath = fs.resolve(baseUrl, extractPathPrefix(path)); - if (fs.exists(basePath) && fs.stat(basePath).isFile()) { - basePath = fs.dirname(basePath); + for (const paths of Object.values(pathMappings.paths)) { + for (const path of paths) { + let foundMatch = false; + + // 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); @@ -70,13 +101,18 @@ export function getBasePaths( 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 `*`. * @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) { - return path.split('*', 1)[0]; +function extractPathPrefix(path: string): {prefix: string, hasWildcard: boolean} { + const [prefix, rest] = path.split('*', 2); + return {prefix, hasWildcard: rest !== undefined}; } /** diff --git a/packages/compiler-cli/ngcc/test/entry_point_finder/utils_spec.ts b/packages/compiler-cli/ngcc/test/entry_point_finder/utils_spec.ts index 536e23559b..8d365844c5 100644 --- a/packages/compiler-cli/ngcc/test/entry_point_finder/utils_spec.ts +++ b/packages/compiler-cli/ngcc/test/entry_point_finder/utils_spec.ts @@ -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', () => { const projectDirectory = _('/path/to/project'); const fs = getFileSystem();