file-entry-cache 9.1.0 → 10.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,347 @@
1
+ // src/index.ts
2
+ import crypto from "node:crypto";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { FlatCache, createFromFile as createFlatCacheFile } from "flat-cache";
6
+ function createFromFile(filePath, useCheckSum, currentWorkingDirectory) {
7
+ const fname = path.basename(filePath);
8
+ const directory = path.dirname(filePath);
9
+ return create(fname, directory, useCheckSum, currentWorkingDirectory);
10
+ }
11
+ function create(cacheId, cacheDirectory, useCheckSum, currentWorkingDirectory) {
12
+ const options = {
13
+ currentWorkingDirectory,
14
+ useCheckSum,
15
+ cache: {
16
+ cacheId,
17
+ cacheDir: cacheDirectory
18
+ }
19
+ };
20
+ const fileEntryCache = new FileEntryCache(options);
21
+ if (cacheDirectory) {
22
+ const cachePath = `${cacheDirectory}/${cacheId}`;
23
+ if (fs.existsSync(cachePath)) {
24
+ fileEntryCache.cache = createFlatCacheFile(cachePath, options.cache);
25
+ fileEntryCache.reconcile();
26
+ }
27
+ }
28
+ return fileEntryCache;
29
+ }
30
+ var FileEntryDefault = class {
31
+ static create = create;
32
+ static createFromFile = createFromFile;
33
+ };
34
+ var FileEntryCache = class {
35
+ _cache = new FlatCache();
36
+ _useCheckSum = false;
37
+ _currentWorkingDirectory;
38
+ _hashAlgorithm = "md5";
39
+ constructor(options) {
40
+ if (options?.cache) {
41
+ this._cache = new FlatCache(options.cache);
42
+ }
43
+ if (options?.useCheckSum) {
44
+ this._useCheckSum = options.useCheckSum;
45
+ }
46
+ if (options?.currentWorkingDirectory) {
47
+ this._currentWorkingDirectory = options.currentWorkingDirectory;
48
+ }
49
+ if (options?.hashAlgorithm) {
50
+ this._hashAlgorithm = options.hashAlgorithm;
51
+ }
52
+ }
53
+ get cache() {
54
+ return this._cache;
55
+ }
56
+ set cache(cache) {
57
+ this._cache = cache;
58
+ }
59
+ get useCheckSum() {
60
+ return this._useCheckSum;
61
+ }
62
+ set useCheckSum(value) {
63
+ this._useCheckSum = value;
64
+ }
65
+ get hashAlgorithm() {
66
+ return this._hashAlgorithm;
67
+ }
68
+ set hashAlgorithm(value) {
69
+ this._hashAlgorithm = value;
70
+ }
71
+ get currentWorkingDirectory() {
72
+ return this._currentWorkingDirectory;
73
+ }
74
+ set currentWorkingDirectory(value) {
75
+ this._currentWorkingDirectory = value;
76
+ }
77
+ /**
78
+ * Given a buffer, calculate md5 hash of its content.
79
+ * @method getHash
80
+ * @param {Buffer} buffer buffer to calculate hash on
81
+ * @return {String} content hash digest
82
+ */
83
+ // eslint-disable-next-line @typescript-eslint/ban-types
84
+ getHash(buffer) {
85
+ return crypto.createHash(this._hashAlgorithm).update(buffer).digest("hex");
86
+ }
87
+ /**
88
+ * Create the key for the file path used for caching.
89
+ * @method createFileKey
90
+ * @param {String} filePath
91
+ * @return {String}
92
+ */
93
+ createFileKey(filePath, options) {
94
+ let result = filePath;
95
+ const currentWorkingDirectory = options?.currentWorkingDirectory ?? this._currentWorkingDirectory;
96
+ if (currentWorkingDirectory && filePath.startsWith(currentWorkingDirectory)) {
97
+ const splitPath = filePath.split(currentWorkingDirectory).pop();
98
+ if (splitPath) {
99
+ result = splitPath;
100
+ if (result.startsWith("/")) {
101
+ result = result.slice(1);
102
+ }
103
+ }
104
+ }
105
+ return result;
106
+ }
107
+ /**
108
+ * Check if the file path is a relative path
109
+ * @method isRelativePath
110
+ * @param filePath - The file path to check
111
+ * @returns {boolean} if the file path is a relative path, false otherwise
112
+ */
113
+ isRelativePath(filePath) {
114
+ return !path.isAbsolute(filePath);
115
+ }
116
+ /**
117
+ * Delete the cache file from the disk
118
+ * @method deleteCacheFile
119
+ * @return {boolean} true if the file was deleted, false otherwise
120
+ */
121
+ deleteCacheFile() {
122
+ return this._cache.removeCacheFile();
123
+ }
124
+ /**
125
+ * Remove the cache from the file and clear the memory cache
126
+ * @method destroy
127
+ */
128
+ destroy() {
129
+ this._cache.destroy();
130
+ }
131
+ /**
132
+ * Remove and Entry From the Cache
133
+ * @method removeEntry
134
+ * @param filePath - The file path to remove from the cache
135
+ */
136
+ removeEntry(filePath, options) {
137
+ if (this.isRelativePath(filePath)) {
138
+ filePath = this.getAbsolutePath(filePath, { currentWorkingDirectory: options?.currentWorkingDirectory });
139
+ this._cache.removeKey(this.createFileKey(filePath));
140
+ }
141
+ const key = this.createFileKey(filePath, { currentWorkingDirectory: options?.currentWorkingDirectory });
142
+ this._cache.removeKey(key);
143
+ }
144
+ /**
145
+ * Reconcile the cache
146
+ * @method reconcile
147
+ */
148
+ reconcile() {
149
+ const items = this._cache.items;
150
+ for (const item of items) {
151
+ const fileDescriptor = this.getFileDescriptor(item.key);
152
+ if (fileDescriptor.notFound) {
153
+ this._cache.removeKey(item.key);
154
+ }
155
+ }
156
+ this._cache.save();
157
+ }
158
+ /**
159
+ * Check if the file has changed
160
+ * @method hasFileChanged
161
+ * @param filePath - The file path to check
162
+ * @returns {boolean} if the file has changed, false otherwise
163
+ */
164
+ hasFileChanged(filePath) {
165
+ let result = false;
166
+ const fileDescriptor = this.getFileDescriptor(filePath);
167
+ if ((!fileDescriptor.err || !fileDescriptor.notFound) && fileDescriptor.changed) {
168
+ result = true;
169
+ }
170
+ return result;
171
+ }
172
+ /**
173
+ * Get the file descriptor for the file path
174
+ * @method getFileDescriptor
175
+ * @param filePath - The file path to get the file descriptor for
176
+ * @param options - The options for getting the file descriptor
177
+ * @returns The file descriptor
178
+ */
179
+ getFileDescriptor(filePath, options) {
180
+ let fstat;
181
+ const result = {
182
+ key: this.createFileKey(filePath),
183
+ changed: false,
184
+ meta: {}
185
+ };
186
+ filePath = this.getAbsolutePath(filePath, { currentWorkingDirectory: options?.currentWorkingDirectory });
187
+ const useCheckSumValue = options?.useCheckSum ?? this._useCheckSum;
188
+ try {
189
+ fstat = fs.statSync(filePath);
190
+ result.meta = {
191
+ size: fstat.size
192
+ };
193
+ result.meta.mtime = fstat.mtime.getTime();
194
+ if (useCheckSumValue) {
195
+ const buffer = fs.readFileSync(filePath);
196
+ result.meta.hash = this.getHash(buffer);
197
+ }
198
+ } catch (error) {
199
+ this.removeEntry(filePath);
200
+ let notFound = false;
201
+ if (error.message.includes("ENOENT")) {
202
+ notFound = true;
203
+ }
204
+ return {
205
+ key: result.key,
206
+ err: error,
207
+ notFound,
208
+ meta: {}
209
+ };
210
+ }
211
+ const metaCache = this._cache.getKey(result.key);
212
+ if (!metaCache) {
213
+ result.changed = true;
214
+ this._cache.setKey(result.key, result.meta);
215
+ return result;
216
+ }
217
+ result.meta.data = metaCache.data;
218
+ if (metaCache?.mtime !== result.meta?.mtime || metaCache?.size !== result.meta?.size) {
219
+ result.changed = true;
220
+ this._cache.setKey(result.key, result.meta);
221
+ }
222
+ if (useCheckSumValue && metaCache?.hash !== result.meta?.hash) {
223
+ result.changed = true;
224
+ this._cache.setKey(result.key, result.meta);
225
+ }
226
+ return result;
227
+ }
228
+ /**
229
+ * Get the file descriptors for the files
230
+ * @method normalizeEntries
231
+ * @param files?: string[] - The files to get the file descriptors for
232
+ * @returns The file descriptors
233
+ */
234
+ normalizeEntries(files) {
235
+ const result = new Array();
236
+ if (files) {
237
+ for (const file of files) {
238
+ const fileDescriptor = this.getFileDescriptor(file);
239
+ result.push(fileDescriptor);
240
+ }
241
+ return result;
242
+ }
243
+ const keys = this.cache.keys();
244
+ for (const key of keys) {
245
+ const fileDescriptor = this.getFileDescriptor(key);
246
+ if (!fileDescriptor.notFound && !fileDescriptor.err) {
247
+ result.push(fileDescriptor);
248
+ }
249
+ }
250
+ return result;
251
+ }
252
+ /**
253
+ * Analyze the files
254
+ * @method analyzeFiles
255
+ * @param files - The files to analyze
256
+ * @returns {AnalyzedFiles} The analysis of the files
257
+ */
258
+ analyzeFiles(files) {
259
+ const result = {
260
+ changedFiles: [],
261
+ notFoundFiles: [],
262
+ notChangedFiles: []
263
+ };
264
+ const fileDescriptors = this.normalizeEntries(files);
265
+ for (const fileDescriptor of fileDescriptors) {
266
+ if (fileDescriptor.notFound) {
267
+ result.notFoundFiles.push(fileDescriptor.key);
268
+ } else if (fileDescriptor.changed) {
269
+ result.changedFiles.push(fileDescriptor.key);
270
+ } else {
271
+ result.notChangedFiles.push(fileDescriptor.key);
272
+ }
273
+ }
274
+ return result;
275
+ }
276
+ /**
277
+ * Get the updated files
278
+ * @method getUpdatedFiles
279
+ * @param files - The files to get the updated files for
280
+ * @returns {string[]} The updated files
281
+ */
282
+ getUpdatedFiles(files) {
283
+ const result = new Array();
284
+ const fileDescriptors = this.normalizeEntries(files);
285
+ for (const fileDescriptor of fileDescriptors) {
286
+ if (fileDescriptor.changed) {
287
+ result.push(fileDescriptor.key);
288
+ }
289
+ }
290
+ return result;
291
+ }
292
+ /**
293
+ * Get the not found files
294
+ * @method getFileDescriptorsByPath
295
+ * @param filePath - the files that you want to get from a path
296
+ * @returns {FileDescriptor[]} The not found files
297
+ */
298
+ getFileDescriptorsByPath(filePath) {
299
+ const result = new Array();
300
+ const keys = this._cache.keys();
301
+ for (const key of keys) {
302
+ const absolutePath = this.getAbsolutePath(filePath);
303
+ if (absolutePath.startsWith(filePath)) {
304
+ const fileDescriptor = this.getFileDescriptor(key);
305
+ result.push(fileDescriptor);
306
+ }
307
+ }
308
+ return result;
309
+ }
310
+ /**
311
+ * Get the Absolute Path. If it is already absolute it will return the path as is.
312
+ * @method getAbsolutePath
313
+ * @param filePath - The file path to get the absolute path for
314
+ * @param options - The options for getting the absolute path. The current working directory is used if not provided.
315
+ * @returns {string}
316
+ */
317
+ getAbsolutePath(filePath, options) {
318
+ if (this.isRelativePath(filePath)) {
319
+ const currentWorkingDirectory = options?.currentWorkingDirectory ?? this._currentWorkingDirectory ?? process.cwd();
320
+ filePath = path.resolve(currentWorkingDirectory, filePath);
321
+ }
322
+ return filePath;
323
+ }
324
+ /**
325
+ * Rename the absolute path keys. This is used when a directory is changed or renamed.
326
+ * @method renameAbsolutePathKeys
327
+ * @param oldPath - The old path to rename
328
+ * @param newPath - The new path to rename to
329
+ */
330
+ renameAbsolutePathKeys(oldPath, newPath) {
331
+ const keys = this._cache.keys();
332
+ for (const key of keys) {
333
+ if (key.startsWith(oldPath)) {
334
+ const newKey = key.replace(oldPath, newPath);
335
+ const meta = this._cache.getKey(key);
336
+ this._cache.removeKey(key);
337
+ this._cache.setKey(newKey, meta);
338
+ }
339
+ }
340
+ }
341
+ };
342
+ export {
343
+ FileEntryCache,
344
+ create,
345
+ createFromFile,
346
+ FileEntryDefault as default
347
+ };
package/package.json CHANGED
@@ -1,33 +1,25 @@
1
1
  {
2
2
  "name": "file-entry-cache",
3
- "version": "9.1.0",
4
- "description": "Super simple cache for file metadata, useful for process that work o a given series of files and that only need to repeat the job on the changed ones since the previous run of the process",
5
- "repository": "jaredwray/file-entry-cache",
6
- "license": "MIT",
7
- "author": {
8
- "name": "Jared Wray",
9
- "url": "https://jaredwray.com"
10
- },
11
- "main": "cache.js",
12
- "files": [
13
- "cache.js"
14
- ],
15
- "engines": {
16
- "node": ">=18"
3
+ "version": "10.0.1",
4
+ "description": "A lightweight cache for file metadata, ideal for processes that work on a specific set of files and only need to reprocess files that have changed since the last run",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "require": "./dist/index.cjs",
12
+ "import": "./dist/index.js"
13
+ }
17
14
  },
18
- "scripts": {
19
- "clean": "rimraf ./coverage /node_modules ./package-lock.json ./yarn.lock ./pnpm-lock.yaml",
20
- "test": "xo --fix && c8 mocha -R spec test/specs/cache.js test/relative.js",
21
- "test:relative": "rimraf ./rfixtures ./tfixtures && mocha test/relative.js",
22
- "test:ci": "xo && c8 --reporter=lcov mocha -R spec test/specs/cache.js test/relative.js",
23
- "perf": "node perf.js"
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/jaredwray/cacheable.git",
18
+ "directory": "packages/file-entry-cache"
24
19
  },
25
- "prepush": [
26
- "npm run test"
27
- ],
28
- "precommit": [
29
- "npm run test"
30
- ],
20
+ "author": "Jared Wray <me@jaredwray.com>",
21
+ "license": "MIT",
22
+ "private": false,
31
23
  "keywords": [
32
24
  "file cache",
33
25
  "task cache files",
@@ -37,23 +29,25 @@
37
29
  "cache"
38
30
  ],
39
31
  "devDependencies": {
40
- "c8": "^10.1.2",
41
- "chai": "^4.3.10",
42
- "glob-expand": "^0.2.1",
43
- "mocha": "^10.5.1",
44
- "rimraf": "^5.0.7",
45
- "webpack": "^5.92.1",
46
- "write": "^2.0.0",
47
- "xo": "^0.58.0"
32
+ "@types/node": "^22.8.1",
33
+ "@vitest/coverage-v8": "^2.1.3",
34
+ "rimraf": "^6.0.1",
35
+ "tsup": "^8.3.5",
36
+ "typescript": "^5.6.3",
37
+ "vitest": "^2.1.3",
38
+ "xo": "^0.59.3"
48
39
  },
49
40
  "dependencies": {
50
- "flat-cache": "^5.0.0"
41
+ "flat-cache": "^6.1.1"
51
42
  },
52
- "xo": {
53
- "rules": {
54
- "unicorn/prefer-module": "off",
55
- "n/prefer-global/process": "off",
56
- "unicorn/prevent-abbreviations": "off"
57
- }
43
+ "files": [
44
+ "dist",
45
+ "license"
46
+ ],
47
+ "scripts": {
48
+ "build": "rimraf ./dist && tsup src/index.ts --format cjs,esm --dts --clean",
49
+ "test": "xo --fix && vitest run --coverage",
50
+ "test:ci": "xo && vitest run",
51
+ "clean": "rimraf ./dist ./coverage ./node_modules"
58
52
  }
59
- }
53
+ }