file-entry-cache 11.0.0-beta.3 → 11.0.0-beta.5

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/README.md CHANGED
@@ -36,6 +36,12 @@
36
36
  - [Path Security and Traversal Prevention](#path-security-and-traversal-prevention)
37
37
  - [Using Checksums to Determine if a File has Changed (useCheckSum)](#using-checksums-to-determine-if-a-file-has-changed-usechecksum)
38
38
  - [Setting Additional Meta Data](#setting-additional-meta-data)
39
+ - [Logger Support](#logger-support)
40
+ - [Logger Interface](#logger-interface)
41
+ - [Using Pino Logger](#using-pino-logger)
42
+ - [Log Levels](#log-levels)
43
+ - [Example with Custom Log Levels](#example-with-custom-log-levels)
44
+ - [Debugging Cache Operations](#debugging-cache-operations)
39
45
  - [How to Contribute](#how-to-contribute)
40
46
  - [License and Copyright](#license-and-copyright)
41
47
 
@@ -117,6 +123,7 @@ There have been many features added and changes made to the `file-entry-cache` c
117
123
  - `hashAlgorithm?` - The algorithm to use for the checksum. Default is `md5` but can be any algorithm supported by `crypto.createHash`
118
124
  - `cwd?` - The current working directory for resolving relative paths. Default is `process.cwd()`
119
125
  - `strictPaths?` - If `true` restricts file access to within `cwd` boundaries, preventing path traversal attacks. Default is `true`
126
+ - `logger?` - A logger instance compatible with Pino logger interface for debugging and monitoring. Default is `undefined`
120
127
  - `cache.ttl?` - The time to live for the cache in milliseconds. Default is `0` which means no expiration
121
128
  - `cache.lruSize?` - The number of items to keep in the cache. Default is `0` which means no limit
122
129
  - `cache.useClone?` - If `true` it will clone the data before returning it. Default is `false`
@@ -135,6 +142,7 @@ There have been many features added and changes made to the `file-entry-cache` c
135
142
  - `getHash(buffer: Buffer): string` - Gets the hash of a buffer used for checksums
136
143
  - `cwd: string` - The current working directory for resolving relative paths. Default is `process.cwd()`
137
144
  - `strictPaths: boolean` - If `true` restricts file access to within `cwd` boundaries. Default is `true`
145
+ - `logger: ILogger | undefined` - A logger instance for debugging and monitoring cache operations
138
146
  - `createFileKey(filePath: string): string` - Returns the cache key for the file path (returns the path exactly as provided).
139
147
  - `deleteCacheFile(): boolean` - Deletes the cache file from disk
140
148
  - `destroy(): void` - Destroys the cache. This will clear the cache in memory. If using cache persistence it will stop the interval.
@@ -396,13 +404,132 @@ const fileDescriptor = fileEntryCache.getFileDescriptor('file.txt', { useCheckSu
396
404
 
397
405
  # Setting Additional Meta Data
398
406
 
399
- In the past we have seen people do random values on the `meta` object. This can cause issues with the `meta` object. To avoid this we have `data` which can be anything.
407
+ In the past we have seen people do random values on the `meta` object. This can cause issues with the `meta` object. To avoid this we have `data` which can be anything.
400
408
 
401
- ```javascript
409
+ ```javascript
402
410
  const fileEntryCache = new FileEntryCache();
403
411
  const fileDescriptor = fileEntryCache.getFileDescriptor('file.txt');
404
412
  fileDescriptor.meta.data = { myData: 'myData' }; //anything you want
405
413
  ```
414
+
415
+ # Logger Support
416
+
417
+ The `FileEntryCache` supports logging through a Pino-compatible logger interface. This is useful for debugging and monitoring cache operations in production environments.
418
+
419
+ ## Logger Interface
420
+
421
+ The logger must implement the following interface:
422
+
423
+ ```typescript
424
+ interface ILogger {
425
+ level?: string;
426
+ trace: (message: string | object, ...args: unknown[]) => void;
427
+ debug: (message: string | object, ...args: unknown[]) => void;
428
+ info: (message: string | object, ...args: unknown[]) => void;
429
+ warn: (message: string | object, ...args: unknown[]) => void;
430
+ error: (message: string | object, ...args: unknown[]) => void;
431
+ fatal: (message: string | object, ...args: unknown[]) => void;
432
+ }
433
+ ```
434
+
435
+ ## Using Pino Logger
436
+
437
+ You can pass a Pino logger instance to the `FileEntryCache` constructor or set it via the `logger` property:
438
+
439
+ ```javascript
440
+ import pino from 'pino';
441
+ import fileEntryCache from 'file-entry-cache';
442
+
443
+ // Create a Pino logger
444
+ const logger = pino({
445
+ level: 'debug',
446
+ transport: {
447
+ target: 'pino-pretty',
448
+ options: {
449
+ colorize: true
450
+ }
451
+ }
452
+ });
453
+
454
+ // Pass logger in constructor
455
+ const cache = new fileEntryCache.FileEntryCache({
456
+ logger,
457
+ cacheId: 'my-cache'
458
+ });
459
+
460
+ // Or set it after creation
461
+ cache.logger = logger;
462
+
463
+ // Now all cache operations will be logged
464
+ const descriptor = cache.getFileDescriptor('./src/file.txt');
465
+ ```
466
+
467
+ ## Log Levels
468
+
469
+ The logger will output different levels of information:
470
+
471
+ - **trace**: Detailed internal operations (key creation, cached meta lookup, file stats)
472
+ - **debug**: Method entry, checksum settings, change detection, file status
473
+ - **info**: Important state changes (file has changed)
474
+ - **error**: File read errors and exceptions
475
+
476
+ ## Example with Custom Log Levels
477
+
478
+ ```javascript
479
+ import pino from 'pino';
480
+ import { FileEntryCache } from 'file-entry-cache';
481
+
482
+ // Create logger with specific level
483
+ const logger = pino({ level: 'info' });
484
+
485
+ const cache = new FileEntryCache({
486
+ logger,
487
+ useCheckSum: true
488
+ });
489
+
490
+ // This will log at info level when files change
491
+ const files = ['./src/index.js', './src/utils.js'];
492
+ files.forEach(file => {
493
+ const descriptor = cache.getFileDescriptor(file);
494
+ if (descriptor.changed) {
495
+ console.log(`Processing changed file: ${file}`);
496
+ }
497
+ });
498
+
499
+ cache.reconcile();
500
+ ```
501
+
502
+ ## Debugging Cache Operations
503
+
504
+ For detailed debugging, set the logger level to `debug` or `trace`:
505
+
506
+ ```javascript
507
+ import pino from 'pino';
508
+ import { FileEntryCache } from 'file-entry-cache';
509
+
510
+ const logger = pino({
511
+ level: 'trace',
512
+ transport: {
513
+ target: 'pino-pretty'
514
+ }
515
+ });
516
+
517
+ const cache = new FileEntryCache({
518
+ logger,
519
+ useCheckSum: true,
520
+ cwd: '/project/root'
521
+ });
522
+
523
+ // Will log detailed information about:
524
+ // - File path resolution
525
+ // - Cache key creation
526
+ // - Cached metadata lookup
527
+ // - File stats reading
528
+ // - Hash calculation (if using checksums)
529
+ // - Change detection logic
530
+ const descriptor = cache.getFileDescriptor('./src/app.js');
531
+ ```
532
+
406
533
  # How to Contribute
407
534
 
408
535
  You can contribute by forking the repo and submitting a pull request. Please make sure to add tests and update the documentation. To learn more about how to contribute go to our main README [https://github.com/jaredwray/cacheable](https://github.com/jaredwray/cacheable). This will talk about how to `Open a Pull Request`, `Ask a Question`, or `Post an Issue`.
package/dist/index.cjs CHANGED
@@ -1,464 +1 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
-
30
- // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
33
- FileEntryCache: () => FileEntryCache,
34
- create: () => create,
35
- createFromFile: () => createFromFile,
36
- default: () => FileEntryDefault
37
- });
38
- module.exports = __toCommonJS(index_exports);
39
- var import_node_crypto = __toESM(require("crypto"), 1);
40
- var import_node_fs = __toESM(require("fs"), 1);
41
- var import_node_path = __toESM(require("path"), 1);
42
- var import_flat_cache = require("flat-cache");
43
- function createFromFile(filePath, useCheckSum, cwd) {
44
- const fname = import_node_path.default.basename(filePath);
45
- const directory = import_node_path.default.dirname(filePath);
46
- return create(fname, directory, useCheckSum, cwd);
47
- }
48
- function create(cacheId, cacheDirectory, useCheckSum, cwd) {
49
- const options = {
50
- useCheckSum,
51
- cwd,
52
- cache: {
53
- cacheId,
54
- cacheDir: cacheDirectory
55
- }
56
- };
57
- const fileEntryCache = new FileEntryCache(options);
58
- if (cacheDirectory) {
59
- const cachePath = `${cacheDirectory}/${cacheId}`;
60
- if (import_node_fs.default.existsSync(cachePath)) {
61
- fileEntryCache.cache = (0, import_flat_cache.createFromFile)(cachePath, options.cache);
62
- }
63
- }
64
- return fileEntryCache;
65
- }
66
- var FileEntryDefault = class {
67
- static create = create;
68
- static createFromFile = createFromFile;
69
- };
70
- var FileEntryCache = class {
71
- _cache = new import_flat_cache.FlatCache({ useClone: false });
72
- _useCheckSum = false;
73
- _hashAlgorithm = "md5";
74
- _cwd = process.cwd();
75
- _strictPaths = true;
76
- /**
77
- * Create a new FileEntryCache instance
78
- * @param options - The options for the FileEntryCache (all properties are optional with defaults)
79
- */
80
- constructor(options) {
81
- if (options?.cache) {
82
- this._cache = new import_flat_cache.FlatCache(options.cache);
83
- }
84
- if (options?.useCheckSum) {
85
- this._useCheckSum = options.useCheckSum;
86
- }
87
- if (options?.hashAlgorithm) {
88
- this._hashAlgorithm = options.hashAlgorithm;
89
- }
90
- if (options?.cwd) {
91
- this._cwd = options.cwd;
92
- }
93
- if (options?.strictPaths !== void 0) {
94
- this._strictPaths = options.strictPaths;
95
- }
96
- }
97
- /**
98
- * Get the cache
99
- * @returns {FlatCache} The cache
100
- */
101
- get cache() {
102
- return this._cache;
103
- }
104
- /**
105
- * Set the cache
106
- * @param {FlatCache} cache - The cache to set
107
- */
108
- set cache(cache) {
109
- this._cache = cache;
110
- }
111
- /**
112
- * Use the hash to check if the file has changed
113
- * @returns {boolean} if the hash is used to check if the file has changed (default: false)
114
- */
115
- get useCheckSum() {
116
- return this._useCheckSum;
117
- }
118
- /**
119
- * Set the useCheckSum value
120
- * @param {boolean} value - The value to set
121
- */
122
- set useCheckSum(value) {
123
- this._useCheckSum = value;
124
- }
125
- /**
126
- * Get the hash algorithm
127
- * @returns {string} The hash algorithm (default: 'md5')
128
- */
129
- get hashAlgorithm() {
130
- return this._hashAlgorithm;
131
- }
132
- /**
133
- * Set the hash algorithm
134
- * @param {string} value - The value to set
135
- */
136
- set hashAlgorithm(value) {
137
- this._hashAlgorithm = value;
138
- }
139
- /**
140
- * Get the current working directory
141
- * @returns {string} The current working directory (default: process.cwd())
142
- */
143
- get cwd() {
144
- return this._cwd;
145
- }
146
- /**
147
- * Set the current working directory
148
- * @param {string} value - The value to set
149
- */
150
- set cwd(value) {
151
- this._cwd = value;
152
- }
153
- /**
154
- * Get whether to restrict paths to cwd boundaries
155
- * @returns {boolean} Whether strict path checking is enabled (default: true)
156
- */
157
- get strictPaths() {
158
- return this._strictPaths;
159
- }
160
- /**
161
- * Set whether to restrict paths to cwd boundaries
162
- * @param {boolean} value - The value to set
163
- */
164
- set strictPaths(value) {
165
- this._strictPaths = value;
166
- }
167
- /**
168
- * Given a buffer, calculate md5 hash of its content.
169
- * @method getHash
170
- * @param {Buffer} buffer buffer to calculate hash on
171
- * @return {String} content hash digest
172
- */
173
- getHash(buffer) {
174
- return import_node_crypto.default.createHash(this._hashAlgorithm).update(buffer).digest("hex");
175
- }
176
- /**
177
- * Create the key for the file path used for caching.
178
- * @method createFileKey
179
- * @param {String} filePath
180
- * @return {String}
181
- */
182
- createFileKey(filePath) {
183
- return filePath;
184
- }
185
- /**
186
- * Check if the file path is a relative path
187
- * @method isRelativePath
188
- * @param filePath - The file path to check
189
- * @returns {boolean} if the file path is a relative path, false otherwise
190
- */
191
- isRelativePath(filePath) {
192
- return !import_node_path.default.isAbsolute(filePath);
193
- }
194
- /**
195
- * Delete the cache file from the disk
196
- * @method deleteCacheFile
197
- * @return {boolean} true if the file was deleted, false otherwise
198
- */
199
- deleteCacheFile() {
200
- return this._cache.removeCacheFile();
201
- }
202
- /**
203
- * Remove the cache from the file and clear the memory cache
204
- * @method destroy
205
- */
206
- destroy() {
207
- this._cache.destroy();
208
- }
209
- /**
210
- * Remove and Entry From the Cache
211
- * @method removeEntry
212
- * @param filePath - The file path to remove from the cache
213
- */
214
- removeEntry(filePath) {
215
- const key = this.createFileKey(filePath);
216
- this._cache.removeKey(key);
217
- }
218
- /**
219
- * Reconcile the cache
220
- * @method reconcile
221
- */
222
- reconcile() {
223
- const { items } = this._cache;
224
- for (const item of items) {
225
- const fileDescriptor = this.getFileDescriptor(item.key);
226
- if (fileDescriptor.notFound) {
227
- this._cache.removeKey(item.key);
228
- }
229
- }
230
- this._cache.save();
231
- }
232
- /**
233
- * Check if the file has changed
234
- * @method hasFileChanged
235
- * @param filePath - The file path to check
236
- * @returns {boolean} if the file has changed, false otherwise
237
- */
238
- hasFileChanged(filePath) {
239
- let result = false;
240
- const fileDescriptor = this.getFileDescriptor(filePath);
241
- if ((!fileDescriptor.err || !fileDescriptor.notFound) && fileDescriptor.changed) {
242
- result = true;
243
- }
244
- return result;
245
- }
246
- /**
247
- * Get the file descriptor for the file path
248
- * @method getFileDescriptor
249
- * @param filePath - The file path to get the file descriptor for
250
- * @param options - The options for getting the file descriptor
251
- * @returns The file descriptor
252
- */
253
- getFileDescriptor(filePath, options) {
254
- let fstat;
255
- const result = {
256
- key: this.createFileKey(filePath),
257
- changed: false,
258
- meta: {}
259
- };
260
- result.meta = this._cache.getKey(result.key) ?? {};
261
- const absolutePath = this.getAbsolutePath(filePath);
262
- const useCheckSumValue = options?.useCheckSum ?? this._useCheckSum;
263
- try {
264
- fstat = import_node_fs.default.statSync(absolutePath);
265
- result.meta = {
266
- size: fstat.size
267
- };
268
- result.meta.mtime = fstat.mtime.getTime();
269
- if (useCheckSumValue) {
270
- const buffer = import_node_fs.default.readFileSync(absolutePath);
271
- result.meta.hash = this.getHash(buffer);
272
- }
273
- } catch (error) {
274
- this.removeEntry(filePath);
275
- let notFound = false;
276
- if (error.message.includes("ENOENT")) {
277
- notFound = true;
278
- }
279
- return {
280
- key: result.key,
281
- err: error,
282
- notFound,
283
- meta: {}
284
- };
285
- }
286
- const metaCache = this._cache.getKey(result.key);
287
- if (!metaCache) {
288
- result.changed = true;
289
- this._cache.setKey(result.key, result.meta);
290
- return result;
291
- }
292
- if (result.meta.data === void 0) {
293
- result.meta.data = metaCache.data;
294
- }
295
- if (useCheckSumValue === false && metaCache?.mtime !== result.meta?.mtime) {
296
- result.changed = true;
297
- }
298
- if (metaCache?.size !== result.meta?.size) {
299
- result.changed = true;
300
- }
301
- if (useCheckSumValue && metaCache?.hash !== result.meta?.hash) {
302
- result.changed = true;
303
- }
304
- this._cache.setKey(result.key, result.meta);
305
- return result;
306
- }
307
- /**
308
- * Get the file descriptors for the files
309
- * @method normalizeEntries
310
- * @param files?: string[] - The files to get the file descriptors for
311
- * @returns The file descriptors
312
- */
313
- normalizeEntries(files) {
314
- const result = [];
315
- if (files) {
316
- for (const file of files) {
317
- const fileDescriptor = this.getFileDescriptor(file);
318
- result.push(fileDescriptor);
319
- }
320
- return result;
321
- }
322
- const keys = this.cache.keys();
323
- for (const key of keys) {
324
- const fileDescriptor = this.getFileDescriptor(key);
325
- if (!fileDescriptor.notFound && !fileDescriptor.err) {
326
- result.push(fileDescriptor);
327
- }
328
- }
329
- return result;
330
- }
331
- /**
332
- * Analyze the files
333
- * @method analyzeFiles
334
- * @param files - The files to analyze
335
- * @returns {AnalyzedFiles} The analysis of the files
336
- */
337
- analyzeFiles(files) {
338
- const result = {
339
- changedFiles: [],
340
- notFoundFiles: [],
341
- notChangedFiles: []
342
- };
343
- const fileDescriptors = this.normalizeEntries(files);
344
- for (const fileDescriptor of fileDescriptors) {
345
- if (fileDescriptor.notFound) {
346
- result.notFoundFiles.push(fileDescriptor.key);
347
- } else if (fileDescriptor.changed) {
348
- result.changedFiles.push(fileDescriptor.key);
349
- } else {
350
- result.notChangedFiles.push(fileDescriptor.key);
351
- }
352
- }
353
- return result;
354
- }
355
- /**
356
- * Get the updated files
357
- * @method getUpdatedFiles
358
- * @param files - The files to get the updated files for
359
- * @returns {string[]} The updated files
360
- */
361
- getUpdatedFiles(files) {
362
- const result = [];
363
- const fileDescriptors = this.normalizeEntries(files);
364
- for (const fileDescriptor of fileDescriptors) {
365
- if (fileDescriptor.changed) {
366
- result.push(fileDescriptor.key);
367
- }
368
- }
369
- return result;
370
- }
371
- /**
372
- * Get the file descriptors by path prefix
373
- * @method getFileDescriptorsByPath
374
- * @param filePath - the path prefix to match
375
- * @returns {FileDescriptor[]} The file descriptors
376
- */
377
- getFileDescriptorsByPath(filePath) {
378
- const result = [];
379
- const keys = this._cache.keys();
380
- for (const key of keys) {
381
- if (key.startsWith(filePath)) {
382
- const fileDescriptor = this.getFileDescriptor(key);
383
- result.push(fileDescriptor);
384
- }
385
- }
386
- return result;
387
- }
388
- /**
389
- * Get the Absolute Path. If it is already absolute it will return the path as is.
390
- * When strictPaths is enabled, ensures the resolved path stays within cwd boundaries.
391
- * @method getAbsolutePath
392
- * @param filePath - The file path to get the absolute path for
393
- * @returns {string}
394
- * @throws {Error} When strictPaths is true and path would resolve outside cwd
395
- */
396
- getAbsolutePath(filePath) {
397
- if (this.isRelativePath(filePath)) {
398
- const sanitizedPath = filePath.replace(/\0/g, "");
399
- const resolved = import_node_path.default.resolve(this._cwd, sanitizedPath);
400
- if (this._strictPaths) {
401
- const normalizedResolved = import_node_path.default.normalize(resolved);
402
- const normalizedCwd = import_node_path.default.normalize(this._cwd);
403
- const isWithinCwd = normalizedResolved === normalizedCwd || normalizedResolved.startsWith(normalizedCwd + import_node_path.default.sep);
404
- if (!isWithinCwd) {
405
- throw new Error(
406
- `Path traversal attempt blocked: "${filePath}" resolves outside of working directory "${this._cwd}"`
407
- );
408
- }
409
- }
410
- return resolved;
411
- }
412
- return filePath;
413
- }
414
- /**
415
- * Get the Absolute Path with a custom working directory. If it is already absolute it will return the path as is.
416
- * When strictPaths is enabled, ensures the resolved path stays within the provided cwd boundaries.
417
- * @method getAbsolutePathWithCwd
418
- * @param filePath - The file path to get the absolute path for
419
- * @param cwd - The custom working directory to resolve relative paths from
420
- * @returns {string}
421
- * @throws {Error} When strictPaths is true and path would resolve outside the provided cwd
422
- */
423
- getAbsolutePathWithCwd(filePath, cwd) {
424
- if (this.isRelativePath(filePath)) {
425
- const sanitizedPath = filePath.replace(/\0/g, "");
426
- const resolved = import_node_path.default.resolve(cwd, sanitizedPath);
427
- if (this._strictPaths) {
428
- const normalizedResolved = import_node_path.default.normalize(resolved);
429
- const normalizedCwd = import_node_path.default.normalize(cwd);
430
- const isWithinCwd = normalizedResolved === normalizedCwd || normalizedResolved.startsWith(normalizedCwd + import_node_path.default.sep);
431
- if (!isWithinCwd) {
432
- throw new Error(
433
- `Path traversal attempt blocked: "${filePath}" resolves outside of working directory "${cwd}"`
434
- );
435
- }
436
- }
437
- return resolved;
438
- }
439
- return filePath;
440
- }
441
- /**
442
- * Rename cache keys that start with a given path prefix.
443
- * @method renameCacheKeys
444
- * @param oldPath - The old path prefix to rename
445
- * @param newPath - The new path prefix to rename to
446
- */
447
- renameCacheKeys(oldPath, newPath) {
448
- const keys = this._cache.keys();
449
- for (const key of keys) {
450
- if (key.startsWith(oldPath)) {
451
- const newKey = key.replace(oldPath, newPath);
452
- const meta = this._cache.getKey(key);
453
- this._cache.removeKey(key);
454
- this._cache.setKey(newKey, meta);
455
- }
456
- }
457
- }
458
- };
459
- // Annotate the CommonJS export names for ESM import in node:
460
- 0 && (module.exports = {
461
- FileEntryCache,
462
- create,
463
- createFromFile
464
- });
1
+ "use strict";var k=Object.create;var g=Object.defineProperty;var C=Object.getOwnPropertyDescriptor;var w=Object.getOwnPropertyNames;var v=Object.getPrototypeOf,A=Object.prototype.hasOwnProperty;var z=(o,e)=>{for(var s in e)g(o,s,{get:e[s],enumerable:!0})},y=(o,e,s,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of w(e))!A.call(o,t)&&t!==s&&g(o,t,{get:()=>e[t],enumerable:!(i=C(e,t))||i.enumerable});return o};var p=(o,e,s)=>(s=o!=null?k(v(o)):{},y(e||!o||!o.__esModule?g(s,"default",{value:o,enumerable:!0}):s,o)),D=o=>y(g({},"__esModule",{value:!0}),o);var K={};z(K,{FileEntryCache:()=>m,create:()=>f,createFromFile:()=>_,default:()=>d});module.exports=D(K);var F=p(require("crypto"),1),u=p(require("fs"),1),c=p(require("path"),1),l=require("flat-cache");function _(o,e){let s=c.default.basename(o),i=c.default.dirname(o);return f(s,i,e)}function f(o,e,s){let i={...s,cache:{cacheId:o,cacheDir:e}},t=new m(i);if(e){let r=`${e}/${o}`;u.default.existsSync(r)&&(t.cache=(0,l.createFromFile)(r,i.cache))}return t}var d=class{static create=f;static createFromFile=_},m=class{_cache=new l.FlatCache({useClone:!1});_useCheckSum=!1;_hashAlgorithm="md5";_cwd=process.cwd();_strictPaths=!1;_logger;_useAbsolutePathAsKey=!1;constructor(e){e?.cache&&(this._cache=new l.FlatCache(e.cache)),e?.useCheckSum&&(this._useCheckSum=e.useCheckSum),e?.hashAlgorithm&&(this._hashAlgorithm=e.hashAlgorithm),e?.cwd&&(this._cwd=e.cwd),e?.strictPaths!==void 0&&(this._strictPaths=e.strictPaths),e?.useAbsolutePathAsKey!==void 0&&(this._useAbsolutePathAsKey=e.useAbsolutePathAsKey),e?.logger&&(this._logger=e.logger)}get cache(){return this._cache}set cache(e){this._cache=e}get logger(){return this._logger}set logger(e){this._logger=e}get useCheckSum(){return this._useCheckSum}set useCheckSum(e){this._useCheckSum=e}get hashAlgorithm(){return this._hashAlgorithm}set hashAlgorithm(e){this._hashAlgorithm=e}get cwd(){return this._cwd}set cwd(e){this._cwd=e}get strictPaths(){return this._strictPaths}set strictPaths(e){this._strictPaths=e}get useAbsolutePathAsKey(){return this._useAbsolutePathAsKey}set useAbsolutePathAsKey(e){this._useAbsolutePathAsKey=e}getHash(e){return F.default.createHash(this._hashAlgorithm).update(e).digest("hex")}createFileKey(e){let s=e;return this._useAbsolutePathAsKey&&this.isRelativePath(e)&&(s=this.getAbsolutePathWithCwd(e,this._cwd)),s}isRelativePath(e){return!c.default.isAbsolute(e)}deleteCacheFile(){return this._cache.removeCacheFile()}destroy(){this._cache.destroy()}removeEntry(e){let s=this.createFileKey(e);this._cache.removeKey(s)}reconcile(){let{items:e}=this._cache;for(let s of e)this.getFileDescriptor(s.key).notFound&&this._cache.removeKey(s.key);this._cache.save()}hasFileChanged(e){let s=!1,i=this.getFileDescriptor(e);return(!i.err||!i.notFound)&&i.changed&&(s=!0),s}getFileDescriptor(e,s){this._logger?.debug({filePath:e,options:s},"Getting file descriptor");let i,t={key:this.createFileKey(e),changed:!1,meta:{}};this._logger?.trace({key:t.key},"Created file key");let r=this._cache.getKey(t.key);r?this._logger?.trace({metaCache:r},"Found cached meta"):this._logger?.trace("No cached meta found"),t.meta=r?{...r}:{};let a=this.getAbsolutePath(e);this._logger?.trace({absolutePath:a},"Resolved absolute path");let n=s?.useCheckSum??this._useCheckSum;this._logger?.debug({useCheckSum:n},"Using checksum setting");try{if(i=u.default.statSync(a),t.meta.size=i.size,t.meta.mtime=i.mtime.getTime(),this._logger?.trace({size:t.meta.size,mtime:t.meta.mtime},"Read file stats"),n){let h=u.default.readFileSync(a);t.meta.hash=this.getHash(h),this._logger?.trace({hash:t.meta.hash},"Calculated file hash")}}catch(h){this._logger?.error({filePath:e,error:h},"Error reading file"),this.removeEntry(e);let b=!1;return h.message.includes("ENOENT")&&(b=!0,this._logger?.debug({filePath:e},"File not found")),{key:t.key,err:h,notFound:b,meta:{}}}return r?(n===!1&&r?.mtime!==t.meta?.mtime&&(t.changed=!0,this._logger?.debug({filePath:e,oldMtime:r.mtime,newMtime:t.meta.mtime},"File changed: mtime differs")),r?.size!==t.meta?.size&&(t.changed=!0,this._logger?.debug({filePath:e,oldSize:r.size,newSize:t.meta.size},"File changed: size differs")),n&&r?.hash!==t.meta?.hash&&(t.changed=!0,this._logger?.debug({filePath:e,oldHash:r.hash,newHash:t.meta.hash},"File changed: hash differs")),this._cache.setKey(t.key,t.meta),t.changed?this._logger?.info({filePath:e},"File has changed"):this._logger?.debug({filePath:e},"File unchanged"),t):(t.changed=!0,this._cache.setKey(t.key,t.meta),this._logger?.debug({filePath:e},"File not in cache, marked as changed"),t)}normalizeEntries(e){let s=[];if(e){for(let t of e){let r=this.getFileDescriptor(t);s.push(r)}return s}let i=this.cache.keys();for(let t of i){let r=this.getFileDescriptor(t);!r.notFound&&!r.err&&s.push(r)}return s}analyzeFiles(e){let s={changedFiles:[],notFoundFiles:[],notChangedFiles:[]},i=this.normalizeEntries(e);for(let t of i)t.notFound?s.notFoundFiles.push(t.key):t.changed?s.changedFiles.push(t.key):s.notChangedFiles.push(t.key);return s}getUpdatedFiles(e){let s=[],i=this.normalizeEntries(e);for(let t of i)t.changed&&s.push(t.key);return s}getFileDescriptorsByPath(e){let s=[],i=this._cache.keys();for(let t of i)if(t.startsWith(e)){let r=this.getFileDescriptor(t);s.push(r)}return s}getAbsolutePath(e){if(this.isRelativePath(e)){let s=e.replace(/\0/g,""),i=c.default.resolve(this._cwd,s);if(this._strictPaths){let t=c.default.normalize(i),r=c.default.normalize(this._cwd);if(!(t===r||t.startsWith(r+c.default.sep)))throw new Error(`Path traversal attempt blocked: "${e}" resolves outside of working directory "${this._cwd}"`)}return i}return e}getAbsolutePathWithCwd(e,s){if(this.isRelativePath(e)){let i=e.replace(/\0/g,""),t=c.default.resolve(s,i);if(this._strictPaths){let r=c.default.normalize(t),a=c.default.normalize(s);if(!(r===a||r.startsWith(a+c.default.sep)))throw new Error(`Path traversal attempt blocked: "${e}" resolves outside of working directory "${s}"`)}return t}return e}renameCacheKeys(e,s){let i=this._cache.keys();for(let t of i)if(t.startsWith(e)){let r=t.replace(e,s),a=this._cache.getKey(t);this._cache.removeKey(t),this._cache.setKey(r,a)}}};0&&(module.exports={FileEntryCache,create,createFromFile});
package/dist/index.d.cts CHANGED
@@ -1,6 +1,22 @@
1
1
  import { Buffer } from 'node:buffer';
2
2
  import { FlatCacheOptions, FlatCache } from 'flat-cache';
3
3
 
4
+ type ILogger = {
5
+ /** Current log level */
6
+ level?: string;
7
+ /** Trace level logging */
8
+ trace: (message: string | object, ...args: unknown[]) => void;
9
+ /** Debug level logging */
10
+ debug: (message: string | object, ...args: unknown[]) => void;
11
+ /** Info level logging */
12
+ info: (message: string | object, ...args: unknown[]) => void;
13
+ /** Warning level logging */
14
+ warn: (message: string | object, ...args: unknown[]) => void;
15
+ /** Error level logging */
16
+ error: (message: string | object, ...args: unknown[]) => void;
17
+ /** Fatal level logging */
18
+ fatal: (message: string | object, ...args: unknown[]) => void;
19
+ };
4
20
  type FileEntryCacheOptions = {
5
21
  /** Whether to use file modified time for change detection (default: true) */
6
22
  useModifiedTime?: boolean;
@@ -12,6 +28,10 @@ type FileEntryCacheOptions = {
12
28
  cwd?: string;
13
29
  /** Restrict file access to within cwd boundaries (default: true) */
14
30
  strictPaths?: boolean;
31
+ /** Whether to use absolute path as cache key (default: false) */
32
+ useAbsolutePathAsKey?: boolean;
33
+ /** Logger instance for logging (default: undefined) */
34
+ logger?: ILogger;
15
35
  /** Options for the underlying flat cache */
16
36
  cache?: FlatCacheOptions;
17
37
  };
@@ -40,6 +60,8 @@ type FileDescriptorMeta = {
40
60
  hash?: string;
41
61
  /** Custom data associated with the file (e.g., lint results, metadata) */
42
62
  data?: unknown;
63
+ /** Allow any additional custom properties */
64
+ [key: string]: unknown;
43
65
  };
44
66
  type AnalyzedFiles = {
45
67
  /** Array of file paths that have changed since last cache */
@@ -52,20 +74,19 @@ type AnalyzedFiles = {
52
74
  /**
53
75
  * Create a new FileEntryCache instance from a file path
54
76
  * @param filePath - The path to the cache file
55
- * @param useCheckSum - Whether to use checksum to detect file changes (default: false)
56
- * @param cwd - The current working directory for resolving relative paths (default: process.cwd())
77
+ * @param options - create options such as useChecksum, cwd, and more
57
78
  * @returns A new FileEntryCache instance
58
79
  */
59
- declare function createFromFile(filePath: string, useCheckSum?: boolean, cwd?: string): FileEntryCache;
80
+ declare function createFromFile(filePath: string, options?: CreateOptions): FileEntryCache;
81
+ type CreateOptions = Omit<FileEntryCacheOptions, "cache">;
60
82
  /**
61
83
  * Create a new FileEntryCache instance
62
84
  * @param cacheId - The cache file name
63
85
  * @param cacheDirectory - The directory to store the cache file (default: undefined, cache won't be persisted)
64
- * @param useCheckSum - Whether to use checksum to detect file changes (default: false)
65
- * @param cwd - The current working directory for resolving relative paths (default: process.cwd())
86
+ * @param options - Whether to use checksum to detect file changes (default: false)
66
87
  * @returns A new FileEntryCache instance
67
88
  */
68
- declare function create(cacheId: string, cacheDirectory?: string, useCheckSum?: boolean, cwd?: string): FileEntryCache;
89
+ declare function create(cacheId: string, cacheDirectory?: string, options?: CreateOptions): FileEntryCache;
69
90
  declare class FileEntryDefault {
70
91
  static create: typeof create;
71
92
  static createFromFile: typeof createFromFile;
@@ -76,6 +97,8 @@ declare class FileEntryCache {
76
97
  private _hashAlgorithm;
77
98
  private _cwd;
78
99
  private _strictPaths;
100
+ private _logger?;
101
+ private _useAbsolutePathAsKey;
79
102
  /**
80
103
  * Create a new FileEntryCache instance
81
104
  * @param options - The options for the FileEntryCache (all properties are optional with defaults)
@@ -91,6 +114,16 @@ declare class FileEntryCache {
91
114
  * @param {FlatCache} cache - The cache to set
92
115
  */
93
116
  set cache(cache: FlatCache);
117
+ /**
118
+ * Get the logger
119
+ * @returns {ILogger | undefined} The logger instance
120
+ */
121
+ get logger(): ILogger | undefined;
122
+ /**
123
+ * Set the logger
124
+ * @param {ILogger | undefined} logger - The logger to set
125
+ */
126
+ set logger(logger: ILogger | undefined);
94
127
  /**
95
128
  * Use the hash to check if the file has changed
96
129
  * @returns {boolean} if the hash is used to check if the file has changed (default: false)
@@ -131,6 +164,16 @@ declare class FileEntryCache {
131
164
  * @param {boolean} value - The value to set
132
165
  */
133
166
  set strictPaths(value: boolean);
167
+ /**
168
+ * Get whether to use absolute path as cache key
169
+ * @returns {boolean} Whether cache keys use absolute paths (default: false)
170
+ */
171
+ get useAbsolutePathAsKey(): boolean;
172
+ /**
173
+ * Set whether to use absolute path as cache key
174
+ * @param {boolean} value - The value to set
175
+ */
176
+ set useAbsolutePathAsKey(value: boolean);
134
177
  /**
135
178
  * Given a buffer, calculate md5 hash of its content.
136
179
  * @method getHash
@@ -245,4 +288,4 @@ declare class FileEntryCache {
245
288
  renameCacheKeys(oldPath: string, newPath: string): void;
246
289
  }
247
290
 
248
- export { type AnalyzedFiles, type FileDescriptor, type FileDescriptorMeta, FileEntryCache, type FileEntryCacheOptions, type GetFileDescriptorOptions, create, createFromFile, FileEntryDefault as default };
291
+ export { type AnalyzedFiles, type CreateOptions, type FileDescriptor, type FileDescriptorMeta, FileEntryCache, type FileEntryCacheOptions, type GetFileDescriptorOptions, type ILogger, create, createFromFile, FileEntryDefault as default };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,22 @@
1
1
  import { Buffer } from 'node:buffer';
2
2
  import { FlatCacheOptions, FlatCache } from 'flat-cache';
3
3
 
4
+ type ILogger = {
5
+ /** Current log level */
6
+ level?: string;
7
+ /** Trace level logging */
8
+ trace: (message: string | object, ...args: unknown[]) => void;
9
+ /** Debug level logging */
10
+ debug: (message: string | object, ...args: unknown[]) => void;
11
+ /** Info level logging */
12
+ info: (message: string | object, ...args: unknown[]) => void;
13
+ /** Warning level logging */
14
+ warn: (message: string | object, ...args: unknown[]) => void;
15
+ /** Error level logging */
16
+ error: (message: string | object, ...args: unknown[]) => void;
17
+ /** Fatal level logging */
18
+ fatal: (message: string | object, ...args: unknown[]) => void;
19
+ };
4
20
  type FileEntryCacheOptions = {
5
21
  /** Whether to use file modified time for change detection (default: true) */
6
22
  useModifiedTime?: boolean;
@@ -12,6 +28,10 @@ type FileEntryCacheOptions = {
12
28
  cwd?: string;
13
29
  /** Restrict file access to within cwd boundaries (default: true) */
14
30
  strictPaths?: boolean;
31
+ /** Whether to use absolute path as cache key (default: false) */
32
+ useAbsolutePathAsKey?: boolean;
33
+ /** Logger instance for logging (default: undefined) */
34
+ logger?: ILogger;
15
35
  /** Options for the underlying flat cache */
16
36
  cache?: FlatCacheOptions;
17
37
  };
@@ -40,6 +60,8 @@ type FileDescriptorMeta = {
40
60
  hash?: string;
41
61
  /** Custom data associated with the file (e.g., lint results, metadata) */
42
62
  data?: unknown;
63
+ /** Allow any additional custom properties */
64
+ [key: string]: unknown;
43
65
  };
44
66
  type AnalyzedFiles = {
45
67
  /** Array of file paths that have changed since last cache */
@@ -52,20 +74,19 @@ type AnalyzedFiles = {
52
74
  /**
53
75
  * Create a new FileEntryCache instance from a file path
54
76
  * @param filePath - The path to the cache file
55
- * @param useCheckSum - Whether to use checksum to detect file changes (default: false)
56
- * @param cwd - The current working directory for resolving relative paths (default: process.cwd())
77
+ * @param options - create options such as useChecksum, cwd, and more
57
78
  * @returns A new FileEntryCache instance
58
79
  */
59
- declare function createFromFile(filePath: string, useCheckSum?: boolean, cwd?: string): FileEntryCache;
80
+ declare function createFromFile(filePath: string, options?: CreateOptions): FileEntryCache;
81
+ type CreateOptions = Omit<FileEntryCacheOptions, "cache">;
60
82
  /**
61
83
  * Create a new FileEntryCache instance
62
84
  * @param cacheId - The cache file name
63
85
  * @param cacheDirectory - The directory to store the cache file (default: undefined, cache won't be persisted)
64
- * @param useCheckSum - Whether to use checksum to detect file changes (default: false)
65
- * @param cwd - The current working directory for resolving relative paths (default: process.cwd())
86
+ * @param options - Whether to use checksum to detect file changes (default: false)
66
87
  * @returns A new FileEntryCache instance
67
88
  */
68
- declare function create(cacheId: string, cacheDirectory?: string, useCheckSum?: boolean, cwd?: string): FileEntryCache;
89
+ declare function create(cacheId: string, cacheDirectory?: string, options?: CreateOptions): FileEntryCache;
69
90
  declare class FileEntryDefault {
70
91
  static create: typeof create;
71
92
  static createFromFile: typeof createFromFile;
@@ -76,6 +97,8 @@ declare class FileEntryCache {
76
97
  private _hashAlgorithm;
77
98
  private _cwd;
78
99
  private _strictPaths;
100
+ private _logger?;
101
+ private _useAbsolutePathAsKey;
79
102
  /**
80
103
  * Create a new FileEntryCache instance
81
104
  * @param options - The options for the FileEntryCache (all properties are optional with defaults)
@@ -91,6 +114,16 @@ declare class FileEntryCache {
91
114
  * @param {FlatCache} cache - The cache to set
92
115
  */
93
116
  set cache(cache: FlatCache);
117
+ /**
118
+ * Get the logger
119
+ * @returns {ILogger | undefined} The logger instance
120
+ */
121
+ get logger(): ILogger | undefined;
122
+ /**
123
+ * Set the logger
124
+ * @param {ILogger | undefined} logger - The logger to set
125
+ */
126
+ set logger(logger: ILogger | undefined);
94
127
  /**
95
128
  * Use the hash to check if the file has changed
96
129
  * @returns {boolean} if the hash is used to check if the file has changed (default: false)
@@ -131,6 +164,16 @@ declare class FileEntryCache {
131
164
  * @param {boolean} value - The value to set
132
165
  */
133
166
  set strictPaths(value: boolean);
167
+ /**
168
+ * Get whether to use absolute path as cache key
169
+ * @returns {boolean} Whether cache keys use absolute paths (default: false)
170
+ */
171
+ get useAbsolutePathAsKey(): boolean;
172
+ /**
173
+ * Set whether to use absolute path as cache key
174
+ * @param {boolean} value - The value to set
175
+ */
176
+ set useAbsolutePathAsKey(value: boolean);
134
177
  /**
135
178
  * Given a buffer, calculate md5 hash of its content.
136
179
  * @method getHash
@@ -245,4 +288,4 @@ declare class FileEntryCache {
245
288
  renameCacheKeys(oldPath: string, newPath: string): void;
246
289
  }
247
290
 
248
- export { type AnalyzedFiles, type FileDescriptor, type FileDescriptorMeta, FileEntryCache, type FileEntryCacheOptions, type GetFileDescriptorOptions, create, createFromFile, FileEntryDefault as default };
291
+ export { type AnalyzedFiles, type CreateOptions, type FileDescriptor, type FileDescriptorMeta, FileEntryCache, type FileEntryCacheOptions, type GetFileDescriptorOptions, type ILogger, create, createFromFile, FileEntryDefault as default };
package/dist/index.js CHANGED
@@ -1,430 +1 @@
1
- // src/index.ts
2
- import crypto from "crypto";
3
- import fs from "fs";
4
- import path from "path";
5
- import {
6
- createFromFile as createFlatCacheFile,
7
- FlatCache
8
- } from "flat-cache";
9
- function createFromFile(filePath, useCheckSum, cwd) {
10
- const fname = path.basename(filePath);
11
- const directory = path.dirname(filePath);
12
- return create(fname, directory, useCheckSum, cwd);
13
- }
14
- function create(cacheId, cacheDirectory, useCheckSum, cwd) {
15
- const options = {
16
- useCheckSum,
17
- cwd,
18
- cache: {
19
- cacheId,
20
- cacheDir: cacheDirectory
21
- }
22
- };
23
- const fileEntryCache = new FileEntryCache(options);
24
- if (cacheDirectory) {
25
- const cachePath = `${cacheDirectory}/${cacheId}`;
26
- if (fs.existsSync(cachePath)) {
27
- fileEntryCache.cache = createFlatCacheFile(cachePath, options.cache);
28
- }
29
- }
30
- return fileEntryCache;
31
- }
32
- var FileEntryDefault = class {
33
- static create = create;
34
- static createFromFile = createFromFile;
35
- };
36
- var FileEntryCache = class {
37
- _cache = new FlatCache({ useClone: false });
38
- _useCheckSum = false;
39
- _hashAlgorithm = "md5";
40
- _cwd = process.cwd();
41
- _strictPaths = true;
42
- /**
43
- * Create a new FileEntryCache instance
44
- * @param options - The options for the FileEntryCache (all properties are optional with defaults)
45
- */
46
- constructor(options) {
47
- if (options?.cache) {
48
- this._cache = new FlatCache(options.cache);
49
- }
50
- if (options?.useCheckSum) {
51
- this._useCheckSum = options.useCheckSum;
52
- }
53
- if (options?.hashAlgorithm) {
54
- this._hashAlgorithm = options.hashAlgorithm;
55
- }
56
- if (options?.cwd) {
57
- this._cwd = options.cwd;
58
- }
59
- if (options?.strictPaths !== void 0) {
60
- this._strictPaths = options.strictPaths;
61
- }
62
- }
63
- /**
64
- * Get the cache
65
- * @returns {FlatCache} The cache
66
- */
67
- get cache() {
68
- return this._cache;
69
- }
70
- /**
71
- * Set the cache
72
- * @param {FlatCache} cache - The cache to set
73
- */
74
- set cache(cache) {
75
- this._cache = cache;
76
- }
77
- /**
78
- * Use the hash to check if the file has changed
79
- * @returns {boolean} if the hash is used to check if the file has changed (default: false)
80
- */
81
- get useCheckSum() {
82
- return this._useCheckSum;
83
- }
84
- /**
85
- * Set the useCheckSum value
86
- * @param {boolean} value - The value to set
87
- */
88
- set useCheckSum(value) {
89
- this._useCheckSum = value;
90
- }
91
- /**
92
- * Get the hash algorithm
93
- * @returns {string} The hash algorithm (default: 'md5')
94
- */
95
- get hashAlgorithm() {
96
- return this._hashAlgorithm;
97
- }
98
- /**
99
- * Set the hash algorithm
100
- * @param {string} value - The value to set
101
- */
102
- set hashAlgorithm(value) {
103
- this._hashAlgorithm = value;
104
- }
105
- /**
106
- * Get the current working directory
107
- * @returns {string} The current working directory (default: process.cwd())
108
- */
109
- get cwd() {
110
- return this._cwd;
111
- }
112
- /**
113
- * Set the current working directory
114
- * @param {string} value - The value to set
115
- */
116
- set cwd(value) {
117
- this._cwd = value;
118
- }
119
- /**
120
- * Get whether to restrict paths to cwd boundaries
121
- * @returns {boolean} Whether strict path checking is enabled (default: true)
122
- */
123
- get strictPaths() {
124
- return this._strictPaths;
125
- }
126
- /**
127
- * Set whether to restrict paths to cwd boundaries
128
- * @param {boolean} value - The value to set
129
- */
130
- set strictPaths(value) {
131
- this._strictPaths = value;
132
- }
133
- /**
134
- * Given a buffer, calculate md5 hash of its content.
135
- * @method getHash
136
- * @param {Buffer} buffer buffer to calculate hash on
137
- * @return {String} content hash digest
138
- */
139
- getHash(buffer) {
140
- return crypto.createHash(this._hashAlgorithm).update(buffer).digest("hex");
141
- }
142
- /**
143
- * Create the key for the file path used for caching.
144
- * @method createFileKey
145
- * @param {String} filePath
146
- * @return {String}
147
- */
148
- createFileKey(filePath) {
149
- return filePath;
150
- }
151
- /**
152
- * Check if the file path is a relative path
153
- * @method isRelativePath
154
- * @param filePath - The file path to check
155
- * @returns {boolean} if the file path is a relative path, false otherwise
156
- */
157
- isRelativePath(filePath) {
158
- return !path.isAbsolute(filePath);
159
- }
160
- /**
161
- * Delete the cache file from the disk
162
- * @method deleteCacheFile
163
- * @return {boolean} true if the file was deleted, false otherwise
164
- */
165
- deleteCacheFile() {
166
- return this._cache.removeCacheFile();
167
- }
168
- /**
169
- * Remove the cache from the file and clear the memory cache
170
- * @method destroy
171
- */
172
- destroy() {
173
- this._cache.destroy();
174
- }
175
- /**
176
- * Remove and Entry From the Cache
177
- * @method removeEntry
178
- * @param filePath - The file path to remove from the cache
179
- */
180
- removeEntry(filePath) {
181
- const key = this.createFileKey(filePath);
182
- this._cache.removeKey(key);
183
- }
184
- /**
185
- * Reconcile the cache
186
- * @method reconcile
187
- */
188
- reconcile() {
189
- const { items } = this._cache;
190
- for (const item of items) {
191
- const fileDescriptor = this.getFileDescriptor(item.key);
192
- if (fileDescriptor.notFound) {
193
- this._cache.removeKey(item.key);
194
- }
195
- }
196
- this._cache.save();
197
- }
198
- /**
199
- * Check if the file has changed
200
- * @method hasFileChanged
201
- * @param filePath - The file path to check
202
- * @returns {boolean} if the file has changed, false otherwise
203
- */
204
- hasFileChanged(filePath) {
205
- let result = false;
206
- const fileDescriptor = this.getFileDescriptor(filePath);
207
- if ((!fileDescriptor.err || !fileDescriptor.notFound) && fileDescriptor.changed) {
208
- result = true;
209
- }
210
- return result;
211
- }
212
- /**
213
- * Get the file descriptor for the file path
214
- * @method getFileDescriptor
215
- * @param filePath - The file path to get the file descriptor for
216
- * @param options - The options for getting the file descriptor
217
- * @returns The file descriptor
218
- */
219
- getFileDescriptor(filePath, options) {
220
- let fstat;
221
- const result = {
222
- key: this.createFileKey(filePath),
223
- changed: false,
224
- meta: {}
225
- };
226
- result.meta = this._cache.getKey(result.key) ?? {};
227
- const absolutePath = this.getAbsolutePath(filePath);
228
- const useCheckSumValue = options?.useCheckSum ?? this._useCheckSum;
229
- try {
230
- fstat = fs.statSync(absolutePath);
231
- result.meta = {
232
- size: fstat.size
233
- };
234
- result.meta.mtime = fstat.mtime.getTime();
235
- if (useCheckSumValue) {
236
- const buffer = fs.readFileSync(absolutePath);
237
- result.meta.hash = this.getHash(buffer);
238
- }
239
- } catch (error) {
240
- this.removeEntry(filePath);
241
- let notFound = false;
242
- if (error.message.includes("ENOENT")) {
243
- notFound = true;
244
- }
245
- return {
246
- key: result.key,
247
- err: error,
248
- notFound,
249
- meta: {}
250
- };
251
- }
252
- const metaCache = this._cache.getKey(result.key);
253
- if (!metaCache) {
254
- result.changed = true;
255
- this._cache.setKey(result.key, result.meta);
256
- return result;
257
- }
258
- if (result.meta.data === void 0) {
259
- result.meta.data = metaCache.data;
260
- }
261
- if (useCheckSumValue === false && metaCache?.mtime !== result.meta?.mtime) {
262
- result.changed = true;
263
- }
264
- if (metaCache?.size !== result.meta?.size) {
265
- result.changed = true;
266
- }
267
- if (useCheckSumValue && metaCache?.hash !== result.meta?.hash) {
268
- result.changed = true;
269
- }
270
- this._cache.setKey(result.key, result.meta);
271
- return result;
272
- }
273
- /**
274
- * Get the file descriptors for the files
275
- * @method normalizeEntries
276
- * @param files?: string[] - The files to get the file descriptors for
277
- * @returns The file descriptors
278
- */
279
- normalizeEntries(files) {
280
- const result = [];
281
- if (files) {
282
- for (const file of files) {
283
- const fileDescriptor = this.getFileDescriptor(file);
284
- result.push(fileDescriptor);
285
- }
286
- return result;
287
- }
288
- const keys = this.cache.keys();
289
- for (const key of keys) {
290
- const fileDescriptor = this.getFileDescriptor(key);
291
- if (!fileDescriptor.notFound && !fileDescriptor.err) {
292
- result.push(fileDescriptor);
293
- }
294
- }
295
- return result;
296
- }
297
- /**
298
- * Analyze the files
299
- * @method analyzeFiles
300
- * @param files - The files to analyze
301
- * @returns {AnalyzedFiles} The analysis of the files
302
- */
303
- analyzeFiles(files) {
304
- const result = {
305
- changedFiles: [],
306
- notFoundFiles: [],
307
- notChangedFiles: []
308
- };
309
- const fileDescriptors = this.normalizeEntries(files);
310
- for (const fileDescriptor of fileDescriptors) {
311
- if (fileDescriptor.notFound) {
312
- result.notFoundFiles.push(fileDescriptor.key);
313
- } else if (fileDescriptor.changed) {
314
- result.changedFiles.push(fileDescriptor.key);
315
- } else {
316
- result.notChangedFiles.push(fileDescriptor.key);
317
- }
318
- }
319
- return result;
320
- }
321
- /**
322
- * Get the updated files
323
- * @method getUpdatedFiles
324
- * @param files - The files to get the updated files for
325
- * @returns {string[]} The updated files
326
- */
327
- getUpdatedFiles(files) {
328
- const result = [];
329
- const fileDescriptors = this.normalizeEntries(files);
330
- for (const fileDescriptor of fileDescriptors) {
331
- if (fileDescriptor.changed) {
332
- result.push(fileDescriptor.key);
333
- }
334
- }
335
- return result;
336
- }
337
- /**
338
- * Get the file descriptors by path prefix
339
- * @method getFileDescriptorsByPath
340
- * @param filePath - the path prefix to match
341
- * @returns {FileDescriptor[]} The file descriptors
342
- */
343
- getFileDescriptorsByPath(filePath) {
344
- const result = [];
345
- const keys = this._cache.keys();
346
- for (const key of keys) {
347
- if (key.startsWith(filePath)) {
348
- const fileDescriptor = this.getFileDescriptor(key);
349
- result.push(fileDescriptor);
350
- }
351
- }
352
- return result;
353
- }
354
- /**
355
- * Get the Absolute Path. If it is already absolute it will return the path as is.
356
- * When strictPaths is enabled, ensures the resolved path stays within cwd boundaries.
357
- * @method getAbsolutePath
358
- * @param filePath - The file path to get the absolute path for
359
- * @returns {string}
360
- * @throws {Error} When strictPaths is true and path would resolve outside cwd
361
- */
362
- getAbsolutePath(filePath) {
363
- if (this.isRelativePath(filePath)) {
364
- const sanitizedPath = filePath.replace(/\0/g, "");
365
- const resolved = path.resolve(this._cwd, sanitizedPath);
366
- if (this._strictPaths) {
367
- const normalizedResolved = path.normalize(resolved);
368
- const normalizedCwd = path.normalize(this._cwd);
369
- const isWithinCwd = normalizedResolved === normalizedCwd || normalizedResolved.startsWith(normalizedCwd + path.sep);
370
- if (!isWithinCwd) {
371
- throw new Error(
372
- `Path traversal attempt blocked: "${filePath}" resolves outside of working directory "${this._cwd}"`
373
- );
374
- }
375
- }
376
- return resolved;
377
- }
378
- return filePath;
379
- }
380
- /**
381
- * Get the Absolute Path with a custom working directory. If it is already absolute it will return the path as is.
382
- * When strictPaths is enabled, ensures the resolved path stays within the provided cwd boundaries.
383
- * @method getAbsolutePathWithCwd
384
- * @param filePath - The file path to get the absolute path for
385
- * @param cwd - The custom working directory to resolve relative paths from
386
- * @returns {string}
387
- * @throws {Error} When strictPaths is true and path would resolve outside the provided cwd
388
- */
389
- getAbsolutePathWithCwd(filePath, cwd) {
390
- if (this.isRelativePath(filePath)) {
391
- const sanitizedPath = filePath.replace(/\0/g, "");
392
- const resolved = path.resolve(cwd, sanitizedPath);
393
- if (this._strictPaths) {
394
- const normalizedResolved = path.normalize(resolved);
395
- const normalizedCwd = path.normalize(cwd);
396
- const isWithinCwd = normalizedResolved === normalizedCwd || normalizedResolved.startsWith(normalizedCwd + path.sep);
397
- if (!isWithinCwd) {
398
- throw new Error(
399
- `Path traversal attempt blocked: "${filePath}" resolves outside of working directory "${cwd}"`
400
- );
401
- }
402
- }
403
- return resolved;
404
- }
405
- return filePath;
406
- }
407
- /**
408
- * Rename cache keys that start with a given path prefix.
409
- * @method renameCacheKeys
410
- * @param oldPath - The old path prefix to rename
411
- * @param newPath - The new path prefix to rename to
412
- */
413
- renameCacheKeys(oldPath, newPath) {
414
- const keys = this._cache.keys();
415
- for (const key of keys) {
416
- if (key.startsWith(oldPath)) {
417
- const newKey = key.replace(oldPath, newPath);
418
- const meta = this._cache.getKey(key);
419
- this._cache.removeKey(key);
420
- this._cache.setKey(newKey, meta);
421
- }
422
- }
423
- }
424
- };
425
- export {
426
- FileEntryCache,
427
- create,
428
- createFromFile,
429
- FileEntryDefault as default
430
- };
1
+ import f from"crypto";import l from"fs";import o from"path";import{createFromFile as b,FlatCache as m}from"flat-cache";function y(a,e){let s=o.basename(a),r=o.dirname(a);return p(s,r,e)}function p(a,e,s){let r={...s,cache:{cacheId:a,cacheDir:e}},t=new u(r);if(e){let i=`${e}/${a}`;l.existsSync(i)&&(t.cache=b(i,r.cache))}return t}var g=class{static create=p;static createFromFile=y},u=class{_cache=new m({useClone:!1});_useCheckSum=!1;_hashAlgorithm="md5";_cwd=process.cwd();_strictPaths=!1;_logger;_useAbsolutePathAsKey=!1;constructor(e){e?.cache&&(this._cache=new m(e.cache)),e?.useCheckSum&&(this._useCheckSum=e.useCheckSum),e?.hashAlgorithm&&(this._hashAlgorithm=e.hashAlgorithm),e?.cwd&&(this._cwd=e.cwd),e?.strictPaths!==void 0&&(this._strictPaths=e.strictPaths),e?.useAbsolutePathAsKey!==void 0&&(this._useAbsolutePathAsKey=e.useAbsolutePathAsKey),e?.logger&&(this._logger=e.logger)}get cache(){return this._cache}set cache(e){this._cache=e}get logger(){return this._logger}set logger(e){this._logger=e}get useCheckSum(){return this._useCheckSum}set useCheckSum(e){this._useCheckSum=e}get hashAlgorithm(){return this._hashAlgorithm}set hashAlgorithm(e){this._hashAlgorithm=e}get cwd(){return this._cwd}set cwd(e){this._cwd=e}get strictPaths(){return this._strictPaths}set strictPaths(e){this._strictPaths=e}get useAbsolutePathAsKey(){return this._useAbsolutePathAsKey}set useAbsolutePathAsKey(e){this._useAbsolutePathAsKey=e}getHash(e){return f.createHash(this._hashAlgorithm).update(e).digest("hex")}createFileKey(e){let s=e;return this._useAbsolutePathAsKey&&this.isRelativePath(e)&&(s=this.getAbsolutePathWithCwd(e,this._cwd)),s}isRelativePath(e){return!o.isAbsolute(e)}deleteCacheFile(){return this._cache.removeCacheFile()}destroy(){this._cache.destroy()}removeEntry(e){let s=this.createFileKey(e);this._cache.removeKey(s)}reconcile(){let{items:e}=this._cache;for(let s of e)this.getFileDescriptor(s.key).notFound&&this._cache.removeKey(s.key);this._cache.save()}hasFileChanged(e){let s=!1,r=this.getFileDescriptor(e);return(!r.err||!r.notFound)&&r.changed&&(s=!0),s}getFileDescriptor(e,s){this._logger?.debug({filePath:e,options:s},"Getting file descriptor");let r,t={key:this.createFileKey(e),changed:!1,meta:{}};this._logger?.trace({key:t.key},"Created file key");let i=this._cache.getKey(t.key);i?this._logger?.trace({metaCache:i},"Found cached meta"):this._logger?.trace("No cached meta found"),t.meta=i?{...i}:{};let c=this.getAbsolutePath(e);this._logger?.trace({absolutePath:c},"Resolved absolute path");let n=s?.useCheckSum??this._useCheckSum;this._logger?.debug({useCheckSum:n},"Using checksum setting");try{if(r=l.statSync(c),t.meta.size=r.size,t.meta.mtime=r.mtime.getTime(),this._logger?.trace({size:t.meta.size,mtime:t.meta.mtime},"Read file stats"),n){let h=l.readFileSync(c);t.meta.hash=this.getHash(h),this._logger?.trace({hash:t.meta.hash},"Calculated file hash")}}catch(h){this._logger?.error({filePath:e,error:h},"Error reading file"),this.removeEntry(e);let d=!1;return h.message.includes("ENOENT")&&(d=!0,this._logger?.debug({filePath:e},"File not found")),{key:t.key,err:h,notFound:d,meta:{}}}return i?(n===!1&&i?.mtime!==t.meta?.mtime&&(t.changed=!0,this._logger?.debug({filePath:e,oldMtime:i.mtime,newMtime:t.meta.mtime},"File changed: mtime differs")),i?.size!==t.meta?.size&&(t.changed=!0,this._logger?.debug({filePath:e,oldSize:i.size,newSize:t.meta.size},"File changed: size differs")),n&&i?.hash!==t.meta?.hash&&(t.changed=!0,this._logger?.debug({filePath:e,oldHash:i.hash,newHash:t.meta.hash},"File changed: hash differs")),this._cache.setKey(t.key,t.meta),t.changed?this._logger?.info({filePath:e},"File has changed"):this._logger?.debug({filePath:e},"File unchanged"),t):(t.changed=!0,this._cache.setKey(t.key,t.meta),this._logger?.debug({filePath:e},"File not in cache, marked as changed"),t)}normalizeEntries(e){let s=[];if(e){for(let t of e){let i=this.getFileDescriptor(t);s.push(i)}return s}let r=this.cache.keys();for(let t of r){let i=this.getFileDescriptor(t);!i.notFound&&!i.err&&s.push(i)}return s}analyzeFiles(e){let s={changedFiles:[],notFoundFiles:[],notChangedFiles:[]},r=this.normalizeEntries(e);for(let t of r)t.notFound?s.notFoundFiles.push(t.key):t.changed?s.changedFiles.push(t.key):s.notChangedFiles.push(t.key);return s}getUpdatedFiles(e){let s=[],r=this.normalizeEntries(e);for(let t of r)t.changed&&s.push(t.key);return s}getFileDescriptorsByPath(e){let s=[],r=this._cache.keys();for(let t of r)if(t.startsWith(e)){let i=this.getFileDescriptor(t);s.push(i)}return s}getAbsolutePath(e){if(this.isRelativePath(e)){let s=e.replace(/\0/g,""),r=o.resolve(this._cwd,s);if(this._strictPaths){let t=o.normalize(r),i=o.normalize(this._cwd);if(!(t===i||t.startsWith(i+o.sep)))throw new Error(`Path traversal attempt blocked: "${e}" resolves outside of working directory "${this._cwd}"`)}return r}return e}getAbsolutePathWithCwd(e,s){if(this.isRelativePath(e)){let r=e.replace(/\0/g,""),t=o.resolve(s,r);if(this._strictPaths){let i=o.normalize(t),c=o.normalize(s);if(!(i===c||i.startsWith(c+o.sep)))throw new Error(`Path traversal attempt blocked: "${e}" resolves outside of working directory "${s}"`)}return t}return e}renameCacheKeys(e,s){let r=this._cache.keys();for(let t of r)if(t.startsWith(e)){let i=t.replace(e,s),c=this._cache.getKey(t);this._cache.removeKey(t),this._cache.setKey(i,c)}}};export{u as FileEntryCache,p as create,y as createFromFile,g as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "file-entry-cache",
3
- "version": "11.0.0-beta.3",
3
+ "version": "11.0.0-beta.5",
4
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
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -30,8 +30,9 @@
30
30
  ],
31
31
  "devDependencies": {
32
32
  "@biomejs/biome": "^2.2.5",
33
- "@types/node": "^24.6.2",
33
+ "@types/node": "^24.7.1",
34
34
  "@vitest/coverage-v8": "^3.2.4",
35
+ "pino": "^10.0.0",
35
36
  "rimraf": "^6.0.1",
36
37
  "tsup": "^8.5.0",
37
38
  "typescript": "^5.9.3",
@@ -45,7 +46,7 @@
45
46
  "license"
46
47
  ],
47
48
  "scripts": {
48
- "build": "rimraf ./dist && tsup src/index.ts --format cjs,esm --dts --clean",
49
+ "build": "rimraf ./dist && tsup src/index.ts --format cjs,esm --dts --clean --minify",
49
50
  "prepublish": "pnpm build",
50
51
  "lint": "biome check --write --error-on-warnings",
51
52
  "test": "pnpm lint && vitest run --coverage",