diff --git a/__tests__/cache-restore.test.ts b/__tests__/cache-restore.test.ts index 59135452c..b7bd5f152 100644 --- a/__tests__/cache-restore.test.ts +++ b/__tests__/cache-restore.test.ts @@ -45,6 +45,7 @@ describe('cache-restore', () => { } } + let inputs = {} as any; let saveStateSpy: jest.SpyInstance; let infoSpy: jest.SpyInstance; let debugSpy: jest.SpyInstance; @@ -52,6 +53,7 @@ describe('cache-restore', () => { let getCommandOutputSpy: jest.SpyInstance; let restoreCacheSpy: jest.SpyInstance; let hashFilesSpy: jest.SpyInstance; + let inSpy: jest.SpyInstance; beforeEach(() => { // core @@ -67,6 +69,10 @@ describe('cache-restore', () => { saveStateSpy = jest.spyOn(core, 'saveState'); saveStateSpy.mockImplementation(() => undefined); + inputs = {}; + inSpy = jest.spyOn(core, 'getInput'); + inSpy.mockImplementation(name => inputs[name]); + // glob hashFilesSpy = jest.spyOn(glob, 'hashFiles'); hashFilesSpy.mockImplementation((pattern: string) => { @@ -131,11 +137,12 @@ describe('cache-restore', () => { return findCacheFolder(command); } }); + inputs['node-version'] = 'vABC'; await restoreCache(packageManager); expect(hashFilesSpy).toHaveBeenCalled(); expect(infoSpy).toHaveBeenCalledWith( - `Cache restored from key: node-cache-${platform}-${packageManager}-v2-${fileHash}` + `Cache restored from key: node-cache-${platform}-${packageManager}-v2-vABC-${fileHash}` ); expect(infoSpy).not.toHaveBeenCalledWith( `${packageManager} cache is not found` diff --git a/dist/cache-save/index.js b/dist/cache-save/index.js index aad770848..f682d7d89 100644 --- a/dist/cache-save/index.js +++ b/dist/cache-save/index.js @@ -59248,7 +59248,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.getCacheDirectoryPath = exports.getPackageManagerInfo = exports.getPackageManagerVersion = exports.getPackageManagerCommandOutput = exports.getPackageManagerWorkingDir = exports.getCommandOutput = exports.supportedPackageManagers = void 0; +exports.isYarn3OfflineCache = exports.isCacheFeatureAvailable = exports.isGhes = exports.getCacheDirectoryPath = exports.getPackageManagerInfo = exports.getPackageManagerVersion = exports.getPackageManagerCommandOutput = exports.getPackageManagerWorkingDir = exports.getCommandOutput = exports.supportedPackageManagers = void 0; const core = __importStar(__nccwpck_require__(2186)); const exec = __importStar(__nccwpck_require__(1514)); const cache = __importStar(__nccwpck_require__(7799)); @@ -59356,6 +59356,30 @@ function isCacheFeatureAvailable() { return false; } exports.isCacheFeatureAvailable = isCacheFeatureAvailable; +// a hack for further possible compatibility issues and a syntax sugar +const getYarn3ProjectDirectoryPath = () => exports.getCacheDirectoryPath(exports.supportedPackageManagers.yarn2, 'yarn'); +// see https://yarnpkg.com/features/offline-cache +let isYarn3OfflineCacheMemoized = null; +const isYarn3OfflineCache = (packageManagerInfo) => __awaiter(void 0, void 0, void 0, function* () { + if (isYarn3OfflineCacheMemoized !== null) + return isYarn3OfflineCacheMemoized; + isYarn3OfflineCacheMemoized = false; + if (packageManagerInfo !== exports.supportedPackageManagers.yarn2) + return isYarn3OfflineCacheMemoized; + const yarnDir = yield getYarn3ProjectDirectoryPath(); + const workDir = yarnDir || process.env.GITHUB_WORKSPACE; + const yarnCacheFile = path_1.default.join(workDir, '.yarn', 'cache'); + if (fs_1.default.existsSync(yarnCacheFile) && fs_1.default.lstatSync(yarnCacheFile).isDirectory()) + return isYarn3OfflineCacheMemoized; + const yarnRcPath = path_1.default.join(workDir, '.yarnrc.yml'); + if (!(fs_1.default.existsSync(yarnRcPath) && fs_1.default.lstatSync(yarnRcPath).isFile())) + return isYarn3OfflineCacheMemoized; + const yarnRc = fs_1.default.readFileSync(path_1.default.join(workDir, '.yarnrc.yml')).toString(); + const globalCache = yarnRc.match(/^enableGlobalCache:\s*true/m); + isYarn3OfflineCacheMemoized = !globalCache; + return isYarn3OfflineCacheMemoized; +}); +exports.isYarn3OfflineCache = isYarn3OfflineCache; /***/ }), diff --git a/dist/setup/index.js b/dist/setup/index.js index eae17a848..d2878728e 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -71138,12 +71138,14 @@ const path_1 = __importDefault(__nccwpck_require__(1017)); const fs_1 = __importDefault(__nccwpck_require__(7147)); const constants_1 = __nccwpck_require__(9042); const cache_utils_1 = __nccwpck_require__(1678); +const util_1 = __nccwpck_require__(2629); const restoreCache = (packageManager, cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () { const packageManagerInfo = yield cache_utils_1.getPackageManagerInfo(packageManager); if (!packageManagerInfo) { throw new Error(`Caching for '${packageManager}' is not supported`); } const platform = process.env.RUNNER_OS; + const nodeVersion = util_1.resolveVersionInput(); const cachePath = yield cache_utils_1.getCacheDirectoryPath(packageManagerInfo, packageManager); const lockFilePath = cacheDependencyPath ? cacheDependencyPath @@ -71152,10 +71154,13 @@ const restoreCache = (packageManager, cacheDependencyPath) => __awaiter(void 0, if (!fileHash) { throw new Error('Some specified paths were not resolved, unable to cache dependencies.'); } - const primaryKey = `node-cache-${platform}-${packageManager}-v2-${fileHash}`; + const keyPrefix = `node-cache-${platform}-${packageManager}-v2-${nodeVersion}`; + const primaryKey = `${keyPrefix}-${fileHash}`; core.debug(`primary key is ${primaryKey}`); core.saveState(constants_1.State.CachePrimaryKey, primaryKey); - const cacheKey = yield cache.restoreCache([cachePath], primaryKey); + const cacheKey = (yield cache_utils_1.isYarn3OfflineCache(packageManagerInfo)) + ? yield cache.restoreCache([cachePath], primaryKey, [keyPrefix]) + : yield cache.restoreCache([cachePath], primaryKey); core.setOutput('cache-hit', Boolean(cacheKey)); if (!cacheKey) { core.info(`${packageManager} cache is not found`); @@ -71216,7 +71221,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.getCacheDirectoryPath = exports.getPackageManagerInfo = exports.getPackageManagerVersion = exports.getPackageManagerCommandOutput = exports.getPackageManagerWorkingDir = exports.getCommandOutput = exports.supportedPackageManagers = void 0; +exports.isYarn3OfflineCache = exports.isCacheFeatureAvailable = exports.isGhes = exports.getCacheDirectoryPath = exports.getPackageManagerInfo = exports.getPackageManagerVersion = exports.getPackageManagerCommandOutput = exports.getPackageManagerWorkingDir = exports.getCommandOutput = exports.supportedPackageManagers = void 0; const core = __importStar(__nccwpck_require__(2186)); const exec = __importStar(__nccwpck_require__(1514)); const cache = __importStar(__nccwpck_require__(7799)); @@ -71324,6 +71329,30 @@ function isCacheFeatureAvailable() { return false; } exports.isCacheFeatureAvailable = isCacheFeatureAvailable; +// a hack for further possible compatibility issues and a syntax sugar +const getYarn3ProjectDirectoryPath = () => exports.getCacheDirectoryPath(exports.supportedPackageManagers.yarn2, 'yarn'); +// see https://yarnpkg.com/features/offline-cache +let isYarn3OfflineCacheMemoized = null; +const isYarn3OfflineCache = (packageManagerInfo) => __awaiter(void 0, void 0, void 0, function* () { + if (isYarn3OfflineCacheMemoized !== null) + return isYarn3OfflineCacheMemoized; + isYarn3OfflineCacheMemoized = false; + if (packageManagerInfo !== exports.supportedPackageManagers.yarn2) + return isYarn3OfflineCacheMemoized; + const yarnDir = yield getYarn3ProjectDirectoryPath(); + const workDir = yarnDir || process.env.GITHUB_WORKSPACE; + const yarnCacheFile = path_1.default.join(workDir, '.yarn', 'cache'); + if (fs_1.default.existsSync(yarnCacheFile) && fs_1.default.lstatSync(yarnCacheFile).isDirectory()) + return isYarn3OfflineCacheMemoized; + const yarnRcPath = path_1.default.join(workDir, '.yarnrc.yml'); + if (!(fs_1.default.existsSync(yarnRcPath) && fs_1.default.lstatSync(yarnRcPath).isFile())) + return isYarn3OfflineCacheMemoized; + const yarnRc = fs_1.default.readFileSync(path_1.default.join(workDir, '.yarnrc.yml')).toString(); + const globalCache = yarnRc.match(/^enableGlobalCache:\s*true/m); + isYarn3OfflineCacheMemoized = !globalCache; + return isYarn3OfflineCacheMemoized; +}); +exports.isYarn3OfflineCache = isYarn3OfflineCache; /***/ }), @@ -72064,7 +72093,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { Object.defineProperty(exports, "__esModule", ({ value: true })); exports.run = void 0; const core = __importStar(__nccwpck_require__(2186)); -const fs_1 = __importDefault(__nccwpck_require__(7147)); const os_1 = __importDefault(__nccwpck_require__(2037)); const auth = __importStar(__nccwpck_require__(7573)); const path = __importStar(__nccwpck_require__(1017)); @@ -72079,7 +72107,7 @@ function run() { // Version is optional. If supplied, install / use from the tool cache // If not supplied then task is still used to setup proxy, auth, etc... // - const version = resolveVersionInput(); + const version = util_1.resolveVersionInput(); let arch = core.getInput('architecture'); const cache = core.getInput('cache'); // if architecture supplied but node-version is not @@ -72126,25 +72154,6 @@ function run() { }); } exports.run = run; -function resolveVersionInput() { - let version = core.getInput('node-version'); - const versionFileInput = core.getInput('node-version-file'); - if (version && versionFileInput) { - core.warning('Both node-version and node-version-file inputs are specified, only node-version will be used'); - } - if (version) { - return version; - } - if (versionFileInput) { - const versionFilePath = path.join(process.env.GITHUB_WORKSPACE, versionFileInput); - if (!fs_1.default.existsSync(versionFilePath)) { - throw new Error(`The specified node version file at: ${versionFilePath} does not exist`); - } - version = util_1.parseNodeVersionFile(fs_1.default.readFileSync(versionFilePath, 'utf8')); - core.info(`Resolved ${versionFileInput} as ${version}`); - } - return version; -} /***/ }), @@ -72182,10 +72191,15 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.printEnvDetailsAndSetOutput = exports.parseNodeVersionFile = void 0; +exports.resolveVersionInput = exports.printEnvDetailsAndSetOutput = exports.parseNodeVersionFile = void 0; const core = __importStar(__nccwpck_require__(2186)); const exec = __importStar(__nccwpck_require__(1514)); +const path_1 = __importDefault(__nccwpck_require__(1017)); +const fs_1 = __importDefault(__nccwpck_require__(7147)); function parseNodeVersionFile(contents) { var _a, _b, _c; let nodeVersion; @@ -72245,6 +72259,31 @@ function getToolVersion(tool, options) { } }); } +let resolvedVersionInputMemoized = null; +function resolveVersionInput() { + if (resolvedVersionInputMemoized !== null) + return resolvedVersionInputMemoized; + let version = core.getInput('node-version'); + const versionFileInput = core.getInput('node-version-file'); + if (version && versionFileInput) { + core.warning('Both node-version and node-version-file inputs are specified, only node-version will be used'); + } + if (version) { + resolvedVersionInputMemoized = version; + return version; + } + if (versionFileInput) { + const versionFilePath = path_1.default.join(process.env.GITHUB_WORKSPACE, versionFileInput); + if (!fs_1.default.existsSync(versionFilePath)) { + throw new Error(`The specified node version file at: ${versionFilePath} does not exist`); + } + version = parseNodeVersionFile(fs_1.default.readFileSync(versionFilePath, 'utf8')); + core.info(`Resolved ${versionFileInput} as ${version}`); + } + resolvedVersionInputMemoized = version; + return version; +} +exports.resolveVersionInput = resolveVersionInput; /***/ }), diff --git a/src/cache-restore.ts b/src/cache-restore.ts index 0341c109f..fbb285b5e 100644 --- a/src/cache-restore.ts +++ b/src/cache-restore.ts @@ -8,8 +8,10 @@ import {State} from './constants'; import { getCacheDirectoryPath, getPackageManagerInfo, + isYarn3OfflineCache, PackageManagerInfo } from './cache-utils'; +import {resolveVersionInput} from './util'; export const restoreCache = async ( packageManager: string, @@ -20,6 +22,7 @@ export const restoreCache = async ( throw new Error(`Caching for '${packageManager}' is not supported`); } const platform = process.env.RUNNER_OS; + const nodeVersion = resolveVersionInput(); const cachePath = await getCacheDirectoryPath( packageManagerInfo, @@ -36,12 +39,16 @@ export const restoreCache = async ( ); } - const primaryKey = `node-cache-${platform}-${packageManager}-v2-${fileHash}`; + const keyPrefix = `node-cache-${platform}-${packageManager}-v2-${nodeVersion}`; + const primaryKey = `${keyPrefix}-${fileHash}`; core.debug(`primary key is ${primaryKey}`); core.saveState(State.CachePrimaryKey, primaryKey); - const cacheKey = await cache.restoreCache([cachePath], primaryKey); + const cacheKey = (await isYarn3OfflineCache(packageManagerInfo)) + ? await cache.restoreCache([cachePath], primaryKey, [keyPrefix]) + : await cache.restoreCache([cachePath], primaryKey); + core.setOutput('cache-hit', Boolean(cacheKey)); if (!cacheKey) { diff --git a/src/cache-utils.ts b/src/cache-utils.ts index ebd5144b1..c9a646487 100644 --- a/src/cache-utils.ts +++ b/src/cache-utils.ts @@ -150,3 +150,38 @@ export function isCacheFeatureAvailable(): boolean { return false; } + +// a hack for further possible compatibility issues and a syntax sugar +const getYarn3ProjectDirectoryPath = () => + getCacheDirectoryPath(supportedPackageManagers.yarn2, 'yarn'); + +// see https://yarnpkg.com/features/offline-cache +let isYarn3OfflineCacheMemoized: boolean | null = null; +export const isYarn3OfflineCache = async ( + packageManagerInfo: PackageManagerInfo +): Promise => { + if (isYarn3OfflineCacheMemoized !== null) return isYarn3OfflineCacheMemoized; + + isYarn3OfflineCacheMemoized = false; + + if (packageManagerInfo !== supportedPackageManagers.yarn2) + return isYarn3OfflineCacheMemoized; + + const yarnDir = await getYarn3ProjectDirectoryPath(); + + const workDir = yarnDir || process.env.GITHUB_WORKSPACE!; + + const yarnCacheFile = path.join(workDir, '.yarn', 'cache'); + if (fs.existsSync(yarnCacheFile) && fs.lstatSync(yarnCacheFile).isDirectory()) + return isYarn3OfflineCacheMemoized; + + const yarnRcPath = path.join(workDir, '.yarnrc.yml'); + if (!(fs.existsSync(yarnRcPath) && fs.lstatSync(yarnRcPath).isFile())) + return isYarn3OfflineCacheMemoized; + + const yarnRc = fs.readFileSync(path.join(workDir, '.yarnrc.yml')).toString(); + const globalCache = yarnRc.match(/^enableGlobalCache:\s*true/m); + + isYarn3OfflineCacheMemoized = !globalCache; + return isYarn3OfflineCacheMemoized; +}; diff --git a/src/main.ts b/src/main.ts index 90cd1d9d9..49c57011a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -8,7 +8,11 @@ import * as path from 'path'; import {restoreCache} from './cache-restore'; import {isCacheFeatureAvailable} from './cache-utils'; import {getNodejsDistribution} from './distributions/installer-factory'; -import {parseNodeVersionFile, printEnvDetailsAndSetOutput} from './util'; +import { + parseNodeVersionFile, + printEnvDetailsAndSetOutput, + resolveVersionInput +} from './util'; export async function run() { try { @@ -76,37 +80,3 @@ export async function run() { core.setFailed(err.message); } } - -function resolveVersionInput(): string { - let version = core.getInput('node-version'); - const versionFileInput = core.getInput('node-version-file'); - - if (version && versionFileInput) { - core.warning( - 'Both node-version and node-version-file inputs are specified, only node-version will be used' - ); - } - - if (version) { - return version; - } - - if (versionFileInput) { - const versionFilePath = path.join( - process.env.GITHUB_WORKSPACE!, - versionFileInput - ); - - if (!fs.existsSync(versionFilePath)) { - throw new Error( - `The specified node version file at: ${versionFilePath} does not exist` - ); - } - - version = parseNodeVersionFile(fs.readFileSync(versionFilePath, 'utf8')); - - core.info(`Resolved ${versionFileInput} as ${version}`); - } - - return version; -} diff --git a/src/util.ts b/src/util.ts index 60f2649c2..253edd5fb 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,5 +1,7 @@ import * as core from '@actions/core'; import * as exec from '@actions/exec'; +import path from 'path'; +import fs from 'fs'; export function parseNodeVersionFile(contents: string): string { let nodeVersion: string | undefined; @@ -61,3 +63,43 @@ async function getToolVersion(tool: string, options: string[]) { return ''; } } + +let resolvedVersionInputMemoized: string | null = null; +export function resolveVersionInput(): string { + if (resolvedVersionInputMemoized !== null) + return resolvedVersionInputMemoized; + + let version = core.getInput('node-version'); + const versionFileInput = core.getInput('node-version-file'); + + if (version && versionFileInput) { + core.warning( + 'Both node-version and node-version-file inputs are specified, only node-version will be used' + ); + } + + if (version) { + resolvedVersionInputMemoized = version; + return version; + } + + if (versionFileInput) { + const versionFilePath = path.join( + process.env.GITHUB_WORKSPACE!, + versionFileInput + ); + + if (!fs.existsSync(versionFilePath)) { + throw new Error( + `The specified node version file at: ${versionFilePath} does not exist` + ); + } + + version = parseNodeVersionFile(fs.readFileSync(versionFilePath, 'utf8')); + + core.info(`Resolved ${versionFileInput} as ${version}`); + } + + resolvedVersionInputMemoized = version; + return version; +}