1 | import * as chokidar from "chokidar";
|
2 | import * as findUp from "find-up";
|
3 | import * as path from "path";
|
4 | import anymatch from "anymatch";
|
5 | import { logger } from "backfill-logger";
|
6 | import { outputFolderAsArray } from "backfill-config";
|
7 |
|
8 | let changedFilesOutsideScope: string[] = [];
|
9 | let changedFilesInsideScope: string[] = [];
|
10 |
|
11 | let watcher: chokidar.FSWatcher;
|
12 |
|
13 | function getGitRepositoryRoot(packageRoot: string) {
|
14 | const nearestGitFolder = findUp.sync(".git", {
|
15 | cwd: packageRoot,
|
16 | type: "directory"
|
17 | });
|
18 |
|
19 | if (nearestGitFolder) {
|
20 |
|
21 | return path.join(nearestGitFolder, "..");
|
22 | }
|
23 |
|
24 | return packageRoot;
|
25 | }
|
26 |
|
27 | function addGlobstars(globPatterns: string[]): string[] {
|
28 | const folders = globPatterns.map(p => path.posix.join("**", p, "**", "*"));
|
29 | const files = globPatterns.map(p => path.posix.join("**", p));
|
30 |
|
31 | return [...folders, ...files];
|
32 | }
|
33 |
|
34 | export function initializeWatcher(
|
35 | packageRoot: string,
|
36 | internalCacheFolder: string,
|
37 | logFolder: string,
|
38 | outputFolder: string | string[],
|
39 | hashGlobs: string[]
|
40 | ) {
|
41 |
|
42 | const repositoryRoot = getGitRepositoryRoot(packageRoot);
|
43 |
|
44 |
|
45 | changedFilesOutsideScope = [];
|
46 | changedFilesInsideScope = [];
|
47 |
|
48 | logger.info("Running in AUDIT mode");
|
49 | logger.info(`[audit] Watching file changes in: ${repositoryRoot}`);
|
50 | logger.info(`[audit] Backfill will cache folder: ${outputFolder}`);
|
51 |
|
52 |
|
53 | const ignoreGlobs = addGlobstars([
|
54 | ".git",
|
55 | ".cache",
|
56 | logFolder,
|
57 | internalCacheFolder
|
58 | ]);
|
59 |
|
60 | const cacheFolderGlob = outputFolderAsArray(outputFolder).map(folder =>
|
61 | path.posix.join("**", folder, "**")
|
62 | );
|
63 |
|
64 | watcher = chokidar
|
65 | .watch(hashGlobs, {
|
66 | ignored: ignoreGlobs,
|
67 | cwd: repositoryRoot,
|
68 | persistent: true,
|
69 | ignoreInitial: true,
|
70 | followSymlinks: false,
|
71 | usePolling: true
|
72 | })
|
73 | .on("all", (event, filePath) => {
|
74 | const logLine = `${filePath} (${event})`;
|
75 | logger.silly(`[audit] File change: ${logLine}`);
|
76 |
|
77 | if (!anymatch(cacheFolderGlob, filePath)) {
|
78 | changedFilesOutsideScope.push(logLine);
|
79 | } else {
|
80 | changedFilesInsideScope.push(logLine);
|
81 | }
|
82 | });
|
83 | }
|
84 |
|
85 | export const sideEffectWarningString =
|
86 | "[audit] The following files got changed outside of the scope of the folder to be cached:";
|
87 | export const sideEffectCallToActionString =
|
88 | "[audit] You should make sure that these changes are non-essential, as they would not be brought back on a cache-hit.";
|
89 | export const noSideEffectString =
|
90 | "[audit] All observed file changes were within the scope of the folder to be cached.";
|
91 |
|
92 | async function delay(time: number) {
|
93 | return new Promise(resolve => {
|
94 | setTimeout(resolve, time);
|
95 | });
|
96 | }
|
97 |
|
98 | export async function closeWatcher() {
|
99 |
|
100 | await delay(1000);
|
101 |
|
102 | if (changedFilesOutsideScope.length > 0) {
|
103 | logger.warn(sideEffectWarningString);
|
104 | changedFilesOutsideScope.forEach(file => logger.warn(`- ${file}`));
|
105 | logger.warn(sideEffectCallToActionString);
|
106 | } else {
|
107 | logger.info(noSideEffectString);
|
108 | }
|
109 |
|
110 | watcher.close();
|
111 | }
|