forked from cypress-io/cypress
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcircle-cache.js
More file actions
136 lines (113 loc) · 3.99 KB
/
circle-cache.js
File metadata and controls
136 lines (113 loc) · 3.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/* eslint-disable no-console */
const glob = require('glob')
const path = require('path')
const fsExtra = require('fs-extra')
const minimist = require('minimist')
const crypto = require('crypto')
const fs = require('fs')
const stringify = require('fast-json-stable-stringify')
const rootPackageJson = require('../package.json')
const opts = minimist(process.argv.slice(2))
async function circleCache () {
switch (opts.action) {
case 'prepare': return await prepareCircleCache()
case 'unpack': return await unpackCircleCache()
case 'cacheKey': return await cacheKey()
default: {
throw new Error('Expected --action "prepare", "unpack", or "cacheKey"')
}
}
}
// On Windows, both the forward slash (/) and backward slash (\) are accepted as path segment separators
// using forward slashes to match the returned globbed file path separators
const BASE_DIR = path.join(__dirname, '..').replaceAll(/\\/g, '/')
const CACHE_DIR = `${BASE_DIR}/globbed_node_modules`
const p = (str) => `${BASE_DIR}/${str}`
const workspacePaths = rootPackageJson.workspaces.packages
const packageGlobs = workspacePaths.filter((s) => s.endsWith('/*'))
// Gets the sha of all of the patch-package files we have, so we can use this in the cache key.
// Otherwise, adding/editing a patch will not invalidate the CI cache we have for the yarn install
async function cacheKey () {
const yarnLocks = [p('yarn.lock')]
const patchFiles = glob.sync(p('**/*.patch'), {
ignore: ['**/node_modules/**', '**/*_node_modules/**', '**/dist-{app,launchpad}/**'],
})
const packageJsons = glob.sync(`${BASE_DIR}/{.,${workspacePaths.join(',')}}/package.json`)
// Concat the stable stringify of all of the package.json dependencies that make up
const hashedPackageDeps = packageJsons.sort().map((abs) => require(abs)).map(
({ name, dependencies, devDependencies, peerDependencies }) => {
return hashString(
stringify({ name, dependencies, devDependencies, peerDependencies }),
)
},
).join('')
const filesToHash = yarnLocks.concat(patchFiles).sort()
const hashedFiles = await Promise.all(filesToHash.map((p) => hashFile(p)))
const cacheKeySource = hashedFiles.concat(hashedPackageDeps)
const cacheKey = hashString(cacheKeySource.join(''))
// Log to stdout, used by circle to generate cache key
console.log(cacheKey)
}
// Need to dynamically unpack and re-assemble all of the node_modules directories
// https://discuss.circleci.com/t/dynamic-or-programmatic-caching-of-directories/1455
async function prepareCircleCache () {
const paths = glob.sync(p(`{${packageGlobs.join(',')}}/node_modules/`))
await Promise.all(
paths.map(async (src) => {
await fsExtra.move(
src,
src
.replace(/(.*?)\/node_modules/, '$1_node_modules')
.replace(BASE_DIR, CACHE_DIR),
)
}),
)
console.log(`Moved globbed node_modules for ${packageGlobs.join(', ')} to ${CACHE_DIR}`)
}
async function unpackCircleCache () {
const paths = glob.sync(p('globbed_node_modules/*/*/'))
if (paths.length === 0) {
throw new Error('Should have found globbed node_modules to unpack')
}
await Promise.all(
paths.map(async (src) => {
await fsExtra.move(
src,
src
.replace(CACHE_DIR, BASE_DIR)
.replace(/(.*?)_node_modules/, `$1/node_modules`),
)
}),
)
console.log(`Unpacked globbed node_modules from ${CACHE_DIR} to ${packageGlobs.join(', ')}`)
await fsExtra.remove(CACHE_DIR)
}
function hashFile (filePath) {
return new Promise(
(resolve, reject) => {
const hash = crypto.createHash('sha1')
const rs = fs.createReadStream(filePath)
rs.on('error', reject)
rs.on('data', (chunk) => {
hash.update(chunk)
})
rs.on('end', () => {
return resolve(hash.digest('hex'))
})
},
)
}
function hashString (s) {
return crypto
.createHash('sha1')
.update(s)
.digest('hex')
}
circleCache()
.then(() => {
process.exit(0)
})
.catch((e) => {
console.error(e)
process.exit(1)
})