From 5de08eab2b866c48fc19fb345ebac827900aa21f Mon Sep 17 00:00:00 2001
From: Sergey Dolin <dsame@github.com>
Date: Fri, 19 May 2023 19:57:43 +0200
Subject: [PATCH] refactor, introduce
 `cacheDependencyPathToProjectsDirectories`

it is necessary for the next PR related yarn optimization
---
 __tests__/cache-save.test.ts  |  60 +++++++++++++----
 __tests__/cache-utils.test.ts |  41 +++++++++---
 dist/cache-save/index.js      |  76 ++++++++++++++++-----
 dist/setup/index.js           |  76 ++++++++++++++++-----
 src/cache-utils.ts            | 121 ++++++++++++++++++++++++++--------
 5 files changed, 293 insertions(+), 81 deletions(-)

diff --git a/__tests__/cache-save.test.ts b/__tests__/cache-save.test.ts
index 0651138e..4fc99792 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 80e4a084..789d3ba6 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<Globber> =>
           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 6b045c6d..6fa2cf89 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 for cache-dependency-path: "${cacheDependencyPath}", will be skipped`);
+        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 7230bd04..57163eb8 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 for cache-dependency-path: "${cacheDependencyPath}", will be skipped`);
+        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 9c7f2827..2c41af60 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<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);
-  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<string[]> => {
@@ -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<string> => {
-  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 for cache-dependency-path: "${cacheDependencyPath}", will be skipped`
+    );
+    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<string[]> => {
+  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<string> => {
+  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<string[]> => {
   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<string[]> => {
-  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<string[]> => {
   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];
 };