Skip to content

Commit 195482c

Browse files
mydeachargome
andauthored
ci: Stabilize CI dependency cache key (#13401)
Ensure it only changes if actual dependencies change. Previously, we would invalidate the dependency cache every time a package.json of the workspace changed in any way. This is defensive, but it also means that we also invalidate if one of these things happen: 1. A script or similar is added/edited for workspace package 2. A release is made, bumping internal dependency versions This change updates this to calculate the hash with a slightly more sophisticated approach, which should hopefully ensure we only actually bust the cache when a dependency _actually_ changes. This should lead to the dependency cache being re-used much more, because only rarely is an actual dependency changed. --------- Co-authored-by: Charly Gomez <[email protected]>
1 parent 6cb6999 commit 195482c

File tree

2 files changed

+74
-3
lines changed

2 files changed

+74
-3
lines changed

.github/actions/install-dependencies/action.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,9 @@ outputs:
99
runs:
1010
using: "composite"
1111
steps:
12-
# we use a hash of yarn.lock as our cache key, because if it hasn't changed, our dependencies haven't changed,
13-
# so no need to reinstall them
1412
- name: Compute dependency cache key
1513
id: compute_lockfile_hash
16-
run: echo "hash=dependencies-${{ hashFiles('yarn.lock', 'packages/*/package.json', 'dev-packages/*/package.json') }}" >> "$GITHUB_OUTPUT"
14+
run: node ./scripts/dependency-hash-key.js >> "$GITHUB_OUTPUT"
1715
shell: bash
1816

1917
- name: Check dependency cache

scripts/dependency-hash-key.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
const crypto = require('crypto');
2+
const fs = require('fs');
3+
const path = require('path');
4+
5+
/**
6+
* Build a cache key for the dependencies of the monorepo.
7+
* In addition to the content of the yarn.lock file, we also include
8+
* dependencies of all workspace packages in the cache key.
9+
* This ensures that we get a consistent cache key even if a dependency change does not affect
10+
* the yarn.lock file.
11+
*/
12+
function outputDependencyCacheKey() {
13+
const lockfileContent = fs.readFileSync(path.join(process.cwd(), 'yarn.lock'), 'utf8');
14+
15+
const hashParts = [lockfileContent];
16+
17+
const packageJson = require(path.join(process.cwd(), 'package.json'));
18+
19+
const workspacePackages = packageJson.workspaces || [];
20+
21+
// Get the package name (e.g. @sentry/browser) of all workspace packages
22+
// we want to ignore their version numbers later
23+
const workspacePackageNames = getWorkspacePackageNames(workspacePackages);
24+
25+
// Add the dependencies of the workspace itself
26+
hashParts.push(getNormalizedDependencies(packageJson, workspacePackageNames));
27+
28+
// Now for each workspace package, add the dependencies
29+
workspacePackages.forEach(workspace => {
30+
const packageJsonPath = path.join(process.cwd(), workspace, 'package.json');
31+
const packageJson = require(packageJsonPath);
32+
hashParts.push(getNormalizedDependencies(packageJson, workspacePackageNames));
33+
});
34+
35+
const hash = crypto.createHash('md5').update(hashParts.join('\n')).digest('hex');
36+
// We log the output in a way that the GitHub Actions can append it to the output
37+
// We prefix it with `dependencies-` so it is easier to identify in the logs
38+
// eslint-disable-next-line no-console
39+
console.log(`hash=dependencies-${hash}`);
40+
}
41+
42+
function getNormalizedDependencies(packageJson, workspacePackageNames) {
43+
const { dependencies, devDependencies } = packageJson;
44+
45+
const mergedDependencies = {
46+
...devDependencies,
47+
...dependencies,
48+
};
49+
50+
const normalizedDependencies = {};
51+
52+
// Sort the keys to ensure a consistent order
53+
Object.keys(mergedDependencies)
54+
.sort()
55+
.forEach(key => {
56+
// If the dependency is a workspace package, ignore the version
57+
// No need to invalidate a cache after every release
58+
const version = workspacePackageNames.includes(key) ? '**workspace**' : mergedDependencies[key];
59+
normalizedDependencies[key] = version;
60+
});
61+
62+
return JSON.stringify(normalizedDependencies);
63+
}
64+
65+
function getWorkspacePackageNames(workspacePackages) {
66+
return workspacePackages.map(workspace => {
67+
const packageJsonPath = path.join(process.cwd(), workspace, 'package.json');
68+
const packageJson = require(packageJsonPath);
69+
return packageJson.name;
70+
});
71+
}
72+
73+
outputDependencyCacheKey();

0 commit comments

Comments
 (0)