diff --git a/__tests__/cache-save.test.ts b/__tests__/cache-save.test.ts index 0651138e3..4fc997920 100644 --- a/__tests__/cache-save.test.ts +++ b/__tests__/cache-save.test.ts @@ -118,9 +118,14 @@ describe('run', () => { expect(getStateSpy).toHaveBeenCalledTimes(2); expect(getCommandOutputSpy).toHaveBeenCalledTimes(2); expect(debugSpy).toHaveBeenCalledWith( - 'yarn path is /some/random/path/yarn1 (derived from cache-dependency-path: "")' + 'Project directory "." derived from cache-dependency-path: ""' + ); + expect(debugSpy).toHaveBeenCalledWith( + 'Consumed yarn version is 1.2.3 (working dir: ".")' + ); + expect(debugSpy).toHaveBeenCalledWith( + 'yarn\'s cache folder "/some/random/path/yarn1" configured for the directory "."' ); - expect(debugSpy).toHaveBeenCalledWith('Consumed yarn version is 1.2.3'); expect(infoSpy).toHaveBeenCalledWith( `Cache hit occurred on the primary key ${yarnFileHash}, not saving cache.` ); @@ -140,9 +145,14 @@ describe('run', () => { expect(getStateSpy).toHaveBeenCalledTimes(2); expect(getCommandOutputSpy).toHaveBeenCalledTimes(2); expect(debugSpy).toHaveBeenCalledWith( - 'yarn path is /some/random/path/yarn2 (derived from cache-dependency-path: "")' + 'Project directory "." derived from cache-dependency-path: ""' + ); + expect(debugSpy).toHaveBeenCalledWith( + 'Consumed yarn version is 2.2.3 (working dir: ".")' + ); + expect(debugSpy).toHaveBeenCalledWith( + 'yarn\'s cache folder "/some/random/path/yarn2" configured for the directory "."' ); - expect(debugSpy).toHaveBeenCalledWith('Consumed yarn version is 2.2.3'); expect(infoSpy).toHaveBeenCalledWith( `Cache hit occurred on the primary key ${yarnFileHash}, not saving cache.` ); @@ -159,7 +169,9 @@ describe('run', () => { expect(getInputSpy).toHaveBeenCalled(); expect(getStateSpy).toHaveBeenCalledTimes(2); expect(getCommandOutputSpy).toHaveBeenCalledTimes(1); - expect(debugSpy).toHaveBeenCalledWith(`npm path is ${commonPath}/npm`); + expect(debugSpy).toHaveBeenCalledWith( + `npm's cache folder "${commonPath}/npm" configured for the root directory` + ); expect(infoSpy).toHaveBeenCalledWith( `Cache hit occurred on the primary key ${npmFileHash}, not saving cache.` ); @@ -176,7 +188,9 @@ describe('run', () => { expect(getInputSpy).toHaveBeenCalled(); expect(getStateSpy).toHaveBeenCalledTimes(2); expect(getCommandOutputSpy).toHaveBeenCalledTimes(1); - expect(debugSpy).toHaveBeenCalledWith(`pnpm path is ${commonPath}/pnpm`); + expect(debugSpy).toHaveBeenCalledWith( + `pnpm's cache folder "${commonPath}/pnpm" configured for the root directory` + ); expect(infoSpy).toHaveBeenCalledWith( `Cache hit occurred on the primary key ${pnpmFileHash}, not saving cache.` ); @@ -204,9 +218,14 @@ describe('run', () => { expect(getStateSpy).toHaveBeenCalledTimes(2); expect(getCommandOutputSpy).toHaveBeenCalledTimes(2); expect(debugSpy).toHaveBeenCalledWith( - 'yarn path is /some/random/path/yarn1 (derived from cache-dependency-path: "")' + 'Project directory "." derived from cache-dependency-path: ""' + ); + expect(debugSpy).toHaveBeenCalledWith( + 'Consumed yarn version is 1.2.3 (working dir: ".")' + ); + expect(debugSpy).toHaveBeenCalledWith( + 'yarn\'s cache folder "/some/random/path/yarn1" configured for the directory "."' ); - expect(debugSpy).toHaveBeenCalledWith('Consumed yarn version is 1.2.3'); expect(infoSpy).not.toHaveBeenCalledWith( `Cache hit occurred on the primary key ${yarnFileHash}, not saving cache.` ); @@ -236,9 +255,14 @@ describe('run', () => { expect(getStateSpy).toHaveBeenCalledTimes(2); expect(getCommandOutputSpy).toHaveBeenCalledTimes(2); expect(debugSpy).toHaveBeenCalledWith( - 'yarn path is /some/random/path/yarn2 (derived from cache-dependency-path: "")' + 'Project directory "." derived from cache-dependency-path: ""' + ); + expect(debugSpy).toHaveBeenCalledWith( + 'Consumed yarn version is 2.2.3 (working dir: ".")' + ); + expect(debugSpy).toHaveBeenCalledWith( + 'yarn\'s cache folder "/some/random/path/yarn2" configured for the directory "."' ); - expect(debugSpy).toHaveBeenCalledWith('Consumed yarn version is 2.2.3'); expect(infoSpy).not.toHaveBeenCalledWith( `Cache hit occurred on the primary key ${yarnFileHash}, not saving cache.` ); @@ -265,7 +289,9 @@ describe('run', () => { expect(getInputSpy).toHaveBeenCalled(); expect(getStateSpy).toHaveBeenCalledTimes(2); expect(getCommandOutputSpy).toHaveBeenCalledTimes(1); - expect(debugSpy).toHaveBeenCalledWith(`npm path is ${commonPath}/npm`); + expect(debugSpy).toHaveBeenCalledWith( + `npm's cache folder "${commonPath}/npm" configured for the root directory` + ); expect(infoSpy).not.toHaveBeenCalledWith( `Cache hit occurred on the primary key ${npmFileHash}, not saving cache.` ); @@ -292,7 +318,9 @@ describe('run', () => { expect(getInputSpy).toHaveBeenCalled(); expect(getStateSpy).toHaveBeenCalledTimes(2); expect(getCommandOutputSpy).toHaveBeenCalledTimes(1); - expect(debugSpy).toHaveBeenCalledWith(`pnpm path is ${commonPath}/pnpm`); + expect(debugSpy).toHaveBeenCalledWith( + `pnpm's cache folder "${commonPath}/pnpm" configured for the root directory` + ); expect(infoSpy).not.toHaveBeenCalledWith( `Cache hit occurred on the primary key ${pnpmFileHash}, not saving cache.` ); @@ -322,7 +350,9 @@ describe('run', () => { expect(getInputSpy).toHaveBeenCalled(); expect(getStateSpy).toHaveBeenCalledTimes(2); expect(getCommandOutputSpy).toHaveBeenCalledTimes(1); - expect(debugSpy).toHaveBeenCalledWith(`npm path is ${commonPath}/npm`); + expect(debugSpy).toHaveBeenCalledWith( + `npm's cache folder "${commonPath}/npm" configured for the root directory` + ); expect(infoSpy).not.toHaveBeenCalledWith( `Cache hit occurred on the primary key ${npmFileHash}, not saving cache.` ); @@ -352,7 +382,9 @@ describe('run', () => { expect(getInputSpy).toHaveBeenCalled(); expect(getStateSpy).toHaveBeenCalledTimes(2); expect(getCommandOutputSpy).toHaveBeenCalledTimes(1); - expect(debugSpy).toHaveBeenCalledWith(`npm path is ${commonPath}/npm`); + expect(debugSpy).toHaveBeenCalledWith( + `npm's cache folder "${commonPath}/npm" configured for the root directory` + ); expect(infoSpy).not.toHaveBeenCalledWith( `Cache hit occurred on the primary key ${npmFileHash}, not saving cache.` ); diff --git a/__tests__/cache-utils.test.ts b/__tests__/cache-utils.test.ts index 80e4a0844..789d3ba61 100644 --- a/__tests__/cache-utils.test.ts +++ b/__tests__/cache-utils.test.ts @@ -7,7 +7,8 @@ import { isCacheFeatureAvailable, supportedPackageManagers, getCommandOutput, - expandCacheDependencyPath + expandCacheDependencyPath, + expandedPatternsMemoized } from '../src/cache-utils'; import fs from 'fs'; import * as cacheUtils from '../src/cache-utils'; @@ -104,6 +105,10 @@ describe('cache-utils', () => { (pattern: string): Promise => MockGlobber.create(['/foo', '/bar']) ); + + Object.keys(expandedPatternsMemoized).forEach( + key => delete expandedPatternsMemoized[key] + ); }); afterEach(() => { @@ -194,13 +199,36 @@ two [supportedPackageManagers.yarn, '/dir/file.lock'], [supportedPackageManagers.yarn, '/**/file.lock'] ])( - 'getCacheDirectoriesPaths should return empty array of folder in case of error', + 'getCacheDirectoriesPaths should throw for getCommandOutput returning empty', async (packageManagerInfo, cacheDependency) => { getCommandOutputSpy.mockImplementation((command: string) => // return empty string to indicate getCacheFolderPath failed // --version still works command.includes('version') ? '1.' : '' ); + + await expect( + cacheUtils.getCacheDirectoriesPaths( + packageManagerInfo, + cacheDependency + ) + ).rejects.toThrow(); //'Could not get cache folder path for /dir'); + } + ); + + it.each([ + [supportedPackageManagers.npm, ''], + [supportedPackageManagers.npm, '/dir/file.lock'], + [supportedPackageManagers.npm, '/**/file.lock'], + [supportedPackageManagers.pnpm, ''], + [supportedPackageManagers.pnpm, '/dir/file.lock'], + [supportedPackageManagers.pnpm, '/**/file.lock'], + [supportedPackageManagers.yarn, ''], + [supportedPackageManagers.yarn, '/dir/file.lock'], + [supportedPackageManagers.yarn, '/**/file.lock'] + ])( + 'getCacheDirectoriesPaths should throw in case of having not directories', + async (packageManagerInfo, cacheDependency) => { lstatSpy.mockImplementation(arg => ({ isDirectory: () => false })); @@ -248,9 +276,8 @@ two } ); - // TODO: by design - glob is not expected to return duplicates so 3 patterns do not collapse to 2 it.each(['1.1.1', '2.2.2'])( - 'getCacheDirectoriesPaths yarn v%s should return 3 dirs with globbed cacheDependency expanding to duplicates', + 'getCacheDirectoriesPaths yarn v%s should return 2 dirs with globbed cacheDependency expanding to duplicates', async version => { let dirNo = 1; getCommandOutputSpy.mockImplementation((command: string) => @@ -269,11 +296,7 @@ two supportedPackageManagers.yarn, '/tmp/**/file' ); - expect(dirs).toEqual([ - `file_${version}_1`, - `file_${version}_2`, - `file_${version}_3` - ]); + expect(dirs).toEqual([`file_${version}_1`, `file_${version}_2`]); } ); diff --git a/dist/cache-save/index.js b/dist/cache-save/index.js index 6b045c6df..1331b8bb2 100644 --- a/dist/cache-save/index.js +++ b/dist/cache-save/index.js @@ -60434,7 +60434,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.isCacheFeatureAvailable = exports.isGhes = exports.getCacheDirectoriesPaths = exports.expandCacheDependencyPath = exports.getPackageManagerInfo = exports.getCommandOutputGuarded = exports.getCommandOutput = exports.supportedPackageManagers = exports.yarn2GetCacheFolderCommand = exports.yarn1GetCacheFolderCommand = exports.pnpmGetCacheFolderCommand = exports.npmGetCacheFolderCommand = void 0; +exports.isCacheFeatureAvailable = exports.isGhes = exports.getCacheDirectoriesPaths = exports.expandCacheDependencyPath = exports.expandedPatternsMemoized = exports.getPackageManagerInfo = exports.getCommandOutputGuarded = exports.getCommandOutput = exports.supportedPackageManagers = exports.yarn2GetCacheFolderCommand = exports.yarn1GetCacheFolderCommand = exports.pnpmGetCacheFolderCommand = exports.npmGetCacheFolderCommand = void 0; const core = __importStar(__nccwpck_require__(2186)); const exec = __importStar(__nccwpck_require__(1514)); const cache = __importStar(__nccwpck_require__(7799)); @@ -60462,7 +60462,7 @@ exports.supportedPackageManagers = { lockFilePatterns: ['yarn.lock'], getCacheFolderPath: (projectDir) => __awaiter(void 0, void 0, void 0, function* () { const yarnVersion = yield exports.getCommandOutputGuarded(`yarn --version`, 'Could not retrieve version of yarn', projectDir); - core.debug(`Consumed yarn version is ${yarnVersion}`); + core.debug(`Consumed yarn version is ${yarnVersion} (working dir: "${projectDir}")`); const stdOut = yarnVersion.startsWith('1.') ? yield exports.getCommandOutput(exports.yarn1GetCacheFolderCommand, projectDir) : yield exports.getCommandOutput(exports.yarn2GetCacheFolderCommand, projectDir); @@ -60507,10 +60507,27 @@ const getPackageManagerInfo = (packageManager) => __awaiter(void 0, void 0, void } }); exports.getPackageManagerInfo = getPackageManagerInfo; +exports.expandedPatternsMemoized = {}; +/** + * Wrapper around `glob.create(pattern).glob()` with the memoization + * @param pattern is expected to be a globed path + * @return list of files or directories expanded from glob + */ const globPatternToArray = (pattern) => __awaiter(void 0, void 0, void 0, function* () { + const memoized = exports.expandedPatternsMemoized[pattern]; + if (memoized) + return Promise.resolve(memoized); const globber = yield glob.create(pattern); - return globber.glob(); + const expanded = yield globber.glob(); + exports.expandedPatternsMemoized[pattern] = expanded; + return expanded; }); +/** + * Expands (converts) the string input `cache-dependency-path` to list of files' paths + * First it breaks the input by new lines and then expand glob patterns if any + * @param cacheDependencyPath - either a single string or multiline string with possible glob patterns + * @return list of files on which the cache depends + */ const expandCacheDependencyPath = (cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () { const multilinePaths = cacheDependencyPath .split(/\r?\n/) @@ -60521,26 +60538,55 @@ const expandCacheDependencyPath = (cacheDependencyPath) => __awaiter(void 0, voi return expandedPaths.length === 0 ? [''] : expandedPaths.flat(); }); exports.expandCacheDependencyPath = expandCacheDependencyPath; -const cacheDependencyPathToCacheFolderPath = (packageManagerInfo, cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () { - const cacheDependencyPathDirectory = path_1.default.dirname(cacheDependencyPath); - const cacheFolderPath = fs_1.default.existsSync(cacheDependencyPathDirectory) && - fs_1.default.lstatSync(cacheDependencyPathDirectory).isDirectory() - ? yield packageManagerInfo.getCacheFolderPath(cacheDependencyPathDirectory) - : yield packageManagerInfo.getCacheFolderPath(); - core.debug(`${packageManagerInfo.name} path is ${cacheFolderPath} (derived from cache-dependency-path: "${cacheDependencyPath}")`); +/** + * Converts dependency file to the directory it resides in and ensures the directory exists + * @param cacheDependencyPath - file name + * @return either directory containing file or null + */ +const cacheDependencyPathToProjectDirectory = (cacheDependencyPath) => { + const projectDirectory = path_1.default.dirname(cacheDependencyPath); + if (fs_1.default.existsSync(projectDirectory) && + fs_1.default.lstatSync(projectDirectory).isDirectory()) { + core.debug(`Project directory "${projectDirectory}" derived from cache-dependency-path: "${cacheDependencyPath}"`); + return projectDirectory; + } + else { + core.debug(`No project directory found cache-dependency-path: "${cacheDependencyPath}", root dir assumed`); + return null; + } +}; +/** + * Expands (converts) the string input `cache-dependency-path` to list of directories that + * may be project roots + * @param cacheDependencyPath + * @return list of directories and possible + */ +const cacheDependencyPathToProjectsDirectories = (cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () { + const cacheDependenciesPaths = yield exports.expandCacheDependencyPath(cacheDependencyPath); + const existingDirectories = cacheDependenciesPaths + .map(cacheDependencyPath => cacheDependencyPathToProjectDirectory(cacheDependencyPath)) + .filter(path => path !== null); + if (existingDirectories.length === 0) + throw Error('No existing directories found containing `cache-dependency-path`="${cacheDependencyPath}"'); + // uniq + return existingDirectories.filter((cachePath, i, result) => cachePath != null && result.indexOf(cachePath) === i); +}); +const projectDirectoryToCacheFolderPath = (packageManagerInfo, projectDirectory) => __awaiter(void 0, void 0, void 0, function* () { + const cacheFolderPath = yield packageManagerInfo.getCacheFolderPath(projectDirectory); + core.debug(`${packageManagerInfo.name}'s cache folder "${cacheFolderPath}" configured for the directory "${projectDirectory}"`); return cacheFolderPath; }); -const cacheDependenciesPathsToCacheFoldersPaths = (packageManagerInfo, cacheDependenciesPaths) => __awaiter(void 0, void 0, void 0, function* () { - const cacheFoldersPaths = yield Promise.all(cacheDependenciesPaths.map(cacheDependencyPath => cacheDependencyPathToCacheFolderPath(packageManagerInfo, cacheDependencyPath))); +const projectDirectoriesToCacheFoldersPaths = (packageManagerInfo, projectDirectories) => __awaiter(void 0, void 0, void 0, function* () { + const cacheFoldersPaths = yield Promise.all(projectDirectories.map(projectDirectory => projectDirectoryToCacheFolderPath(packageManagerInfo, projectDirectory))); return cacheFoldersPaths.filter((cachePath, i, result) => result.indexOf(cachePath) === i); }); const cacheDependencyPathToCacheFoldersPaths = (packageManagerInfo, cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () { - const cacheDependenciesPaths = yield exports.expandCacheDependencyPath(cacheDependencyPath); - return cacheDependenciesPathsToCacheFoldersPaths(packageManagerInfo, cacheDependenciesPaths); + const projectDirectories = yield cacheDependencyPathToProjectsDirectories(cacheDependencyPath); + return projectDirectoriesToCacheFoldersPaths(packageManagerInfo, projectDirectories); }); const cacheFoldersPathsForRoot = (packageManagerInfo) => __awaiter(void 0, void 0, void 0, function* () { const cacheFolderPath = yield packageManagerInfo.getCacheFolderPath(); - core.debug(`${packageManagerInfo.name} path is ${cacheFolderPath}`); + core.debug(`${packageManagerInfo.name}'s cache folder "${cacheFolderPath}" configured for the root directory`); return [cacheFolderPath]; }); const getCacheDirectoriesPaths = (packageManagerInfo, cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () { diff --git a/dist/setup/index.js b/dist/setup/index.js index 7230bd041..fefe1ecb6 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -71216,7 +71216,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.isCacheFeatureAvailable = exports.isGhes = exports.getCacheDirectoriesPaths = exports.expandCacheDependencyPath = exports.getPackageManagerInfo = exports.getCommandOutputGuarded = exports.getCommandOutput = exports.supportedPackageManagers = exports.yarn2GetCacheFolderCommand = exports.yarn1GetCacheFolderCommand = exports.pnpmGetCacheFolderCommand = exports.npmGetCacheFolderCommand = void 0; +exports.isCacheFeatureAvailable = exports.isGhes = exports.getCacheDirectoriesPaths = exports.expandCacheDependencyPath = exports.expandedPatternsMemoized = exports.getPackageManagerInfo = exports.getCommandOutputGuarded = exports.getCommandOutput = exports.supportedPackageManagers = exports.yarn2GetCacheFolderCommand = exports.yarn1GetCacheFolderCommand = exports.pnpmGetCacheFolderCommand = exports.npmGetCacheFolderCommand = void 0; const core = __importStar(__nccwpck_require__(2186)); const exec = __importStar(__nccwpck_require__(1514)); const cache = __importStar(__nccwpck_require__(7799)); @@ -71244,7 +71244,7 @@ exports.supportedPackageManagers = { lockFilePatterns: ['yarn.lock'], getCacheFolderPath: (projectDir) => __awaiter(void 0, void 0, void 0, function* () { const yarnVersion = yield exports.getCommandOutputGuarded(`yarn --version`, 'Could not retrieve version of yarn', projectDir); - core.debug(`Consumed yarn version is ${yarnVersion}`); + core.debug(`Consumed yarn version is ${yarnVersion} (working dir: "${projectDir}")`); const stdOut = yarnVersion.startsWith('1.') ? yield exports.getCommandOutput(exports.yarn1GetCacheFolderCommand, projectDir) : yield exports.getCommandOutput(exports.yarn2GetCacheFolderCommand, projectDir); @@ -71289,10 +71289,27 @@ const getPackageManagerInfo = (packageManager) => __awaiter(void 0, void 0, void } }); exports.getPackageManagerInfo = getPackageManagerInfo; +exports.expandedPatternsMemoized = {}; +/** + * Wrapper around `glob.create(pattern).glob()` with the memoization + * @param pattern is expected to be a globed path + * @return list of files or directories expanded from glob + */ const globPatternToArray = (pattern) => __awaiter(void 0, void 0, void 0, function* () { + const memoized = exports.expandedPatternsMemoized[pattern]; + if (memoized) + return Promise.resolve(memoized); const globber = yield glob.create(pattern); - return globber.glob(); + const expanded = yield globber.glob(); + exports.expandedPatternsMemoized[pattern] = expanded; + return expanded; }); +/** + * Expands (converts) the string input `cache-dependency-path` to list of files' paths + * First it breaks the input by new lines and then expand glob patterns if any + * @param cacheDependencyPath - either a single string or multiline string with possible glob patterns + * @return list of files on which the cache depends + */ const expandCacheDependencyPath = (cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () { const multilinePaths = cacheDependencyPath .split(/\r?\n/) @@ -71303,26 +71320,55 @@ const expandCacheDependencyPath = (cacheDependencyPath) => __awaiter(void 0, voi return expandedPaths.length === 0 ? [''] : expandedPaths.flat(); }); exports.expandCacheDependencyPath = expandCacheDependencyPath; -const cacheDependencyPathToCacheFolderPath = (packageManagerInfo, cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () { - const cacheDependencyPathDirectory = path_1.default.dirname(cacheDependencyPath); - const cacheFolderPath = fs_1.default.existsSync(cacheDependencyPathDirectory) && - fs_1.default.lstatSync(cacheDependencyPathDirectory).isDirectory() - ? yield packageManagerInfo.getCacheFolderPath(cacheDependencyPathDirectory) - : yield packageManagerInfo.getCacheFolderPath(); - core.debug(`${packageManagerInfo.name} path is ${cacheFolderPath} (derived from cache-dependency-path: "${cacheDependencyPath}")`); +/** + * Converts dependency file to the directory it resides in and ensures the directory exists + * @param cacheDependencyPath - file name + * @return either directory containing file or null + */ +const cacheDependencyPathToProjectDirectory = (cacheDependencyPath) => { + const projectDirectory = path_1.default.dirname(cacheDependencyPath); + if (fs_1.default.existsSync(projectDirectory) && + fs_1.default.lstatSync(projectDirectory).isDirectory()) { + core.debug(`Project directory "${projectDirectory}" derived from cache-dependency-path: "${cacheDependencyPath}"`); + return projectDirectory; + } + else { + core.debug(`No project directory found cache-dependency-path: "${cacheDependencyPath}", root dir assumed`); + return null; + } +}; +/** + * Expands (converts) the string input `cache-dependency-path` to list of directories that + * may be project roots + * @param cacheDependencyPath + * @return list of directories and possible + */ +const cacheDependencyPathToProjectsDirectories = (cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () { + const cacheDependenciesPaths = yield exports.expandCacheDependencyPath(cacheDependencyPath); + const existingDirectories = cacheDependenciesPaths + .map(cacheDependencyPath => cacheDependencyPathToProjectDirectory(cacheDependencyPath)) + .filter(path => path !== null); + if (existingDirectories.length === 0) + throw Error('No existing directories found containing `cache-dependency-path`="${cacheDependencyPath}"'); + // uniq + return existingDirectories.filter((cachePath, i, result) => cachePath != null && result.indexOf(cachePath) === i); +}); +const projectDirectoryToCacheFolderPath = (packageManagerInfo, projectDirectory) => __awaiter(void 0, void 0, void 0, function* () { + const cacheFolderPath = yield packageManagerInfo.getCacheFolderPath(projectDirectory); + core.debug(`${packageManagerInfo.name}'s cache folder "${cacheFolderPath}" configured for the directory "${projectDirectory}"`); return cacheFolderPath; }); -const cacheDependenciesPathsToCacheFoldersPaths = (packageManagerInfo, cacheDependenciesPaths) => __awaiter(void 0, void 0, void 0, function* () { - const cacheFoldersPaths = yield Promise.all(cacheDependenciesPaths.map(cacheDependencyPath => cacheDependencyPathToCacheFolderPath(packageManagerInfo, cacheDependencyPath))); +const projectDirectoriesToCacheFoldersPaths = (packageManagerInfo, projectDirectories) => __awaiter(void 0, void 0, void 0, function* () { + const cacheFoldersPaths = yield Promise.all(projectDirectories.map(projectDirectory => projectDirectoryToCacheFolderPath(packageManagerInfo, projectDirectory))); return cacheFoldersPaths.filter((cachePath, i, result) => result.indexOf(cachePath) === i); }); const cacheDependencyPathToCacheFoldersPaths = (packageManagerInfo, cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () { - const cacheDependenciesPaths = yield exports.expandCacheDependencyPath(cacheDependencyPath); - return cacheDependenciesPathsToCacheFoldersPaths(packageManagerInfo, cacheDependenciesPaths); + const projectDirectories = yield cacheDependencyPathToProjectsDirectories(cacheDependencyPath); + return projectDirectoriesToCacheFoldersPaths(packageManagerInfo, projectDirectories); }); const cacheFoldersPathsForRoot = (packageManagerInfo) => __awaiter(void 0, void 0, void 0, function* () { const cacheFolderPath = yield packageManagerInfo.getCacheFolderPath(); - core.debug(`${packageManagerInfo.name} path is ${cacheFolderPath}`); + core.debug(`${packageManagerInfo.name}'s cache folder "${cacheFolderPath}" configured for the root directory`); return [cacheFolderPath]; }); const getCacheDirectoriesPaths = (packageManagerInfo, cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () { diff --git a/src/cache-utils.ts b/src/cache-utils.ts index 9c7f28272..9a473f9ba 100644 --- a/src/cache-utils.ts +++ b/src/cache-utils.ts @@ -51,7 +51,9 @@ export const supportedPackageManagers: SupportedPackageManagers = { projectDir ); - core.debug(`Consumed yarn version is ${yarnVersion}`); + core.debug( + `Consumed yarn version is ${yarnVersion} (working dir: "${projectDir}")` + ); const stdOut = yarnVersion.startsWith('1.') ? await getCommandOutput(yarn1GetCacheFolderCommand, projectDir) @@ -110,12 +112,27 @@ export const getPackageManagerInfo = async (packageManager: string) => { return null; } }; - +export const expandedPatternsMemoized: Record = {}; +/** + * Wrapper around `glob.create(pattern).glob()` with the memoization + * @param pattern is expected to be a globed path + * @return list of files or directories expanded from glob + */ const globPatternToArray = async (pattern: string): Promise => { + const memoized = expandedPatternsMemoized[pattern]; + if (memoized) return Promise.resolve(memoized); const globber = await glob.create(pattern); - return globber.glob(); + const expanded = await globber.glob(); + expandedPatternsMemoized[pattern] = expanded; + return expanded; }; +/** + * Expands (converts) the string input `cache-dependency-path` to list of files' paths + * First it breaks the input by new lines and then expand glob patterns if any + * @param cacheDependencyPath - either a single string or multiline string with possible glob patterns + * @return list of files on which the cache depends + */ export const expandCacheDependencyPath = async ( cacheDependencyPath: string ): Promise => { @@ -130,52 +147,98 @@ export const expandCacheDependencyPath = async ( return expandedPaths.length === 0 ? [''] : expandedPaths.flat(); }; -const cacheDependencyPathToCacheFolderPath = async ( - packageManagerInfo: PackageManagerInfo, +/** + * Converts dependency file to the directory it resides in and ensures the directory exists + * @param cacheDependencyPath - file name + * @return either directory containing file or null + */ +const cacheDependencyPathToProjectDirectory = ( cacheDependencyPath: string -): Promise => { - const cacheDependencyPathDirectory = path.dirname(cacheDependencyPath); - const cacheFolderPath = - fs.existsSync(cacheDependencyPathDirectory) && - fs.lstatSync(cacheDependencyPathDirectory).isDirectory() - ? await packageManagerInfo.getCacheFolderPath( - cacheDependencyPathDirectory - ) - : await packageManagerInfo.getCacheFolderPath(); +): string | null => { + const projectDirectory = path.dirname(cacheDependencyPath); + if ( + fs.existsSync(projectDirectory) && + fs.lstatSync(projectDirectory).isDirectory() + ) { + core.debug( + `Project directory "${projectDirectory}" derived from cache-dependency-path: "${cacheDependencyPath}"` + ); + return projectDirectory; + } else { + core.debug( + `No project directory found cache-dependency-path: "${cacheDependencyPath}", root dir assumed` + ); + return null; + } +}; - core.debug( - `${packageManagerInfo.name} path is ${cacheFolderPath} (derived from cache-dependency-path: "${cacheDependencyPath}")` +/** + * Expands (converts) the string input `cache-dependency-path` to list of directories that + * may be project roots + * @param cacheDependencyPath + * @return list of directories and possible + */ +const cacheDependencyPathToProjectsDirectories = async ( + cacheDependencyPath: string +): Promise => { + const cacheDependenciesPaths = await expandCacheDependencyPath( + cacheDependencyPath ); + const existingDirectories: string[] = cacheDependenciesPaths + .map(cacheDependencyPath => + cacheDependencyPathToProjectDirectory(cacheDependencyPath) + ) + .filter(path => path !== null) as string[]; + + if (existingDirectories.length === 0) + throw Error( + 'No existing directories found containing `cache-dependency-path`="${cacheDependencyPath}"' + ); + + // uniq + return existingDirectories.filter( + (cachePath, i, result) => + cachePath != null && result.indexOf(cachePath) === i + ); +}; + +const projectDirectoryToCacheFolderPath = async ( + packageManagerInfo: PackageManagerInfo, + projectDirectory: string +): Promise => { + const cacheFolderPath = await packageManagerInfo.getCacheFolderPath( + projectDirectory + ); + core.debug( + `${packageManagerInfo.name}'s cache folder "${cacheFolderPath}" configured for the directory "${projectDirectory}"` + ); return cacheFolderPath; }; -const cacheDependenciesPathsToCacheFoldersPaths = async ( + +const projectDirectoriesToCacheFoldersPaths = async ( packageManagerInfo: PackageManagerInfo, - cacheDependenciesPaths: string[] + projectDirectories: string[] ): Promise => { const cacheFoldersPaths = await Promise.all( - cacheDependenciesPaths.map(cacheDependencyPath => - cacheDependencyPathToCacheFolderPath( - packageManagerInfo, - cacheDependencyPath - ) + projectDirectories.map(projectDirectory => + projectDirectoryToCacheFolderPath(packageManagerInfo, projectDirectory) ) ); return cacheFoldersPaths.filter( (cachePath, i, result) => result.indexOf(cachePath) === i ); }; - const cacheDependencyPathToCacheFoldersPaths = async ( packageManagerInfo: PackageManagerInfo, cacheDependencyPath: string ): Promise => { - const cacheDependenciesPaths = await expandCacheDependencyPath( + const projectDirectories = await cacheDependencyPathToProjectsDirectories( cacheDependencyPath ); - return cacheDependenciesPathsToCacheFoldersPaths( + return projectDirectoriesToCacheFoldersPaths( packageManagerInfo, - cacheDependenciesPaths + projectDirectories ); }; @@ -183,7 +246,9 @@ const cacheFoldersPathsForRoot = async ( packageManagerInfo: PackageManagerInfo ): Promise => { const cacheFolderPath = await packageManagerInfo.getCacheFolderPath(); - core.debug(`${packageManagerInfo.name} path is ${cacheFolderPath}`); + core.debug( + `${packageManagerInfo.name}'s cache folder "${cacheFolderPath}" configured for the root directory` + ); return [cacheFolderPath]; };