Files
react-native/scripts/clean-gha-cache.js
T
Blake Friedman 4410899ec7 Only cache node_modules on main (#45544)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/45544

## This diff now does 5 things:
1. removes the old way we used `actions/setup-node` to manage the cache itself.
2. it creates a new `update-node-modules-cache` workflow, which is the only job that will update the node modules cache
3. it create a `yarn-install-with-cache` action that should be used install of directly calling `yarn install --non-interactive`.  This will load a cache against a hash of `package.json`.
4. updated the cache reaper to aggressively remove everything but the latest `npm-{{ hash('package.json') }}`.
5. removed a `cache-setup`, which couldn't be used (we're using artefacts now).

## Why are we doing this:
The various `node-cache-` keys for platforms and on various branches accounts for a very large proportion of the cache (10-20%).

We don't frequently change these dependencies, and even when we do running `yarn install` after loading the cache will resolve any issues.

Limiting the cache to `main` and aggressively pruning older cache entries will clean up a lot of "small win" caching.

Changelog: [Internal]

Reviewed By: cortinico

Differential Revision: D59917944

fbshipit-source-id: 4be6f1959e8fde642a4f208f7d19aceba2c3262f
2024-07-19 13:04:07 -07:00

92 lines
3.0 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const {execSync} = require('node:child_process');
const CACHE_LIMIT = (10 * 1024 ** 3) * 0.9;
// Doing this to capture node-modules- and the pre-existing node-cache-<platform>-yarn-<sha> entries
const NODE_CACHE_KEY = 'node-';
function cleanData(rawStr) {
const now = new Date();
const json = JSON.parse(rawStr);
return json
.map(raw => ({
...raw,
createdAt: now - new Date(raw.createdAt),
msSinceLastAccessed: now - new Date(raw.lastAccessedAt),
}))
// Order: oldest last access time first
.sort((a, b) => b.msSinceLastAccessed - a.msSinceLastAccessed);
}
const mb = bytes => (bytes / 1024 ** 2).toFixed(2) + 'MB';
const hrs = ms => (ms / (1000 * 60 * 60)).toFixed(1) + 'hrs';
const dryRun = process.argv.indexOf('--dry') !== -1;
function cacheToString(entry) {
return `[${hrs(entry.msSinceLastAccessed).padStart(7)}] ${mb(entry.sizeInBytes).padStart(7)} -> ${entry.key}`;
}
function main() {
const cacheUsage = cleanData(execSync(
'gh cache list --sort last_accessed_at --json id,key,createdAt,lastAccessedAt,sizeInBytes --limit 1000',
'utf8'
));
let total = 0;
let remove = [];
let cleaned = 0;
// Be aggressive with node cache entries. Ignore entries < 1MB and only keep most
// recently created entry.
const nodeCacheUsage = cacheUsage
.filter(({key, sizeInBytes}) =>
// I've observed some noisy entries, ignore anything that isn't > 1MB
key.startsWith(NODE_CACHE_KEY) && sizeInBytes > 1024 * 1024
)
.sort((a, b) => b.createdAt - a.createdAt);
// Leave the latest entry only
const keeping = nodeCacheUsage.pop();
console.log('TASK: clean up old node_modules cache entries.', keeping ? `\nkeeping ${cacheToString(keeping)}` : ' Skipping, no cache entries.');
for (const entry of nodeCacheUsage) {
console.warn(`removing ${cacheToString(entry)}`);
cleaned += entry.sizeInBytes;
remove.push(entry.id);
}
// Cleanup everything else
console.log('TASK: clean up everything else that takes us over our threshold: ' + mb(CACHE_LIMIT));
for (let i = cacheUsage.length - 1; i > 0; i--) {
const cache = cacheUsage[i];
total += cache.sizeInBytes;
// Are we in the danger zone?
if (total > CACHE_LIMIT) {
console.warn(cacheToString(cacheUsage[i]));
cleaned += cache.sizeInBytes;
remove.push(cache.id);
} else {
console.warn(`skip ${cacheUsage.length - i} ${mb(cache.sizeInBytes)} ${hrs(cache.msSinceLastAccessed)}`);
}
}
console.warn(`Identifed ${remove.length} cache keys for removal, reducing cache from ${mb(total)} -> ${mb(total - cleaned)}`);
const cleanup = remove.map(id => `gh cache delete ${id} --repo facebook/react-native`);
for (const cmd of cleanup) {
if (dryRun) {
console.warn(`Skip: ${cmd}`);
} else {
console.warn(`${cmd} 🪓 ${execSync(cmd, 'utf8').toString()}`);
}
}
}
main();