Skip to content

Commit f01118b

Browse files
committed
Cache fallback with rolling time based expiration
1 parent c2ac33f commit f01118b

File tree

6 files changed

+40
-17
lines changed

6 files changed

+40
-17
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ The action defaults to search for the dependency file (`package-lock.json`, `npm
126126

127127
**Note:** The action does not cache `node_modules`
128128

129+
Use `cache-invalidate-after-days` to change the default fallback cache invalidation of every 120 days. Set to 0 to deactivate.
130+
129131
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.
130132

131133
**Caching npm dependencies:**

__tests__/cache-restore.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,13 +132,13 @@ describe('cache-restore', () => {
132132
}
133133
});
134134

135-
await restoreCache(packageManager, '');
135+
await restoreCache(packageManager, '', '0');
136136
expect(hashFilesSpy).toHaveBeenCalled();
137137
expect(infoSpy).toHaveBeenCalledWith(
138-
`Cache restored from key: node-cache-${platform}-${packageManager}-${fileHash}`
138+
`Cache restored from key: ${platform}-0-setup-node-${packageManager}-${fileHash}`
139139
);
140140
expect(infoSpy).not.toHaveBeenCalledWith(
141-
`${packageManager} cache is not found`
141+
`Cache not found for input keys: ${platform}-0-setup-node-${packageManager}-${fileHash}, ${platform}-0-setup-node-${packageManager}-`
142142
);
143143
expect(setOutputSpy).toHaveBeenCalledWith('cache-hit', true);
144144
}
@@ -163,10 +163,10 @@ describe('cache-restore', () => {
163163
});
164164

165165
restoreCacheSpy.mockImplementationOnce(() => undefined);
166-
await restoreCache(packageManager, '');
166+
await restoreCache(packageManager, '', '0');
167167
expect(hashFilesSpy).toHaveBeenCalled();
168168
expect(infoSpy).toHaveBeenCalledWith(
169-
`${packageManager} cache is not found`
169+
`Cache not found for input keys: ${platform}-0-setup-node-${packageManager}-${fileHash}, ${platform}-0-setup-node-${packageManager}-`
170170
);
171171
expect(setOutputSpy).toHaveBeenCalledWith('cache-hit', false);
172172
}

action.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ inputs:
2525
description: 'Used to specify a package manager for caching in the default directory. Supported values: npm, yarn, pnpm.'
2626
cache-dependency-path:
2727
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.'
28+
cache-invalidate-after-days:
29+
description: 'Used to control how often the fallback cache is invalidated automatically.'
2830
# TODO: add input to control forcing to pull from cloud or dist.
2931
# escape valve for someone having issues or needing the absolute latest which isn't cached yet
3032
outputs:

dist/setup/index.js

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93090,7 +93090,7 @@ const path_1 = __importDefault(__nccwpck_require__(1017));
9309093090
const fs_1 = __importDefault(__nccwpck_require__(7147));
9309193091
const constants_1 = __nccwpck_require__(9042);
9309293092
const cache_utils_1 = __nccwpck_require__(1678);
93093-
const restoreCache = (packageManager, cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () {
93093+
const restoreCache = (packageManager, cacheDependencyPath, cacheInvalidateAfterDays) => __awaiter(void 0, void 0, void 0, function* () {
9309493094
const packageManagerInfo = yield (0, cache_utils_1.getPackageManagerInfo)(packageManager);
9309593095
if (!packageManagerInfo) {
9309693096
throw new Error(`Caching for '${packageManager}' is not supported`);
@@ -93105,22 +93105,30 @@ const restoreCache = (packageManager, cacheDependencyPath) => __awaiter(void 0,
9310593105
if (!fileHash) {
9310693106
throw new Error('Some specified paths were not resolved, unable to cache dependencies.');
9310793107
}
93108-
const keyPrefix = `node-cache-${platform}-${packageManager}`;
93108+
const numericCacheInvalidateAfterDays = cacheInvalidateAfterDays && cacheInvalidateAfterDays === '0'
93109+
? 0
93110+
: (parseInt(cacheInvalidateAfterDays || '', 10) || 120);
93111+
const timedInvalidationPrefix = numericCacheInvalidateAfterDays
93112+
? 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
93113+
: 0;
93114+
const keyPrefixBase = `node-cache-${platform}-${packageManager}`;
93115+
const keyPrefix = `${keyPrefixBase}-${timedInvalidationPrefix}`;
9310993116
const primaryKey = `${keyPrefix}-${fileHash}`;
93117+
const restoreKeys = [`${keyPrefix}-`];
9311093118
core.debug(`primary key is ${primaryKey}`);
9311193119
core.saveState(constants_1.State.CachePrimaryKey, primaryKey);
9311293120
const isManagedByYarnBerry = yield (0, cache_utils_1.repoHasYarnBerryManagedDependencies)(packageManagerInfo, cacheDependencyPath);
9311393121
let cacheKey;
9311493122
if (isManagedByYarnBerry) {
9311593123
core.info('All dependencies are managed locally by yarn3, the previous cache can be used');
93116-
cacheKey = yield cache.restoreCache(cachePaths, primaryKey, [keyPrefix]);
93124+
cacheKey = yield cache.restoreCache(cachePaths, primaryKey, [keyPrefixBase]);
9311793125
}
9311893126
else {
93119-
cacheKey = yield cache.restoreCache(cachePaths, primaryKey);
93127+
cacheKey = yield cache.restoreCache(cachePaths, primaryKey, restoreKeys);
9312093128
}
9312193129
core.setOutput('cache-hit', Boolean(cacheKey));
9312293130
if (!cacheKey) {
93123-
core.info(`${packageManager} cache is not found`);
93131+
core.info(`Cache not found for input keys: ${[primaryKey, ...restoreKeys].join(', ')}`);
9312493132
return;
9312593133
}
9312693134
core.saveState(constants_1.State.CacheMatchedKey, cacheKey);
@@ -94251,7 +94259,8 @@ function run() {
9425194259
if (cache && (0, cache_utils_1.isCacheFeatureAvailable)()) {
9425294260
core.saveState(constants_1.State.CachePackageManager, cache);
9425394261
const cacheDependencyPath = core.getInput('cache-dependency-path');
94254-
yield (0, cache_restore_1.restoreCache)(cache, cacheDependencyPath);
94262+
const cacheInvalidateAfterDays = core.getInput('cache-invalidate-after-days');
94263+
yield (0, cache_restore_1.restoreCache)(cache, cacheDependencyPath, cacheInvalidateAfterDays);
9425594264
}
9425694265
const matchersPath = path.join(__dirname, '../..', '.github');
9425794266
core.info(`##[add-matcher]${path.join(matchersPath, 'tsc.json')}`);

src/cache-restore.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import {
1414

1515
export const restoreCache = async (
1616
packageManager: string,
17-
cacheDependencyPath: string
17+
cacheDependencyPath: string,
18+
cacheInvalidateAfterDays?: string
1819
) => {
1920
const packageManagerInfo = await getPackageManagerInfo(packageManager);
2021
if (!packageManagerInfo) {
@@ -37,9 +38,17 @@ export const restoreCache = async (
3738
'Some specified paths were not resolved, unable to cache dependencies.'
3839
);
3940
}
41+
const numericCacheInvalidateAfterDays = cacheInvalidateAfterDays && cacheInvalidateAfterDays === '0'
42+
? 0
43+
: (parseInt(cacheInvalidateAfterDays || '', 10) || 120)
44+
const timedInvalidationPrefix = numericCacheInvalidateAfterDays
45+
? 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
46+
: 0;
4047

41-
const keyPrefix = `node-cache-${platform}-${packageManager}`;
48+
const keyPrefixBase = `node-cache-${platform}-${packageManager}`;
49+
const keyPrefix = `${keyPrefixBase}-${timedInvalidationPrefix}`;
4250
const primaryKey = `${keyPrefix}-${fileHash}`;
51+
const restoreKeys = [`${keyPrefix}-`];
4352
core.debug(`primary key is ${primaryKey}`);
4453

4554
core.saveState(State.CachePrimaryKey, primaryKey);
@@ -53,15 +62,15 @@ export const restoreCache = async (
5362
core.info(
5463
'All dependencies are managed locally by yarn3, the previous cache can be used'
5564
);
56-
cacheKey = await cache.restoreCache(cachePaths, primaryKey, [keyPrefix]);
65+
cacheKey = await cache.restoreCache(cachePaths, primaryKey, [keyPrefixBase]);
5766
} else {
58-
cacheKey = await cache.restoreCache(cachePaths, primaryKey);
67+
cacheKey = await cache.restoreCache(cachePaths, primaryKey, restoreKeys);
5968
}
6069

6170
core.setOutput('cache-hit', Boolean(cacheKey));
6271

6372
if (!cacheKey) {
64-
core.info(`${packageManager} cache is not found`);
73+
core.info(`Cache not found for input keys: ${[primaryKey, ...restoreKeys].join(', ')}`);
6574
return;
6675
}
6776

src/main.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ export async function run() {
6262
if (cache && isCacheFeatureAvailable()) {
6363
core.saveState(State.CachePackageManager, cache);
6464
const cacheDependencyPath = core.getInput('cache-dependency-path');
65-
await restoreCache(cache, cacheDependencyPath);
65+
const cacheInvalidateAfterDays = core.getInput('cache-invalidate-after-days');
66+
await restoreCache(cache, cacheDependencyPath, cacheInvalidateAfterDays);
6667
}
6768

6869
const matchersPath = path.join(__dirname, '../..', '.github');

0 commit comments

Comments
 (0)