From 9ddc512bc1770f4522d2a1614b4de57894596f4c Mon Sep 17 00:00:00 2001
From: Sergey Dolin <dsame@github.com>
Date: Tue, 30 May 2023 14:15:01 +0200
Subject: [PATCH] Apply fixes

---
 __tests__/cache-restore.test.ts |   8 +-
 __tests__/cache-save.test.ts    |  28 ++---
 __tests__/cache-utils.test.ts   |  76 ++++---------
 dist/cache-save/index.js        | 143 +++++++++----------------
 dist/setup/index.js             | 143 +++++++++----------------
 src/cache-restore.ts            |   4 +-
 src/cache-save.ts               |   4 +-
 src/cache-utils.ts              | 184 +++++++++++---------------------
 8 files changed, 193 insertions(+), 397 deletions(-)

diff --git a/__tests__/cache-restore.test.ts b/__tests__/cache-restore.test.ts
index ba20a22d..86dff3f3 100644
--- a/__tests__/cache-restore.test.ts
+++ b/__tests__/cache-restore.test.ts
@@ -32,13 +32,13 @@ describe('cache-restore', () => {
 
   function findCacheFolder(command: string) {
     switch (command) {
-      case utils.npmGetCacheFolderCommand:
+      case 'npm config get cache':
         return npmCachePath;
-      case utils.pnpmGetCacheFolderCommand:
+      case 'pnpm store path --silent':
         return pnpmCachePath;
-      case utils.yarn1GetCacheFolderCommand:
+      case 'yarn cache dir':
         return yarn1CachePath;
-      case utils.yarn2GetCacheFolderCommand:
+      case 'yarn config get cacheFolder':
         return yarn2CachePath;
       default:
         return 'packge/not/found';
diff --git a/__tests__/cache-save.test.ts b/__tests__/cache-save.test.ts
index 4fc99792..c84621c5 100644
--- a/__tests__/cache-save.test.ts
+++ b/__tests__/cache-save.test.ts
@@ -118,13 +118,10 @@ describe('run', () => {
       expect(getStateSpy).toHaveBeenCalledTimes(2);
       expect(getCommandOutputSpy).toHaveBeenCalledTimes(2);
       expect(debugSpy).toHaveBeenCalledWith(
-        'Project directory "." derived from cache-dependency-path: ""'
+        'Consumed yarn version is 1.2.3 (working dir: "")'
       );
       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 "."'
+        'yarn\'s cache folder "/some/random/path/yarn1" configured for the root directory'
       );
       expect(infoSpy).toHaveBeenCalledWith(
         `Cache hit occurred on the primary key ${yarnFileHash}, not saving cache.`
@@ -145,13 +142,10 @@ describe('run', () => {
       expect(getStateSpy).toHaveBeenCalledTimes(2);
       expect(getCommandOutputSpy).toHaveBeenCalledTimes(2);
       expect(debugSpy).toHaveBeenCalledWith(
-        'Project directory "." derived from cache-dependency-path: ""'
+        'Consumed yarn version is 2.2.3 (working dir: "")'
       );
       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 "."'
+        'yarn\'s cache folder "/some/random/path/yarn2" configured for the root directory'
       );
       expect(infoSpy).toHaveBeenCalledWith(
         `Cache hit occurred on the primary key ${yarnFileHash}, not saving cache.`
@@ -218,13 +212,10 @@ describe('run', () => {
       expect(getStateSpy).toHaveBeenCalledTimes(2);
       expect(getCommandOutputSpy).toHaveBeenCalledTimes(2);
       expect(debugSpy).toHaveBeenCalledWith(
-        'Project directory "." derived from cache-dependency-path: ""'
+        'Consumed yarn version is 1.2.3 (working dir: "")'
       );
       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 "."'
+        'yarn\'s cache folder "/some/random/path/yarn1" configured for the root directory'
       );
       expect(infoSpy).not.toHaveBeenCalledWith(
         `Cache hit occurred on the primary key ${yarnFileHash}, not saving cache.`
@@ -255,13 +246,10 @@ describe('run', () => {
       expect(getStateSpy).toHaveBeenCalledTimes(2);
       expect(getCommandOutputSpy).toHaveBeenCalledTimes(2);
       expect(debugSpy).toHaveBeenCalledWith(
-        'Project directory "." derived from cache-dependency-path: ""'
+        'Consumed yarn version is 2.2.3 (working dir: "")'
       );
       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 "."'
+        'yarn\'s cache folder "/some/random/path/yarn2" configured for the root directory'
       );
       expect(infoSpy).not.toHaveBeenCalledWith(
         `Cache hit occurred on the primary key ${yarnFileHash}, not saving cache.`
diff --git a/__tests__/cache-utils.test.ts b/__tests__/cache-utils.test.ts
index 789d3ba6..da9c793b 100644
--- a/__tests__/cache-utils.test.ts
+++ b/__tests__/cache-utils.test.ts
@@ -7,8 +7,7 @@ import {
   isCacheFeatureAvailable,
   supportedPackageManagers,
   getCommandOutput,
-  expandCacheDependencyPath,
-  expandedPatternsMemoized
+  memoizedCacheDependencies
 } from '../src/cache-utils';
 import fs from 'fs';
 import * as cacheUtils from '../src/cache-utils';
@@ -106,8 +105,8 @@ describe('cache-utils', () => {
           MockGlobber.create(['/foo', '/bar'])
       );
 
-      Object.keys(expandedPatternsMemoized).forEach(
-        key => delete expandedPatternsMemoized[key]
+      Object.keys(memoizedCacheDependencies).forEach(
+        key => delete memoizedCacheDependencies[key]
       );
     });
 
@@ -117,44 +116,6 @@ describe('cache-utils', () => {
       globCreateSpy.mockRestore();
     });
 
-    it('expandCacheDependencyPath should handle one line', async () => {
-      expect(await expandCacheDependencyPath('one')).toEqual(['one']);
-    });
-
-    it('expandCacheDependencyPath should handle one line glob', async () => {
-      globCreateSpy.mockImplementation(
-        (pattern: string): Promise<Globber> =>
-          MockGlobber.create(['one', 'two'])
-      );
-      expect(await expandCacheDependencyPath('**')).toEqual(['one', 'two']);
-    });
-
-    it('expandCacheDependencyPath should handle multiple lines', async () => {
-      const lines = `
-          one
-two
-
-          `;
-      expect(await expandCacheDependencyPath(lines)).toEqual(['one', 'two']);
-    });
-
-    it('expandCacheDependencyPath should handle multiple globs', async () => {
-      const lines = `
-          one
-**
-
-          `;
-      globCreateSpy.mockImplementation(
-        (pattern: string): Promise<Globber> =>
-          MockGlobber.create(['two', 'three'])
-      );
-      expect(await expandCacheDependencyPath(lines)).toEqual([
-        'one',
-        'two',
-        'three'
-      ]);
-    });
-
     it.each([
       [supportedPackageManagers.npm, ''],
       [supportedPackageManagers.npm, '/dir/file.lock'],
@@ -167,7 +128,7 @@ two
       async (packageManagerInfo, cacheDependency) => {
         getCommandOutputSpy.mockImplementation(() => 'foo');
 
-        const dirs = await cacheUtils.getCacheDirectoriesPaths(
+        const dirs = await cacheUtils.getCacheDirectories(
           packageManagerInfo,
           cacheDependency
         );
@@ -181,7 +142,7 @@ two
     it('getCacheDirectoriesPaths should return one dir for yarn without cacheDependency', async () => {
       getCommandOutputSpy.mockImplementation(() => 'foo');
 
-      const dirs = await cacheUtils.getCacheDirectoriesPaths(
+      const dirs = await cacheUtils.getCacheDirectories(
         supportedPackageManagers.yarn,
         ''
       );
@@ -208,10 +169,7 @@ two
         );
 
         await expect(
-          cacheUtils.getCacheDirectoriesPaths(
-            packageManagerInfo,
-            cacheDependency
-          )
+          cacheUtils.getCacheDirectories(packageManagerInfo, cacheDependency)
         ).rejects.toThrow(); //'Could not get cache folder path for /dir');
       }
     );
@@ -234,10 +192,7 @@ two
         }));
 
         await expect(
-          cacheUtils.getCacheDirectoriesPaths(
-            packageManagerInfo,
-            cacheDependency
-          )
+          cacheUtils.getCacheDirectories(packageManagerInfo, cacheDependency)
         ).rejects.toThrow(); //'Could not get cache folder path for /dir');
       }
     );
@@ -248,7 +203,7 @@ two
         getCommandOutputSpy.mockImplementationOnce(() => version);
         getCommandOutputSpy.mockImplementationOnce(() => `foo${version}`);
 
-        const dirs = await cacheUtils.getCacheDirectoriesPaths(
+        const dirs = await cacheUtils.getCacheDirectories(
           supportedPackageManagers.yarn,
           ''
         );
@@ -268,7 +223,7 @@ two
             MockGlobber.create(['/tmp/dir1/file', '/tmp/dir2/file'])
         );
 
-        const dirs = await cacheUtils.getCacheDirectoriesPaths(
+        const dirs = await cacheUtils.getCacheDirectories(
           supportedPackageManagers.yarn,
           '/tmp/**/file'
         );
@@ -292,7 +247,7 @@ two
             ])
         );
 
-        const dirs = await cacheUtils.getCacheDirectoriesPaths(
+        const dirs = await cacheUtils.getCacheDirectories(
           supportedPackageManagers.yarn,
           '/tmp/**/file'
         );
@@ -318,7 +273,7 @@ two
             ])
         );
 
-        const dirs = await cacheUtils.getCacheDirectoriesPaths(
+        const dirs = await cacheUtils.getCacheDirectories(
           supportedPackageManagers.yarn,
           '/tmp/**/file'
         );
@@ -367,13 +322,18 @@ two
           `;
         globCreateSpy.mockImplementation(
           (pattern: string): Promise<Globber> =>
-            MockGlobber.create(['/tmp/dir3/file', '/tmp/dir4/file'])
+            MockGlobber.create([
+              '/tmp/dir1/file',
+              '/tmp/dir2/file',
+              '/tmp/dir3/file',
+              '/tmp/dir4/file'
+            ])
         );
         let dirNo = 1;
         getCommandOutputSpy.mockImplementation((command: string) =>
           command.includes('version') ? version : `file_${version}_${dirNo++}`
         );
-        const dirs = await cacheUtils.getCacheDirectoriesPaths(
+        const dirs = await cacheUtils.getCacheDirectories(
           supportedPackageManagers.yarn,
           cacheDependencyPath
         );
diff --git a/dist/cache-save/index.js b/dist/cache-save/index.js
index 631e6d47..b7b72fde 100644
--- a/dist/cache-save/index.js
+++ b/dist/cache-save/index.js
@@ -60378,7 +60378,7 @@ const cachePackages = (packageManager) => __awaiter(void 0, void 0, void 0, func
     // TODO: core.getInput has a bug - it can return undefined despite its definition (tests only?)
     //       export declare function getInput(name: string, options?: InputOptions): string;
     const cacheDependencyPath = core.getInput('cache-dependency-path') || '';
-    const cachePaths = yield cache_utils_1.getCacheDirectoriesPaths(packageManagerInfo, cacheDependencyPath);
+    const cachePaths = yield cache_utils_1.getCacheDirectories(packageManagerInfo, cacheDependencyPath);
     if (cachePaths.length === 0) {
         throw new Error(`Cache folder paths are not retrieved for ${packageManager} with cache-dependency-path = ${cacheDependencyPath}`);
     }
@@ -60434,38 +60434,33 @@ 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.expandedPatternsMemoized = exports.getPackageManagerInfo = exports.getCommandOutputGuarded = exports.getCommandOutput = exports.supportedPackageManagers = exports.yarn2GetCacheFolderCommand = exports.yarn1GetCacheFolderCommand = exports.pnpmGetCacheFolderCommand = exports.npmGetCacheFolderCommand = void 0;
+exports.isCacheFeatureAvailable = exports.isGhes = exports.getCacheDirectories = exports.memoizedCacheDependencies = exports.getPackageManagerInfo = exports.getCommandOutputNotEmpty = exports.getCommandOutput = exports.supportedPackageManagers = void 0;
 const core = __importStar(__nccwpck_require__(2186));
 const exec = __importStar(__nccwpck_require__(1514));
 const cache = __importStar(__nccwpck_require__(7799));
 const glob = __importStar(__nccwpck_require__(8090));
 const path_1 = __importDefault(__nccwpck_require__(1017));
 const fs_1 = __importDefault(__nccwpck_require__(7147));
-// for testing purposes
-exports.npmGetCacheFolderCommand = 'npm config get cache';
-exports.pnpmGetCacheFolderCommand = 'pnpm store path --silent';
-exports.yarn1GetCacheFolderCommand = 'yarn cache dir';
-exports.yarn2GetCacheFolderCommand = 'yarn config get cacheFolder';
 exports.supportedPackageManagers = {
     npm: {
         name: 'npm',
         lockFilePatterns: ['package-lock.json', 'npm-shrinkwrap.json', 'yarn.lock'],
-        getCacheFolderPath: () => exports.getCommandOutputGuarded(exports.npmGetCacheFolderCommand, 'Could not get npm cache folder path')
+        getCacheFolderPath: () => exports.getCommandOutputNotEmpty('npm config get cache', 'Could not get npm cache folder path')
     },
     pnpm: {
         name: 'pnpm',
         lockFilePatterns: ['pnpm-lock.yaml'],
-        getCacheFolderPath: () => exports.getCommandOutputGuarded(exports.pnpmGetCacheFolderCommand, 'Could not get pnpm cache folder path')
+        getCacheFolderPath: () => exports.getCommandOutputNotEmpty('pnpm store path --silent', 'Could not get pnpm cache folder path')
     },
     yarn: {
         name: 'yarn',
         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} (working dir: "${projectDir}")`);
+            const yarnVersion = yield exports.getCommandOutputNotEmpty(`yarn --version`, 'Could not retrieve version of yarn', projectDir);
+            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);
+                ? yield exports.getCommandOutput('yarn cache dir', projectDir)
+                : yield exports.getCommandOutput('yarn config get cacheFolder', projectDir);
             if (!stdOut) {
                 throw new Error(`Could not get yarn cache folder path for ${projectDir}`);
             }
@@ -60484,14 +60479,14 @@ const getCommandOutput = (toolCommand, cwd) => __awaiter(void 0, void 0, void 0,
     return stdout.trim();
 });
 exports.getCommandOutput = getCommandOutput;
-const getCommandOutputGuarded = (toolCommand, error, cwd) => __awaiter(void 0, void 0, void 0, function* () {
+const getCommandOutputNotEmpty = (toolCommand, error, cwd) => __awaiter(void 0, void 0, void 0, function* () {
     const stdOut = exports.getCommandOutput(toolCommand, cwd);
     if (!stdOut) {
         throw new Error(error);
     }
     return stdOut;
 });
-exports.getCommandOutputGuarded = getCommandOutputGuarded;
+exports.getCommandOutputNotEmpty = getCommandOutputNotEmpty;
 const getPackageManagerInfo = (packageManager) => __awaiter(void 0, void 0, void 0, function* () {
     if (packageManager === 'npm') {
         return exports.supportedPackageManagers.npm;
@@ -60511,54 +60506,7 @@ exports.getPackageManagerInfo = getPackageManagerInfo;
  * glob expanding memoized because it involves potentially very deep
  * traversing through the directories tree
  */
-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);
-    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/)
-        .map(path => path.trim())
-        .filter(path => Boolean(path));
-    const expandedPathsPromises = multilinePaths.map(path => path.includes('*') ? globPatternToArray(path) : Promise.resolve([path]));
-    const expandedPaths = yield Promise.all(expandedPathsPromises);
-    return expandedPaths.length === 0 ? [''] : expandedPaths.flat();
-});
-exports.expandCacheDependencyPath = expandCacheDependencyPath;
-/**
- * Converts dependency file path to the directory it resides in and ensures the directory exists
- * @param cacheDependencyPath - a file name path
- * @return either directory containing the 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 for cache-dependency-path: "${cacheDependencyPath}", will be skipped`);
-        return null;
-    }
-};
+exports.memoizedCacheDependencies = {};
 /**
  * Expands (converts) the string input `cache-dependency-path` to list of directories that
  * may be project roots
@@ -60566,69 +60514,76 @@ const cacheDependencyPathToProjectDirectory = (cacheDependencyPath) => {
  *                              expected to be the result of `core.getInput('cache-dependency-path')`
  * @return list of directories and possible
  */
-const cacheDependencyPathToProjectsDirectories = (cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () {
-    const cacheDependenciesPaths = yield exports.expandCacheDependencyPath(cacheDependencyPath);
+const getProjectDirectoriesFromCacheDependencyPath = (cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () {
+    let cacheDependenciesPaths;
+    // memoize unglobbed paths to avoid traversing FS
+    const memoized = exports.memoizedCacheDependencies[cacheDependencyPath];
+    if (memoized) {
+        cacheDependenciesPaths = memoized;
+    }
+    else {
+        cacheDependenciesPaths = (yield glob
+            .create(cacheDependencyPath)
+            .then(globber => globber.glob())) || [''];
+        exports.memoizedCacheDependencies[cacheDependencyPath] = cacheDependenciesPaths;
+    }
     const existingDirectories = cacheDependenciesPaths
-        .map(cacheDependencyPath => cacheDependencyPathToProjectDirectory(cacheDependencyPath))
-        .filter(path => path !== null);
+        .map(cacheDependencyPath => path_1.default.dirname(cacheDependencyPath))
+        // uniq in order to do not traverse the same directories during the further processing
+        .filter((cachePath, i, result) => cachePath != null && result.indexOf(cachePath) === i)
+        .filter(directory => fs_1.default.existsSync(directory) && fs_1.default.lstatSync(directory).isDirectory());
     // if user explicitly pointed out some file, but it does not exist it is definitely
     // not he wanted, thus we should throw an error not trying to workaround with unexpected
     // result to the whole build
     if (existingDirectories.length === 0)
         throw Error('No existing directories found containing `cache-dependency-path`="${cacheDependencyPath}"');
-    // uniq in order to do not traverse the same directories during the further processing
-    return existingDirectories.filter((cachePath, i, result) => cachePath != null && result.indexOf(cachePath) === i);
+    return existingDirectories;
 });
 /**
- * Utility function to be used from within `map`
- * Finds the cache directories configured for the project directory
- * @param packageManagerInfo - an object having getCacheFolderPath method specific to given PM
- * @param projectDirectory - the string pointing out to a project dir (i.e. directory with its own .yarnrc)
- * @return list of directories to be cached according to the project configuration in the directory
- */
-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;
-});
-/**
- * Top-entry function to find the cache directories configured for the repo if cache-dependency-path is not empty
+ * Finds the cache directories configured for the repo if cache-dependency-path is not empty
  * @param packageManagerInfo - an object having getCacheFolderPath method specific to given PM
  * @param cacheDependencyPath - either a single string or multiline string with possible glob patterns
  *                              expected to be the result of `core.getInput('cache-dependency-path')`
  * @return list of files on which the cache depends
  */
-const cacheDependencyPathToCacheFoldersPaths = (packageManagerInfo, cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () {
-    const projectDirectories = yield cacheDependencyPathToProjectsDirectories(cacheDependencyPath);
-    const cacheFoldersPaths = yield Promise.all(projectDirectories.map(projectDirectory => projectDirectoryToCacheFolderPath(packageManagerInfo, projectDirectory)));
+const getCacheDirectoriesFromCacheDependencyPath = (packageManagerInfo, cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () {
+    const projectDirectories = yield getProjectDirectoriesFromCacheDependencyPath(cacheDependencyPath);
+    const cacheFoldersPaths = yield Promise.all(projectDirectories.map(projectDirectory => packageManagerInfo
+        .getCacheFolderPath(projectDirectory)
+        .then(cacheFolderPath => {
+        core.debug(`${packageManagerInfo.name}'s cache folder "${cacheFolderPath}" configured for the directory "${projectDirectory}"`);
+        return cacheFolderPath;
+    })));
     // uniq in order to do not cache the same directories twice
     return cacheFoldersPaths.filter((cachePath, i, result) => result.indexOf(cachePath) === i);
 });
 /**
- * Top-entry function to find the cache directories configured for the repo if cache-dependency-path is empty
+ * Finds the cache directories configured for the repo ignoring cache-dependency-path
  * @param packageManagerInfo - an object having getCacheFolderPath method specific to given PM
  * @return list of files on which the cache depends
  */
-const cacheFoldersPathsForRoot = (packageManagerInfo) => __awaiter(void 0, void 0, void 0, function* () {
+const getCacheDirectoriesForRootProject = (packageManagerInfo) => __awaiter(void 0, void 0, void 0, function* () {
     const cacheFolderPath = yield packageManagerInfo.getCacheFolderPath();
     core.debug(`${packageManagerInfo.name}'s cache folder "${cacheFolderPath}" configured for the root directory`);
     return [cacheFolderPath];
 });
 /**
- * Main function to find the cache directories configured for the repo
+ * A function to find the cache directories configured for the repo
  * currently it handles only the case of PM=yarn && cacheDependencyPath is not empty
  * @param packageManagerInfo - an object having getCacheFolderPath method specific to given PM
  * @param cacheDependencyPath - either a single string or multiline string with possible glob patterns
  *                              expected to be the result of `core.getInput('cache-dependency-path')`
  * @return list of files on which the cache depends
  */
-const getCacheDirectoriesPaths = (packageManagerInfo, cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () {
-    // TODO: multiple directories limited to yarn so far
-    return packageManagerInfo === exports.supportedPackageManagers.yarn
-        ? cacheDependencyPathToCacheFoldersPaths(packageManagerInfo, cacheDependencyPath)
-        : cacheFoldersPathsForRoot(packageManagerInfo);
+const getCacheDirectories = (packageManagerInfo, cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () {
+    // For yarn, if cacheDependencyPath is set, ask information about cache folders in each project
+    // folder satisfied by cacheDependencyPath https://github.com/actions/setup-node/issues/488
+    if (packageManagerInfo.name === 'yarn' && cacheDependencyPath) {
+        return getCacheDirectoriesFromCacheDependencyPath(packageManagerInfo, cacheDependencyPath);
+    }
+    return getCacheDirectoriesForRootProject(packageManagerInfo);
 });
-exports.getCacheDirectoriesPaths = getCacheDirectoriesPaths;
+exports.getCacheDirectories = getCacheDirectories;
 function isGhes() {
     const ghUrl = new URL(process.env['GITHUB_SERVER_URL'] || 'https://github.com');
     return ghUrl.hostname.toUpperCase() !== 'GITHUB.COM';
diff --git a/dist/setup/index.js b/dist/setup/index.js
index 23abc160..39a3cef8 100644
--- a/dist/setup/index.js
+++ b/dist/setup/index.js
@@ -71144,7 +71144,7 @@ const restoreCache = (packageManager, cacheDependencyPath) => __awaiter(void 0,
         throw new Error(`Caching for '${packageManager}' is not supported`);
     }
     const platform = process.env.RUNNER_OS;
-    const cachePaths = yield cache_utils_1.getCacheDirectoriesPaths(packageManagerInfo, cacheDependencyPath);
+    const cachePaths = yield cache_utils_1.getCacheDirectories(packageManagerInfo, cacheDependencyPath);
     const lockFilePath = cacheDependencyPath
         ? cacheDependencyPath
         : findLockFile(packageManagerInfo);
@@ -71216,38 +71216,33 @@ 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.expandedPatternsMemoized = exports.getPackageManagerInfo = exports.getCommandOutputGuarded = exports.getCommandOutput = exports.supportedPackageManagers = exports.yarn2GetCacheFolderCommand = exports.yarn1GetCacheFolderCommand = exports.pnpmGetCacheFolderCommand = exports.npmGetCacheFolderCommand = void 0;
+exports.isCacheFeatureAvailable = exports.isGhes = exports.getCacheDirectories = exports.memoizedCacheDependencies = exports.getPackageManagerInfo = exports.getCommandOutputNotEmpty = exports.getCommandOutput = exports.supportedPackageManagers = void 0;
 const core = __importStar(__nccwpck_require__(2186));
 const exec = __importStar(__nccwpck_require__(1514));
 const cache = __importStar(__nccwpck_require__(7799));
 const glob = __importStar(__nccwpck_require__(8090));
 const path_1 = __importDefault(__nccwpck_require__(1017));
 const fs_1 = __importDefault(__nccwpck_require__(7147));
-// for testing purposes
-exports.npmGetCacheFolderCommand = 'npm config get cache';
-exports.pnpmGetCacheFolderCommand = 'pnpm store path --silent';
-exports.yarn1GetCacheFolderCommand = 'yarn cache dir';
-exports.yarn2GetCacheFolderCommand = 'yarn config get cacheFolder';
 exports.supportedPackageManagers = {
     npm: {
         name: 'npm',
         lockFilePatterns: ['package-lock.json', 'npm-shrinkwrap.json', 'yarn.lock'],
-        getCacheFolderPath: () => exports.getCommandOutputGuarded(exports.npmGetCacheFolderCommand, 'Could not get npm cache folder path')
+        getCacheFolderPath: () => exports.getCommandOutputNotEmpty('npm config get cache', 'Could not get npm cache folder path')
     },
     pnpm: {
         name: 'pnpm',
         lockFilePatterns: ['pnpm-lock.yaml'],
-        getCacheFolderPath: () => exports.getCommandOutputGuarded(exports.pnpmGetCacheFolderCommand, 'Could not get pnpm cache folder path')
+        getCacheFolderPath: () => exports.getCommandOutputNotEmpty('pnpm store path --silent', 'Could not get pnpm cache folder path')
     },
     yarn: {
         name: 'yarn',
         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} (working dir: "${projectDir}")`);
+            const yarnVersion = yield exports.getCommandOutputNotEmpty(`yarn --version`, 'Could not retrieve version of yarn', projectDir);
+            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);
+                ? yield exports.getCommandOutput('yarn cache dir', projectDir)
+                : yield exports.getCommandOutput('yarn config get cacheFolder', projectDir);
             if (!stdOut) {
                 throw new Error(`Could not get yarn cache folder path for ${projectDir}`);
             }
@@ -71266,14 +71261,14 @@ const getCommandOutput = (toolCommand, cwd) => __awaiter(void 0, void 0, void 0,
     return stdout.trim();
 });
 exports.getCommandOutput = getCommandOutput;
-const getCommandOutputGuarded = (toolCommand, error, cwd) => __awaiter(void 0, void 0, void 0, function* () {
+const getCommandOutputNotEmpty = (toolCommand, error, cwd) => __awaiter(void 0, void 0, void 0, function* () {
     const stdOut = exports.getCommandOutput(toolCommand, cwd);
     if (!stdOut) {
         throw new Error(error);
     }
     return stdOut;
 });
-exports.getCommandOutputGuarded = getCommandOutputGuarded;
+exports.getCommandOutputNotEmpty = getCommandOutputNotEmpty;
 const getPackageManagerInfo = (packageManager) => __awaiter(void 0, void 0, void 0, function* () {
     if (packageManager === 'npm') {
         return exports.supportedPackageManagers.npm;
@@ -71293,54 +71288,7 @@ exports.getPackageManagerInfo = getPackageManagerInfo;
  * glob expanding memoized because it involves potentially very deep
  * traversing through the directories tree
  */
-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);
-    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/)
-        .map(path => path.trim())
-        .filter(path => Boolean(path));
-    const expandedPathsPromises = multilinePaths.map(path => path.includes('*') ? globPatternToArray(path) : Promise.resolve([path]));
-    const expandedPaths = yield Promise.all(expandedPathsPromises);
-    return expandedPaths.length === 0 ? [''] : expandedPaths.flat();
-});
-exports.expandCacheDependencyPath = expandCacheDependencyPath;
-/**
- * Converts dependency file path to the directory it resides in and ensures the directory exists
- * @param cacheDependencyPath - a file name path
- * @return either directory containing the 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 for cache-dependency-path: "${cacheDependencyPath}", will be skipped`);
-        return null;
-    }
-};
+exports.memoizedCacheDependencies = {};
 /**
  * Expands (converts) the string input `cache-dependency-path` to list of directories that
  * may be project roots
@@ -71348,69 +71296,76 @@ const cacheDependencyPathToProjectDirectory = (cacheDependencyPath) => {
  *                              expected to be the result of `core.getInput('cache-dependency-path')`
  * @return list of directories and possible
  */
-const cacheDependencyPathToProjectsDirectories = (cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () {
-    const cacheDependenciesPaths = yield exports.expandCacheDependencyPath(cacheDependencyPath);
+const getProjectDirectoriesFromCacheDependencyPath = (cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () {
+    let cacheDependenciesPaths;
+    // memoize unglobbed paths to avoid traversing FS
+    const memoized = exports.memoizedCacheDependencies[cacheDependencyPath];
+    if (memoized) {
+        cacheDependenciesPaths = memoized;
+    }
+    else {
+        cacheDependenciesPaths = (yield glob
+            .create(cacheDependencyPath)
+            .then(globber => globber.glob())) || [''];
+        exports.memoizedCacheDependencies[cacheDependencyPath] = cacheDependenciesPaths;
+    }
     const existingDirectories = cacheDependenciesPaths
-        .map(cacheDependencyPath => cacheDependencyPathToProjectDirectory(cacheDependencyPath))
-        .filter(path => path !== null);
+        .map(cacheDependencyPath => path_1.default.dirname(cacheDependencyPath))
+        // uniq in order to do not traverse the same directories during the further processing
+        .filter((cachePath, i, result) => cachePath != null && result.indexOf(cachePath) === i)
+        .filter(directory => fs_1.default.existsSync(directory) && fs_1.default.lstatSync(directory).isDirectory());
     // if user explicitly pointed out some file, but it does not exist it is definitely
     // not he wanted, thus we should throw an error not trying to workaround with unexpected
     // result to the whole build
     if (existingDirectories.length === 0)
         throw Error('No existing directories found containing `cache-dependency-path`="${cacheDependencyPath}"');
-    // uniq in order to do not traverse the same directories during the further processing
-    return existingDirectories.filter((cachePath, i, result) => cachePath != null && result.indexOf(cachePath) === i);
+    return existingDirectories;
 });
 /**
- * Utility function to be used from within `map`
- * Finds the cache directories configured for the project directory
- * @param packageManagerInfo - an object having getCacheFolderPath method specific to given PM
- * @param projectDirectory - the string pointing out to a project dir (i.e. directory with its own .yarnrc)
- * @return list of directories to be cached according to the project configuration in the directory
- */
-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;
-});
-/**
- * Top-entry function to find the cache directories configured for the repo if cache-dependency-path is not empty
+ * Finds the cache directories configured for the repo if cache-dependency-path is not empty
  * @param packageManagerInfo - an object having getCacheFolderPath method specific to given PM
  * @param cacheDependencyPath - either a single string or multiline string with possible glob patterns
  *                              expected to be the result of `core.getInput('cache-dependency-path')`
  * @return list of files on which the cache depends
  */
-const cacheDependencyPathToCacheFoldersPaths = (packageManagerInfo, cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () {
-    const projectDirectories = yield cacheDependencyPathToProjectsDirectories(cacheDependencyPath);
-    const cacheFoldersPaths = yield Promise.all(projectDirectories.map(projectDirectory => projectDirectoryToCacheFolderPath(packageManagerInfo, projectDirectory)));
+const getCacheDirectoriesFromCacheDependencyPath = (packageManagerInfo, cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () {
+    const projectDirectories = yield getProjectDirectoriesFromCacheDependencyPath(cacheDependencyPath);
+    const cacheFoldersPaths = yield Promise.all(projectDirectories.map(projectDirectory => packageManagerInfo
+        .getCacheFolderPath(projectDirectory)
+        .then(cacheFolderPath => {
+        core.debug(`${packageManagerInfo.name}'s cache folder "${cacheFolderPath}" configured for the directory "${projectDirectory}"`);
+        return cacheFolderPath;
+    })));
     // uniq in order to do not cache the same directories twice
     return cacheFoldersPaths.filter((cachePath, i, result) => result.indexOf(cachePath) === i);
 });
 /**
- * Top-entry function to find the cache directories configured for the repo if cache-dependency-path is empty
+ * Finds the cache directories configured for the repo ignoring cache-dependency-path
  * @param packageManagerInfo - an object having getCacheFolderPath method specific to given PM
  * @return list of files on which the cache depends
  */
-const cacheFoldersPathsForRoot = (packageManagerInfo) => __awaiter(void 0, void 0, void 0, function* () {
+const getCacheDirectoriesForRootProject = (packageManagerInfo) => __awaiter(void 0, void 0, void 0, function* () {
     const cacheFolderPath = yield packageManagerInfo.getCacheFolderPath();
     core.debug(`${packageManagerInfo.name}'s cache folder "${cacheFolderPath}" configured for the root directory`);
     return [cacheFolderPath];
 });
 /**
- * Main function to find the cache directories configured for the repo
+ * A function to find the cache directories configured for the repo
  * currently it handles only the case of PM=yarn && cacheDependencyPath is not empty
  * @param packageManagerInfo - an object having getCacheFolderPath method specific to given PM
  * @param cacheDependencyPath - either a single string or multiline string with possible glob patterns
  *                              expected to be the result of `core.getInput('cache-dependency-path')`
  * @return list of files on which the cache depends
  */
-const getCacheDirectoriesPaths = (packageManagerInfo, cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () {
-    // TODO: multiple directories limited to yarn so far
-    return packageManagerInfo === exports.supportedPackageManagers.yarn
-        ? cacheDependencyPathToCacheFoldersPaths(packageManagerInfo, cacheDependencyPath)
-        : cacheFoldersPathsForRoot(packageManagerInfo);
+const getCacheDirectories = (packageManagerInfo, cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () {
+    // For yarn, if cacheDependencyPath is set, ask information about cache folders in each project
+    // folder satisfied by cacheDependencyPath https://github.com/actions/setup-node/issues/488
+    if (packageManagerInfo.name === 'yarn' && cacheDependencyPath) {
+        return getCacheDirectoriesFromCacheDependencyPath(packageManagerInfo, cacheDependencyPath);
+    }
+    return getCacheDirectoriesForRootProject(packageManagerInfo);
 });
-exports.getCacheDirectoriesPaths = getCacheDirectoriesPaths;
+exports.getCacheDirectories = getCacheDirectories;
 function isGhes() {
     const ghUrl = new URL(process.env['GITHUB_SERVER_URL'] || 'https://github.com');
     return ghUrl.hostname.toUpperCase() !== 'GITHUB.COM';
diff --git a/src/cache-restore.ts b/src/cache-restore.ts
index 801d525c..cddc4127 100644
--- a/src/cache-restore.ts
+++ b/src/cache-restore.ts
@@ -6,7 +6,7 @@ import fs from 'fs';
 
 import {State} from './constants';
 import {
-  getCacheDirectoriesPaths,
+  getCacheDirectories,
   getPackageManagerInfo,
   PackageManagerInfo
 } from './cache-utils';
@@ -21,7 +21,7 @@ export const restoreCache = async (
   }
   const platform = process.env.RUNNER_OS;
 
-  const cachePaths = await getCacheDirectoriesPaths(
+  const cachePaths = await getCacheDirectories(
     packageManagerInfo,
     cacheDependencyPath
   );
diff --git a/src/cache-save.ts b/src/cache-save.ts
index a54a741d..6b53a904 100644
--- a/src/cache-save.ts
+++ b/src/cache-save.ts
@@ -1,7 +1,7 @@
 import * as core from '@actions/core';
 import * as cache from '@actions/cache';
 import {State} from './constants';
-import {getCacheDirectoriesPaths, getPackageManagerInfo} from './cache-utils';
+import {getCacheDirectories, getPackageManagerInfo} from './cache-utils';
 
 // Catch and log any unhandled exceptions.  These exceptions can leak out of the uploadChunk method in
 // @actions/toolkit when a failed upload closes the file descriptor causing any in-process reads to
@@ -33,7 +33,7 @@ const cachePackages = async (packageManager: string) => {
   // TODO: core.getInput has a bug - it can return undefined despite its definition (tests only?)
   //       export declare function getInput(name: string, options?: InputOptions): string;
   const cacheDependencyPath = core.getInput('cache-dependency-path') || '';
-  const cachePaths = await getCacheDirectoriesPaths(
+  const cachePaths = await getCacheDirectories(
     packageManagerInfo,
     cacheDependencyPath
   );
diff --git a/src/cache-utils.ts b/src/cache-utils.ts
index 4fc69717..2d244b65 100644
--- a/src/cache-utils.ts
+++ b/src/cache-utils.ts
@@ -16,19 +16,13 @@ interface SupportedPackageManagers {
   pnpm: PackageManagerInfo;
   yarn: PackageManagerInfo;
 }
-
-// for testing purposes
-export const npmGetCacheFolderCommand = 'npm config get cache';
-export const pnpmGetCacheFolderCommand = 'pnpm store path --silent';
-export const yarn1GetCacheFolderCommand = 'yarn cache dir';
-export const yarn2GetCacheFolderCommand = 'yarn config get cacheFolder';
 export const supportedPackageManagers: SupportedPackageManagers = {
   npm: {
     name: 'npm',
     lockFilePatterns: ['package-lock.json', 'npm-shrinkwrap.json', 'yarn.lock'],
     getCacheFolderPath: () =>
-      getCommandOutputGuarded(
-        npmGetCacheFolderCommand,
+      getCommandOutputNotEmpty(
+        'npm config get cache',
         'Could not get npm cache folder path'
       )
   },
@@ -36,8 +30,8 @@ export const supportedPackageManagers: SupportedPackageManagers = {
     name: 'pnpm',
     lockFilePatterns: ['pnpm-lock.yaml'],
     getCacheFolderPath: () =>
-      getCommandOutputGuarded(
-        pnpmGetCacheFolderCommand,
+      getCommandOutputNotEmpty(
+        'pnpm store path --silent',
         'Could not get pnpm cache folder path'
       )
   },
@@ -45,19 +39,21 @@ export const supportedPackageManagers: SupportedPackageManagers = {
     name: 'yarn',
     lockFilePatterns: ['yarn.lock'],
     getCacheFolderPath: async projectDir => {
-      const yarnVersion = await getCommandOutputGuarded(
+      const yarnVersion = await getCommandOutputNotEmpty(
         `yarn --version`,
         'Could not retrieve version of yarn',
         projectDir
       );
 
       core.debug(
-        `Consumed yarn version is ${yarnVersion} (working dir: "${projectDir}")`
+        `Consumed yarn version is ${yarnVersion} (working dir: "${
+          projectDir || ''
+        }")`
       );
 
       const stdOut = yarnVersion.startsWith('1.')
-        ? await getCommandOutput(yarn1GetCacheFolderCommand, projectDir)
-        : await getCommandOutput(yarn2GetCacheFolderCommand, projectDir);
+        ? await getCommandOutput('yarn cache dir', projectDir)
+        : await getCommandOutput('yarn config get cacheFolder', projectDir);
 
       if (!stdOut) {
         throw new Error(
@@ -89,7 +85,7 @@ export const getCommandOutput = async (
   return stdout.trim();
 };
 
-export const getCommandOutputGuarded = async (
+export const getCommandOutputNotEmpty = async (
   toolCommand: string,
   error: string,
   cwd?: string
@@ -117,66 +113,7 @@ export const getPackageManagerInfo = async (packageManager: string) => {
  * glob expanding memoized because it involves potentially very deep
  * traversing through the directories tree
  */
-export const expandedPatternsMemoized: Record<string, string[]> = {};
-/**
- * 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<string[]> => {
-  const memoized = expandedPatternsMemoized[pattern];
-  if (memoized) return Promise.resolve(memoized);
-  const globber = await glob.create(pattern);
-  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<string[]> => {
-  const multilinePaths = cacheDependencyPath
-    .split(/\r?\n/)
-    .map(path => path.trim())
-    .filter(path => Boolean(path));
-  const expandedPathsPromises: Promise<string[]>[] = multilinePaths.map(path =>
-    path.includes('*') ? globPatternToArray(path) : Promise.resolve([path])
-  );
-  const expandedPaths: string[][] = await Promise.all(expandedPathsPromises);
-  return expandedPaths.length === 0 ? [''] : expandedPaths.flat();
-};
-
-/**
- * Converts dependency file path to the directory it resides in and ensures the directory exists
- * @param cacheDependencyPath - a file name path
- * @return either directory containing the file or null
- */
-const cacheDependencyPathToProjectDirectory = (
-  cacheDependencyPath: string
-): 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 for cache-dependency-path: "${cacheDependencyPath}", will be skipped`
-    );
-    return null;
-  }
-};
-
+export const memoizedCacheDependencies: Record<string, string[]> = {};
 /**
  * Expands (converts) the string input `cache-dependency-path` to list of directories that
  * may be project roots
@@ -184,18 +121,33 @@ const cacheDependencyPathToProjectDirectory = (
  *                              expected to be the result of `core.getInput('cache-dependency-path')`
  * @return list of directories and possible
  */
-const cacheDependencyPathToProjectsDirectories = async (
+const getProjectDirectoriesFromCacheDependencyPath = async (
   cacheDependencyPath: string
 ): Promise<string[]> => {
-  const cacheDependenciesPaths = await expandCacheDependencyPath(
-    cacheDependencyPath
-  );
+  let cacheDependenciesPaths: string[];
+
+  // memoize unglobbed paths to avoid traversing FS
+  const memoized = memoizedCacheDependencies[cacheDependencyPath];
+  if (memoized) {
+    cacheDependenciesPaths = memoized;
+  } else {
+    cacheDependenciesPaths = (await glob
+      .create(cacheDependencyPath)
+      .then(globber => globber.glob())) || [''];
+    memoizedCacheDependencies[cacheDependencyPath] = cacheDependenciesPaths;
+  }
 
   const existingDirectories: string[] = cacheDependenciesPaths
-    .map(cacheDependencyPath =>
-      cacheDependencyPathToProjectDirectory(cacheDependencyPath)
+    .map(cacheDependencyPath => path.dirname(cacheDependencyPath))
+    // uniq in order to do not traverse the same directories during the further processing
+    .filter(
+      (cachePath, i, result) =>
+        cachePath != null && result.indexOf(cachePath) === i
     )
-    .filter(path => path !== null) as string[];
+    .filter(
+      directory =>
+        fs.existsSync(directory) && fs.lstatSync(directory).isDirectory()
+    ) as string[];
 
   // if user explicitly pointed out some file, but it does not exist it is definitely
   // not he wanted, thus we should throw an error not trying to workaround with unexpected
@@ -205,50 +157,33 @@ const cacheDependencyPathToProjectsDirectories = async (
       'No existing directories found containing `cache-dependency-path`="${cacheDependencyPath}"'
     );
 
-  // uniq in order to do not traverse the same directories during the further processing
-  return existingDirectories.filter(
-    (cachePath, i, result) =>
-      cachePath != null && result.indexOf(cachePath) === i
-  );
+  return existingDirectories;
 };
 
 /**
- * Utility function to be used from within `map`
- * Finds the cache directories configured for the project directory
- * @param packageManagerInfo - an object having getCacheFolderPath method specific to given PM
- * @param projectDirectory - the string pointing out to a project dir (i.e. directory with its own .yarnrc)
- * @return list of directories to be cached according to the project configuration in the directory
- */
-const projectDirectoryToCacheFolderPath = async (
-  packageManagerInfo: PackageManagerInfo,
-  projectDirectory: string
-): Promise<string> => {
-  const cacheFolderPath = await packageManagerInfo.getCacheFolderPath(
-    projectDirectory
-  );
-  core.debug(
-    `${packageManagerInfo.name}'s cache folder "${cacheFolderPath}" configured for the directory "${projectDirectory}"`
-  );
-  return cacheFolderPath;
-};
-
-/**
- * Top-entry function to find the cache directories configured for the repo if cache-dependency-path is not empty
+ * Finds the cache directories configured for the repo if cache-dependency-path is not empty
  * @param packageManagerInfo - an object having getCacheFolderPath method specific to given PM
  * @param cacheDependencyPath - either a single string or multiline string with possible glob patterns
  *                              expected to be the result of `core.getInput('cache-dependency-path')`
  * @return list of files on which the cache depends
  */
-const cacheDependencyPathToCacheFoldersPaths = async (
+const getCacheDirectoriesFromCacheDependencyPath = async (
   packageManagerInfo: PackageManagerInfo,
   cacheDependencyPath: string
 ): Promise<string[]> => {
-  const projectDirectories = await cacheDependencyPathToProjectsDirectories(
+  const projectDirectories = await getProjectDirectoriesFromCacheDependencyPath(
     cacheDependencyPath
   );
   const cacheFoldersPaths = await Promise.all(
     projectDirectories.map(projectDirectory =>
-      projectDirectoryToCacheFolderPath(packageManagerInfo, projectDirectory)
+      packageManagerInfo
+        .getCacheFolderPath(projectDirectory)
+        .then(cacheFolderPath => {
+          core.debug(
+            `${packageManagerInfo.name}'s cache folder "${cacheFolderPath}" configured for the directory "${projectDirectory}"`
+          );
+          return cacheFolderPath;
+        })
     )
   );
   // uniq in order to do not cache the same directories twice
@@ -258,11 +193,11 @@ const cacheDependencyPathToCacheFoldersPaths = async (
 };
 
 /**
- * Top-entry function to find the cache directories configured for the repo if cache-dependency-path is empty
+ * Finds the cache directories configured for the repo ignoring cache-dependency-path
  * @param packageManagerInfo - an object having getCacheFolderPath method specific to given PM
  * @return list of files on which the cache depends
  */
-const cacheFoldersPathsForRoot = async (
+const getCacheDirectoriesForRootProject = async (
   packageManagerInfo: PackageManagerInfo
 ): Promise<string[]> => {
   const cacheFolderPath = await packageManagerInfo.getCacheFolderPath();
@@ -273,24 +208,27 @@ const cacheFoldersPathsForRoot = async (
 };
 
 /**
- * Main function to find the cache directories configured for the repo
+ * A function to find the cache directories configured for the repo
  * currently it handles only the case of PM=yarn && cacheDependencyPath is not empty
  * @param packageManagerInfo - an object having getCacheFolderPath method specific to given PM
  * @param cacheDependencyPath - either a single string or multiline string with possible glob patterns
  *                              expected to be the result of `core.getInput('cache-dependency-path')`
  * @return list of files on which the cache depends
  */
-export const getCacheDirectoriesPaths = async (
+export const getCacheDirectories = async (
   packageManagerInfo: PackageManagerInfo,
   cacheDependencyPath: string
-): Promise<string[]> =>
-  // TODO: multiple directories limited to yarn so far
-  packageManagerInfo === supportedPackageManagers.yarn
-    ? cacheDependencyPathToCacheFoldersPaths(
-        packageManagerInfo,
-        cacheDependencyPath
-      )
-    : cacheFoldersPathsForRoot(packageManagerInfo);
+): Promise<string[]> => {
+  // For yarn, if cacheDependencyPath is set, ask information about cache folders in each project
+  // folder satisfied by cacheDependencyPath https://github.com/actions/setup-node/issues/488
+  if (packageManagerInfo.name === 'yarn' && cacheDependencyPath) {
+    return getCacheDirectoriesFromCacheDependencyPath(
+      packageManagerInfo,
+      cacheDependencyPath
+    );
+  }
+  return getCacheDirectoriesForRootProject(packageManagerInfo);
+};
 
 export function isGhes(): boolean {
   const ghUrl = new URL(