cache.ts
3.23 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
import * as crypto from 'crypto';
import * as fs from 'fs-extra';
import * as path from 'path';
import * as zlib from 'zlib';
class Snap {
constructor(public hash: string, public data: Buffer) {}
}
interface Snapshot {
[key: string]: Snap | Snapshot;
}
const takeSnapshot = async (dir: string, relativeTo = dir): Promise<Snapshot> => {
const snap: Snapshot = {};
await Promise.all((await fs.readdir(dir)).map(async (child) => {
if (child === 'node_modules') return;
const childPath = path.resolve(dir, child);
const relative = path.relative(relativeTo, childPath);
if ((await fs.stat(childPath)).isDirectory()) {
snap[relative] = await takeSnapshot(childPath, relativeTo);
} else {
const data = await fs.readFile(childPath);
snap[relative] = new Snap(
crypto.createHash('SHA256').update(data).digest('hex'),
data,
);
}
}));
return snap;
};
const writeSnapshot = async (diff: Snapshot, dir: string): Promise<void> => {
for (const key in diff) {
if (diff[key] instanceof Snap) {
await fs.mkdirp(path.dirname(path.resolve(dir, key)));
await fs.writeFile(path.resolve(dir, key), (diff[key] as Snap).data);
} else {
await fs.mkdirp(path.resolve(dir, key));
await writeSnapshot(diff[key] as Snapshot, dir);
}
}
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const serialize = (snap: Snapshot): any => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const jsonReady: any = {};
for (const key in snap) {
if (snap[key] instanceof Snap) {
const s = snap[key] as Snap;
jsonReady[key] = {
__isSnap: true,
hash: s.hash,
data: s.data.toString('base64')
};
} else {
jsonReady[key] = serialize(snap[key] as Snapshot);
}
}
return jsonReady;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const unserialize = (jsonReady: any): Snapshot => {
const snap: Snapshot = {};
for (const key in jsonReady) {
if (jsonReady[key].__isSnap) {
snap[key] = new Snap(
jsonReady[key].hash,
Buffer.from(jsonReady[key].data, 'base64')
);
} else {
snap[key] = unserialize(jsonReady[key]);
}
}
return snap;
};
export const cacheModuleState = async (dir: string, cachePath: string, key: string): Promise<void> => {
const snap = await takeSnapshot(dir);
const moduleBuffer = Buffer.from(JSON.stringify(serialize(snap)));
const zipped = await new Promise(resolve => zlib.gzip(moduleBuffer, (_, result) => resolve(result)));
await fs.mkdirp(cachePath);
await fs.writeFile(path.resolve(cachePath, key), zipped);
};
type ApplyDiffFunction = (dir: string) => Promise<void>;
export const lookupModuleState = async (cachePath: string, key: string): Promise<ApplyDiffFunction | boolean> => {
if (await fs.pathExists(path.resolve(cachePath, key))) {
return async function applyDiff(dir: string): Promise<void> {
const zipped = await fs.readFile(path.resolve(cachePath, key));
const unzipped: Buffer = await new Promise(resolve => { zlib.gunzip(zipped, (_, result) => resolve(result)); });
const diff = unserialize(JSON.parse(unzipped.toString()));
await writeSnapshot(diff, dir);
};
}
return false;
};