diff --git a/README.md b/README.md index 362bd89d..ae101b50 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,8 @@ The action defaults to search for the dependency file (`package-lock.json`, `npm **Note:** The action does not cache `node_modules` +Use `cache-invalidate-after-days` to change the default fallback cache invalidation of every 120 days. Set to 0 to deactivate. + See the examples of using cache for `yarn`/`pnpm` and `cache-dependency-path` input in the [Advanced usage](docs/advanced-usage.md#caching-packages-data) guide. **Caching npm dependencies:** diff --git a/__tests__/cache-restore.test.ts b/__tests__/cache-restore.test.ts index df9170ad..8faee574 100644 --- a/__tests__/cache-restore.test.ts +++ b/__tests__/cache-restore.test.ts @@ -132,13 +132,13 @@ describe('cache-restore', () => { } }); - await restoreCache(packageManager); + await restoreCache(packageManager, undefined, '0'); expect(hashFilesSpy).toHaveBeenCalled(); expect(infoSpy).toHaveBeenCalledWith( - `Cache restored from key: node-cache-${platform}-${packageManager}-${fileHash}` + `Cache restored from key: ${platform}-0-setup-node-${packageManager}-${fileHash}` ); expect(infoSpy).not.toHaveBeenCalledWith( - `${packageManager} cache is not found` + `Cache not found for input keys: ${platform}-0-setup-node-${packageManager}-${fileHash}, ${platform}-0-setup-node-${packageManager}-` ); expect(setOutputSpy).toHaveBeenCalledWith('cache-hit', true); } @@ -163,10 +163,10 @@ describe('cache-restore', () => { }); restoreCacheSpy.mockImplementationOnce(() => undefined); - await restoreCache(packageManager); + await restoreCache(packageManager, undefined, '0'); expect(hashFilesSpy).toHaveBeenCalled(); expect(infoSpy).toHaveBeenCalledWith( - `${packageManager} cache is not found` + `Cache not found for input keys: ${platform}-0-setup-node-${packageManager}-${fileHash}, ${platform}-0-setup-node-${packageManager}-` ); expect(setOutputSpy).toHaveBeenCalledWith('cache-hit', false); } diff --git a/action.yml b/action.yml index b22de1ef..13473abf 100644 --- a/action.yml +++ b/action.yml @@ -25,6 +25,8 @@ inputs: description: 'Used to specify a package manager for caching in the default directory. Supported values: npm, yarn, pnpm.' cache-dependency-path: description: 'Used to specify the path to a dependency file: package-lock.json, yarn.lock, etc. Supports wildcards or a list of file names for caching multiple dependencies.' + cache-invalidate-after-days: + description: 'Used to control how often the fallback cache is invalidated automatically.' # TODO: add input to control forcing to pull from cloud or dist. # escape valve for someone having issues or needing the absolute latest which isn't cached yet outputs: diff --git a/dist/setup/index.js b/dist/setup/index.js index 9b813965..4d054da8 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -71138,7 +71138,7 @@ 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 restoreCache = (packageManager, cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () { +const restoreCache = (packageManager, cacheDependencyPath, cacheInvalidateAfterDays) => __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`); @@ -71152,13 +71152,21 @@ 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}-${fileHash}`; + const numericCacheInvalidateAfterDays = cacheInvalidateAfterDays && cacheInvalidateAfterDays === '0' + ? 0 + : (parseInt(cacheInvalidateAfterDays || '', 10) || 120); + const timedInvalidationPrefix = numericCacheInvalidateAfterDays + ? Math.floor(Date.now() / (1000 * 60 * 60 * 24 * numericCacheInvalidateAfterDays)) % 1000 // % 1000 to get a rolling prefix between 0 and 999 rather than a possibly infinitely large + : 0; + const keyPrefix = `${platform}-${timedInvalidationPrefix}-setup-node-`; + const primaryKey = `${keyPrefix}${packageManager}-${fileHash}`; + const restoreKeys = [`${keyPrefix}${packageManager}-`]; core.debug(`primary key is ${primaryKey}`); core.saveState(constants_1.State.CachePrimaryKey, primaryKey); - const cacheKey = yield cache.restoreCache([cachePath], primaryKey); + const cacheKey = yield cache.restoreCache([cachePath], primaryKey, restoreKeys); core.setOutput('cache-hit', Boolean(cacheKey)); if (!cacheKey) { - core.info(`${packageManager} cache is not found`); + core.info(`Cache not found for input keys: ${[primaryKey, ...restoreKeys].join(', ')}`); return; } core.saveState(constants_1.State.CacheMatchedKey, cacheKey); @@ -72089,7 +72097,8 @@ function run() { } if (cache && cache_utils_1.isCacheFeatureAvailable()) { const cacheDependencyPath = core.getInput('cache-dependency-path'); - yield cache_restore_1.restoreCache(cache, cacheDependencyPath); + const cacheInvalidateAfterDays = core.getInput('cache-invalidate-after-days'); + yield cache_restore_1.restoreCache(cache, cacheDependencyPath, cacheInvalidateAfterDays); } const matchersPath = path.join(__dirname, '../..', '.github'); core.info(`##[add-matcher]${path.join(matchersPath, 'tsc.json')}`); diff --git a/src/cache-restore.ts b/src/cache-restore.ts index c6f14ad7..5b241af8 100644 --- a/src/cache-restore.ts +++ b/src/cache-restore.ts @@ -13,7 +13,8 @@ import { export const restoreCache = async ( packageManager: string, - cacheDependencyPath?: string + cacheDependencyPath?: string, + cacheInvalidateAfterDays?: string ) => { const packageManagerInfo = await getPackageManagerInfo(packageManager); if (!packageManagerInfo) { @@ -35,17 +36,23 @@ export const restoreCache = async ( 'Some specified paths were not resolved, unable to cache dependencies.' ); } - - const primaryKey = `node-cache-${platform}-${packageManager}-${fileHash}`; + const numericCacheInvalidateAfterDays = cacheInvalidateAfterDays && cacheInvalidateAfterDays === '0' + ? 0 + : (parseInt(cacheInvalidateAfterDays || '', 10) || 120) + const timedInvalidationPrefix = numericCacheInvalidateAfterDays + ? Math.floor(Date.now() / (1000 * 60 * 60 * 24 * numericCacheInvalidateAfterDays)) % 1000 // % 1000 to get a rolling prefix between 0 and 999 rather than a possibly infinitely large + : 0; + const keyPrefix = `${platform}-${timedInvalidationPrefix}-setup-node-`; + const primaryKey = `${keyPrefix}${packageManager}-${fileHash}`; + const restoreKeys = [`${keyPrefix}${packageManager}-`]; core.debug(`primary key is ${primaryKey}`); - core.saveState(State.CachePrimaryKey, primaryKey); - const cacheKey = await cache.restoreCache([cachePath], primaryKey); + const cacheKey = await cache.restoreCache([cachePath], primaryKey, restoreKeys); core.setOutput('cache-hit', Boolean(cacheKey)); if (!cacheKey) { - core.info(`${packageManager} cache is not found`); + core.info(`Cache not found for input keys: ${[primaryKey, ...restoreKeys].join(', ')}`); return; } diff --git a/src/main.ts b/src/main.ts index 90cd1d9d..7708a606 100644 --- a/src/main.ts +++ b/src/main.ts @@ -61,7 +61,8 @@ export async function run() { if (cache && isCacheFeatureAvailable()) { const cacheDependencyPath = core.getInput('cache-dependency-path'); - await restoreCache(cache, cacheDependencyPath); + const cacheInvalidateAfterDays = core.getInput('cache-invalidate-after-days'); + await restoreCache(cache, cacheDependencyPath, cacheInvalidateAfterDays); } const matchersPath = path.join(__dirname, '../..', '.github');