file-entry-cache 11.0.0-beta.2 → 11.0.0-beta.4

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,483 +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
- _useModifiedTime = true;
74
- _hashAlgorithm = "md5";
75
- _cwd = process.cwd();
76
- _strictPaths = true;
77
- /**
78
- * Create a new FileEntryCache instance
79
- * @param options - The options for the FileEntryCache (all properties are optional with defaults)
80
- */
81
- constructor(options) {
82
- if (options?.cache) {
83
- this._cache = new import_flat_cache.FlatCache(options.cache);
84
- }
85
- if (options?.useModifiedTime) {
86
- this._useModifiedTime = options.useModifiedTime;
87
- }
88
- if (options?.useCheckSum) {
89
- this._useCheckSum = options.useCheckSum;
90
- }
91
- if (options?.hashAlgorithm) {
92
- this._hashAlgorithm = options.hashAlgorithm;
93
- }
94
- if (options?.cwd) {
95
- this._cwd = options.cwd;
96
- }
97
- if (options?.strictPaths !== void 0) {
98
- this._strictPaths = options.strictPaths;
99
- }
100
- }
101
- /**
102
- * Get the cache
103
- * @returns {FlatCache} The cache
104
- */
105
- get cache() {
106
- return this._cache;
107
- }
108
- /**
109
- * Set the cache
110
- * @param {FlatCache} cache - The cache to set
111
- */
112
- set cache(cache) {
113
- this._cache = cache;
114
- }
115
- /**
116
- * Use the hash to check if the file has changed
117
- * @returns {boolean} if the hash is used to check if the file has changed (default: false)
118
- */
119
- get useCheckSum() {
120
- return this._useCheckSum;
121
- }
122
- /**
123
- * Set the useCheckSum value
124
- * @param {boolean} value - The value to set
125
- */
126
- set useCheckSum(value) {
127
- this._useCheckSum = value;
128
- }
129
- /**
130
- * Use the modified time to check if the file has changed
131
- * @returns {boolean} if the modified time is used to check if the file has changed (default: true)
132
- */
133
- get useModifiedTime() {
134
- return this._useModifiedTime;
135
- }
136
- /**
137
- * Set the useModifiedTime value
138
- * @param {boolean} value - The value to set
139
- */
140
- set useModifiedTime(value) {
141
- this._useModifiedTime = value;
142
- }
143
- /**
144
- * Get the hash algorithm
145
- * @returns {string} The hash algorithm (default: 'md5')
146
- */
147
- get hashAlgorithm() {
148
- return this._hashAlgorithm;
149
- }
150
- /**
151
- * Set the hash algorithm
152
- * @param {string} value - The value to set
153
- */
154
- set hashAlgorithm(value) {
155
- this._hashAlgorithm = value;
156
- }
157
- /**
158
- * Get the current working directory
159
- * @returns {string} The current working directory (default: process.cwd())
160
- */
161
- get cwd() {
162
- return this._cwd;
163
- }
164
- /**
165
- * Set the current working directory
166
- * @param {string} value - The value to set
167
- */
168
- set cwd(value) {
169
- this._cwd = value;
170
- }
171
- /**
172
- * Get whether to restrict paths to cwd boundaries
173
- * @returns {boolean} Whether strict path checking is enabled (default: true)
174
- */
175
- get strictPaths() {
176
- return this._strictPaths;
177
- }
178
- /**
179
- * Set whether to restrict paths to cwd boundaries
180
- * @param {boolean} value - The value to set
181
- */
182
- set strictPaths(value) {
183
- this._strictPaths = value;
184
- }
185
- /**
186
- * Given a buffer, calculate md5 hash of its content.
187
- * @method getHash
188
- * @param {Buffer} buffer buffer to calculate hash on
189
- * @return {String} content hash digest
190
- */
191
- getHash(buffer) {
192
- return import_node_crypto.default.createHash(this._hashAlgorithm).update(buffer).digest("hex");
193
- }
194
- /**
195
- * Create the key for the file path used for caching.
196
- * @method createFileKey
197
- * @param {String} filePath
198
- * @return {String}
199
- */
200
- createFileKey(filePath) {
201
- return filePath;
202
- }
203
- /**
204
- * Check if the file path is a relative path
205
- * @method isRelativePath
206
- * @param filePath - The file path to check
207
- * @returns {boolean} if the file path is a relative path, false otherwise
208
- */
209
- isRelativePath(filePath) {
210
- return !import_node_path.default.isAbsolute(filePath);
211
- }
212
- /**
213
- * Delete the cache file from the disk
214
- * @method deleteCacheFile
215
- * @return {boolean} true if the file was deleted, false otherwise
216
- */
217
- deleteCacheFile() {
218
- return this._cache.removeCacheFile();
219
- }
220
- /**
221
- * Remove the cache from the file and clear the memory cache
222
- * @method destroy
223
- */
224
- destroy() {
225
- this._cache.destroy();
226
- }
227
- /**
228
- * Remove and Entry From the Cache
229
- * @method removeEntry
230
- * @param filePath - The file path to remove from the cache
231
- */
232
- removeEntry(filePath) {
233
- const key = this.createFileKey(filePath);
234
- this._cache.removeKey(key);
235
- }
236
- /**
237
- * Reconcile the cache
238
- * @method reconcile
239
- */
240
- reconcile() {
241
- const { items } = this._cache;
242
- for (const item of items) {
243
- const fileDescriptor = this.getFileDescriptor(item.key);
244
- if (fileDescriptor.notFound) {
245
- this._cache.removeKey(item.key);
246
- }
247
- }
248
- this._cache.save();
249
- }
250
- /**
251
- * Check if the file has changed
252
- * @method hasFileChanged
253
- * @param filePath - The file path to check
254
- * @returns {boolean} if the file has changed, false otherwise
255
- */
256
- hasFileChanged(filePath) {
257
- let result = false;
258
- const fileDescriptor = this.getFileDescriptor(filePath);
259
- if ((!fileDescriptor.err || !fileDescriptor.notFound) && fileDescriptor.changed) {
260
- result = true;
261
- }
262
- return result;
263
- }
264
- /**
265
- * Get the file descriptor for the file path
266
- * @method getFileDescriptor
267
- * @param filePath - The file path to get the file descriptor for
268
- * @param options - The options for getting the file descriptor
269
- * @returns The file descriptor
270
- */
271
- getFileDescriptor(filePath, options) {
272
- let fstat;
273
- const result = {
274
- key: this.createFileKey(filePath),
275
- changed: false,
276
- meta: {}
277
- };
278
- result.meta = this._cache.getKey(result.key) ?? {};
279
- const absolutePath = this.getAbsolutePath(filePath);
280
- const useCheckSumValue = options?.useCheckSum ?? this._useCheckSum;
281
- const useModifiedTimeValue = options?.useModifiedTime ?? this._useModifiedTime;
282
- try {
283
- fstat = import_node_fs.default.statSync(absolutePath);
284
- result.meta = {
285
- size: fstat.size
286
- };
287
- result.meta.mtime = fstat.mtime.getTime();
288
- if (useCheckSumValue) {
289
- const buffer = import_node_fs.default.readFileSync(absolutePath);
290
- result.meta.hash = this.getHash(buffer);
291
- }
292
- } catch (error) {
293
- this.removeEntry(filePath);
294
- let notFound = false;
295
- if (error.message.includes("ENOENT")) {
296
- notFound = true;
297
- }
298
- return {
299
- key: result.key,
300
- err: error,
301
- notFound,
302
- meta: {}
303
- };
304
- }
305
- const metaCache = this._cache.getKey(result.key);
306
- if (!metaCache) {
307
- result.changed = true;
308
- this._cache.setKey(result.key, result.meta);
309
- return result;
310
- }
311
- if (result.meta.data === void 0) {
312
- result.meta.data = metaCache.data;
313
- }
314
- if (useModifiedTimeValue && metaCache?.mtime !== result.meta?.mtime) {
315
- result.changed = true;
316
- }
317
- if (metaCache?.size !== result.meta?.size) {
318
- result.changed = true;
319
- }
320
- if (useCheckSumValue && metaCache?.hash !== result.meta?.hash) {
321
- result.changed = true;
322
- }
323
- this._cache.setKey(result.key, result.meta);
324
- return result;
325
- }
326
- /**
327
- * Get the file descriptors for the files
328
- * @method normalizeEntries
329
- * @param files?: string[] - The files to get the file descriptors for
330
- * @returns The file descriptors
331
- */
332
- normalizeEntries(files) {
333
- const result = [];
334
- if (files) {
335
- for (const file of files) {
336
- const fileDescriptor = this.getFileDescriptor(file);
337
- result.push(fileDescriptor);
338
- }
339
- return result;
340
- }
341
- const keys = this.cache.keys();
342
- for (const key of keys) {
343
- const fileDescriptor = this.getFileDescriptor(key);
344
- if (!fileDescriptor.notFound && !fileDescriptor.err) {
345
- result.push(fileDescriptor);
346
- }
347
- }
348
- return result;
349
- }
350
- /**
351
- * Analyze the files
352
- * @method analyzeFiles
353
- * @param files - The files to analyze
354
- * @returns {AnalyzedFiles} The analysis of the files
355
- */
356
- analyzeFiles(files) {
357
- const result = {
358
- changedFiles: [],
359
- notFoundFiles: [],
360
- notChangedFiles: []
361
- };
362
- const fileDescriptors = this.normalizeEntries(files);
363
- for (const fileDescriptor of fileDescriptors) {
364
- if (fileDescriptor.notFound) {
365
- result.notFoundFiles.push(fileDescriptor.key);
366
- } else if (fileDescriptor.changed) {
367
- result.changedFiles.push(fileDescriptor.key);
368
- } else {
369
- result.notChangedFiles.push(fileDescriptor.key);
370
- }
371
- }
372
- return result;
373
- }
374
- /**
375
- * Get the updated files
376
- * @method getUpdatedFiles
377
- * @param files - The files to get the updated files for
378
- * @returns {string[]} The updated files
379
- */
380
- getUpdatedFiles(files) {
381
- const result = [];
382
- const fileDescriptors = this.normalizeEntries(files);
383
- for (const fileDescriptor of fileDescriptors) {
384
- if (fileDescriptor.changed) {
385
- result.push(fileDescriptor.key);
386
- }
387
- }
388
- return result;
389
- }
390
- /**
391
- * Get the file descriptors by path prefix
392
- * @method getFileDescriptorsByPath
393
- * @param filePath - the path prefix to match
394
- * @returns {FileDescriptor[]} The file descriptors
395
- */
396
- getFileDescriptorsByPath(filePath) {
397
- const result = [];
398
- const keys = this._cache.keys();
399
- for (const key of keys) {
400
- if (key.startsWith(filePath)) {
401
- const fileDescriptor = this.getFileDescriptor(key);
402
- result.push(fileDescriptor);
403
- }
404
- }
405
- return result;
406
- }
407
- /**
408
- * Get the Absolute Path. If it is already absolute it will return the path as is.
409
- * When strictPaths is enabled, ensures the resolved path stays within cwd boundaries.
410
- * @method getAbsolutePath
411
- * @param filePath - The file path to get the absolute path for
412
- * @returns {string}
413
- * @throws {Error} When strictPaths is true and path would resolve outside cwd
414
- */
415
- getAbsolutePath(filePath) {
416
- if (this.isRelativePath(filePath)) {
417
- const sanitizedPath = filePath.replace(/\0/g, "");
418
- const resolved = import_node_path.default.resolve(this._cwd, sanitizedPath);
419
- if (this._strictPaths) {
420
- const normalizedResolved = import_node_path.default.normalize(resolved);
421
- const normalizedCwd = import_node_path.default.normalize(this._cwd);
422
- const isWithinCwd = normalizedResolved === normalizedCwd || normalizedResolved.startsWith(normalizedCwd + import_node_path.default.sep);
423
- if (!isWithinCwd) {
424
- throw new Error(
425
- `Path traversal attempt blocked: "${filePath}" resolves outside of working directory "${this._cwd}"`
426
- );
427
- }
428
- }
429
- return resolved;
430
- }
431
- return filePath;
432
- }
433
- /**
434
- * Get the Absolute Path with a custom working directory. If it is already absolute it will return the path as is.
435
- * When strictPaths is enabled, ensures the resolved path stays within the provided cwd boundaries.
436
- * @method getAbsolutePathWithCwd
437
- * @param filePath - The file path to get the absolute path for
438
- * @param cwd - The custom working directory to resolve relative paths from
439
- * @returns {string}
440
- * @throws {Error} When strictPaths is true and path would resolve outside the provided cwd
441
- */
442
- getAbsolutePathWithCwd(filePath, cwd) {
443
- if (this.isRelativePath(filePath)) {
444
- const sanitizedPath = filePath.replace(/\0/g, "");
445
- const resolved = import_node_path.default.resolve(cwd, sanitizedPath);
446
- if (this._strictPaths) {
447
- const normalizedResolved = import_node_path.default.normalize(resolved);
448
- const normalizedCwd = import_node_path.default.normalize(cwd);
449
- const isWithinCwd = normalizedResolved === normalizedCwd || normalizedResolved.startsWith(normalizedCwd + import_node_path.default.sep);
450
- if (!isWithinCwd) {
451
- throw new Error(
452
- `Path traversal attempt blocked: "${filePath}" resolves outside of working directory "${cwd}"`
453
- );
454
- }
455
- }
456
- return resolved;
457
- }
458
- return filePath;
459
- }
460
- /**
461
- * Rename cache keys that start with a given path prefix.
462
- * @method renameCacheKeys
463
- * @param oldPath - The old path prefix to rename
464
- * @param newPath - The new path prefix to rename to
465
- */
466
- renameCacheKeys(oldPath, newPath) {
467
- const keys = this._cache.keys();
468
- for (const key of keys) {
469
- if (key.startsWith(oldPath)) {
470
- const newKey = key.replace(oldPath, newPath);
471
- const meta = this._cache.getKey(key);
472
- this._cache.removeKey(key);
473
- this._cache.setKey(newKey, meta);
474
- }
475
- }
476
- }
477
- };
478
- // Annotate the CommonJS export names for ESM import in node:
479
- 0 && (module.exports = {
480
- FileEntryCache,
481
- create,
482
- createFromFile
483
- });
1
+ "use strict";var k=Object.create;var g=Object.defineProperty;var C=Object.getOwnPropertyDescriptor;var w=Object.getOwnPropertyNames;var v=Object.getPrototypeOf,z=Object.prototype.hasOwnProperty;var D=(o,e)=>{for(var s in e)g(o,s,{get:e[s],enumerable:!0})},b=(o,e,s,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of w(e))!z.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)):{},b(e||!o||!o.__esModule?g(s,"default",{value:o,enumerable:!0}):s,o)),S=o=>b(g({},"__esModule",{value:!0}),o);var A={};D(A,{FileEntryCache:()=>m,create:()=>f,createFromFile:()=>_,default:()=>d});module.exports=S(A);var y=p(require("crypto"),1),u=p(require("fs"),1),n=p(require("path"),1),l=require("flat-cache");function _(o,e,s){let i=n.default.basename(o),t=n.default.dirname(o);return f(i,t,e,s)}function f(o,e,s,i){let t={useCheckSum:s,cwd:i,cache:{cacheId:o,cacheDir:e}},r=new m(t);if(e){let c=`${e}/${o}`;u.default.existsSync(c)&&(r.cache=(0,l.createFromFile)(c,t.cache))}return r}var d=class{static create=f;static createFromFile=_},m=class{_cache=new l.FlatCache({useClone:!1});_useCheckSum=!1;_hashAlgorithm="md5";_cwd=process.cwd();_strictPaths=!0;_logger;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?.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}getHash(e){return y.default.createHash(this._hashAlgorithm).update(e).digest("hex")}createFileKey(e){return e}isRelativePath(e){return!n.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 c=this.getAbsolutePath(e);this._logger?.trace({absolutePath:c},"Resolved absolute path");let a=s?.useCheckSum??this._useCheckSum;this._logger?.debug({useCheckSum:a},"Using checksum setting");try{if(i=u.default.statSync(c),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"),a){let h=u.default.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 F=!1;return h.message.includes("ENOENT")&&(F=!0,this._logger?.debug({filePath:e},"File not found")),{key:t.key,err:h,notFound:F,meta:{}}}return r?(a===!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")),a&&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=n.default.resolve(this._cwd,s);if(this._strictPaths){let t=n.default.normalize(i),r=n.default.normalize(this._cwd);if(!(t===r||t.startsWith(r+n.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=n.default.resolve(s,i);if(this._strictPaths){let r=n.default.normalize(t),c=n.default.normalize(s);if(!(r===c||r.startsWith(c+n.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),c=this._cache.getKey(t);this._cache.removeKey(t),this._cache.setKey(r,c)}}};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,14 +28,14 @@ type FileEntryCacheOptions = {
12
28
  cwd?: string;
13
29
  /** Restrict file access to within cwd boundaries (default: true) */
14
30
  strictPaths?: boolean;
31
+ /** Logger instance for logging (default: undefined) */
32
+ logger?: ILogger;
15
33
  /** Options for the underlying flat cache */
16
34
  cache?: FlatCacheOptions;
17
35
  };
18
36
  type GetFileDescriptorOptions = {
19
- /** Whether to use checksum for this specific file check (overrides instance setting) */
37
+ /** Whether to use checksum for this specific file check instead of modified time (mtime) (overrides instance setting) */
20
38
  useCheckSum?: boolean;
21
- /** Whether to use modified time for this specific file check (overrides instance setting) */
22
- useModifiedTime?: boolean;
23
39
  };
24
40
  type FileDescriptor = {
25
41
  /** The cache key for this file (typically the file path) */
@@ -42,6 +58,8 @@ type FileDescriptorMeta = {
42
58
  hash?: string;
43
59
  /** Custom data associated with the file (e.g., lint results, metadata) */
44
60
  data?: unknown;
61
+ /** Allow any additional custom properties */
62
+ [key: string]: unknown;
45
63
  };
46
64
  type AnalyzedFiles = {
47
65
  /** Array of file paths that have changed since last cache */
@@ -75,10 +93,10 @@ declare class FileEntryDefault {
75
93
  declare class FileEntryCache {
76
94
  private _cache;
77
95
  private _useCheckSum;
78
- private _useModifiedTime;
79
96
  private _hashAlgorithm;
80
97
  private _cwd;
81
98
  private _strictPaths;
99
+ private _logger?;
82
100
  /**
83
101
  * Create a new FileEntryCache instance
84
102
  * @param options - The options for the FileEntryCache (all properties are optional with defaults)
@@ -94,6 +112,16 @@ declare class FileEntryCache {
94
112
  * @param {FlatCache} cache - The cache to set
95
113
  */
96
114
  set cache(cache: FlatCache);
115
+ /**
116
+ * Get the logger
117
+ * @returns {ILogger | undefined} The logger instance
118
+ */
119
+ get logger(): ILogger | undefined;
120
+ /**
121
+ * Set the logger
122
+ * @param {ILogger | undefined} logger - The logger to set
123
+ */
124
+ set logger(logger: ILogger | undefined);
97
125
  /**
98
126
  * Use the hash to check if the file has changed
99
127
  * @returns {boolean} if the hash is used to check if the file has changed (default: false)
@@ -104,16 +132,6 @@ declare class FileEntryCache {
104
132
  * @param {boolean} value - The value to set
105
133
  */
106
134
  set useCheckSum(value: boolean);
107
- /**
108
- * Use the modified time to check if the file has changed
109
- * @returns {boolean} if the modified time is used to check if the file has changed (default: true)
110
- */
111
- get useModifiedTime(): boolean;
112
- /**
113
- * Set the useModifiedTime value
114
- * @param {boolean} value - The value to set
115
- */
116
- set useModifiedTime(value: boolean);
117
135
  /**
118
136
  * Get the hash algorithm
119
137
  * @returns {string} The hash algorithm (default: 'md5')
@@ -258,4 +276,4 @@ declare class FileEntryCache {
258
276
  renameCacheKeys(oldPath: string, newPath: string): void;
259
277
  }
260
278
 
261
- export { type AnalyzedFiles, type FileDescriptor, type FileDescriptorMeta, FileEntryCache, type FileEntryCacheOptions, type GetFileDescriptorOptions, create, createFromFile, FileEntryDefault as default };
279
+ export { type AnalyzedFiles, 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,14 +28,14 @@ type FileEntryCacheOptions = {
12
28
  cwd?: string;
13
29
  /** Restrict file access to within cwd boundaries (default: true) */
14
30
  strictPaths?: boolean;
31
+ /** Logger instance for logging (default: undefined) */
32
+ logger?: ILogger;
15
33
  /** Options for the underlying flat cache */
16
34
  cache?: FlatCacheOptions;
17
35
  };
18
36
  type GetFileDescriptorOptions = {
19
- /** Whether to use checksum for this specific file check (overrides instance setting) */
37
+ /** Whether to use checksum for this specific file check instead of modified time (mtime) (overrides instance setting) */
20
38
  useCheckSum?: boolean;
21
- /** Whether to use modified time for this specific file check (overrides instance setting) */
22
- useModifiedTime?: boolean;
23
39
  };
24
40
  type FileDescriptor = {
25
41
  /** The cache key for this file (typically the file path) */
@@ -42,6 +58,8 @@ type FileDescriptorMeta = {
42
58
  hash?: string;
43
59
  /** Custom data associated with the file (e.g., lint results, metadata) */
44
60
  data?: unknown;
61
+ /** Allow any additional custom properties */
62
+ [key: string]: unknown;
45
63
  };
46
64
  type AnalyzedFiles = {
47
65
  /** Array of file paths that have changed since last cache */
@@ -75,10 +93,10 @@ declare class FileEntryDefault {
75
93
  declare class FileEntryCache {
76
94
  private _cache;
77
95
  private _useCheckSum;
78
- private _useModifiedTime;
79
96
  private _hashAlgorithm;
80
97
  private _cwd;
81
98
  private _strictPaths;
99
+ private _logger?;
82
100
  /**
83
101
  * Create a new FileEntryCache instance
84
102
  * @param options - The options for the FileEntryCache (all properties are optional with defaults)
@@ -94,6 +112,16 @@ declare class FileEntryCache {
94
112
  * @param {FlatCache} cache - The cache to set
95
113
  */
96
114
  set cache(cache: FlatCache);
115
+ /**
116
+ * Get the logger
117
+ * @returns {ILogger | undefined} The logger instance
118
+ */
119
+ get logger(): ILogger | undefined;
120
+ /**
121
+ * Set the logger
122
+ * @param {ILogger | undefined} logger - The logger to set
123
+ */
124
+ set logger(logger: ILogger | undefined);
97
125
  /**
98
126
  * Use the hash to check if the file has changed
99
127
  * @returns {boolean} if the hash is used to check if the file has changed (default: false)
@@ -104,16 +132,6 @@ declare class FileEntryCache {
104
132
  * @param {boolean} value - The value to set
105
133
  */
106
134
  set useCheckSum(value: boolean);
107
- /**
108
- * Use the modified time to check if the file has changed
109
- * @returns {boolean} if the modified time is used to check if the file has changed (default: true)
110
- */
111
- get useModifiedTime(): boolean;
112
- /**
113
- * Set the useModifiedTime value
114
- * @param {boolean} value - The value to set
115
- */
116
- set useModifiedTime(value: boolean);
117
135
  /**
118
136
  * Get the hash algorithm
119
137
  * @returns {string} The hash algorithm (default: 'md5')
@@ -258,4 +276,4 @@ declare class FileEntryCache {
258
276
  renameCacheKeys(oldPath: string, newPath: string): void;
259
277
  }
260
278
 
261
- export { type AnalyzedFiles, type FileDescriptor, type FileDescriptorMeta, FileEntryCache, type FileEntryCacheOptions, type GetFileDescriptorOptions, create, createFromFile, FileEntryDefault as default };
279
+ export { type AnalyzedFiles, type FileDescriptor, type FileDescriptorMeta, FileEntryCache, type FileEntryCacheOptions, type GetFileDescriptorOptions, type ILogger, create, createFromFile, FileEntryDefault as default };
package/dist/index.js CHANGED
@@ -1,449 +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
- _useModifiedTime = true;
40
- _hashAlgorithm = "md5";
41
- _cwd = process.cwd();
42
- _strictPaths = true;
43
- /**
44
- * Create a new FileEntryCache instance
45
- * @param options - The options for the FileEntryCache (all properties are optional with defaults)
46
- */
47
- constructor(options) {
48
- if (options?.cache) {
49
- this._cache = new FlatCache(options.cache);
50
- }
51
- if (options?.useModifiedTime) {
52
- this._useModifiedTime = options.useModifiedTime;
53
- }
54
- if (options?.useCheckSum) {
55
- this._useCheckSum = options.useCheckSum;
56
- }
57
- if (options?.hashAlgorithm) {
58
- this._hashAlgorithm = options.hashAlgorithm;
59
- }
60
- if (options?.cwd) {
61
- this._cwd = options.cwd;
62
- }
63
- if (options?.strictPaths !== void 0) {
64
- this._strictPaths = options.strictPaths;
65
- }
66
- }
67
- /**
68
- * Get the cache
69
- * @returns {FlatCache} The cache
70
- */
71
- get cache() {
72
- return this._cache;
73
- }
74
- /**
75
- * Set the cache
76
- * @param {FlatCache} cache - The cache to set
77
- */
78
- set cache(cache) {
79
- this._cache = cache;
80
- }
81
- /**
82
- * Use the hash to check if the file has changed
83
- * @returns {boolean} if the hash is used to check if the file has changed (default: false)
84
- */
85
- get useCheckSum() {
86
- return this._useCheckSum;
87
- }
88
- /**
89
- * Set the useCheckSum value
90
- * @param {boolean} value - The value to set
91
- */
92
- set useCheckSum(value) {
93
- this._useCheckSum = value;
94
- }
95
- /**
96
- * Use the modified time to check if the file has changed
97
- * @returns {boolean} if the modified time is used to check if the file has changed (default: true)
98
- */
99
- get useModifiedTime() {
100
- return this._useModifiedTime;
101
- }
102
- /**
103
- * Set the useModifiedTime value
104
- * @param {boolean} value - The value to set
105
- */
106
- set useModifiedTime(value) {
107
- this._useModifiedTime = value;
108
- }
109
- /**
110
- * Get the hash algorithm
111
- * @returns {string} The hash algorithm (default: 'md5')
112
- */
113
- get hashAlgorithm() {
114
- return this._hashAlgorithm;
115
- }
116
- /**
117
- * Set the hash algorithm
118
- * @param {string} value - The value to set
119
- */
120
- set hashAlgorithm(value) {
121
- this._hashAlgorithm = value;
122
- }
123
- /**
124
- * Get the current working directory
125
- * @returns {string} The current working directory (default: process.cwd())
126
- */
127
- get cwd() {
128
- return this._cwd;
129
- }
130
- /**
131
- * Set the current working directory
132
- * @param {string} value - The value to set
133
- */
134
- set cwd(value) {
135
- this._cwd = value;
136
- }
137
- /**
138
- * Get whether to restrict paths to cwd boundaries
139
- * @returns {boolean} Whether strict path checking is enabled (default: true)
140
- */
141
- get strictPaths() {
142
- return this._strictPaths;
143
- }
144
- /**
145
- * Set whether to restrict paths to cwd boundaries
146
- * @param {boolean} value - The value to set
147
- */
148
- set strictPaths(value) {
149
- this._strictPaths = value;
150
- }
151
- /**
152
- * Given a buffer, calculate md5 hash of its content.
153
- * @method getHash
154
- * @param {Buffer} buffer buffer to calculate hash on
155
- * @return {String} content hash digest
156
- */
157
- getHash(buffer) {
158
- return crypto.createHash(this._hashAlgorithm).update(buffer).digest("hex");
159
- }
160
- /**
161
- * Create the key for the file path used for caching.
162
- * @method createFileKey
163
- * @param {String} filePath
164
- * @return {String}
165
- */
166
- createFileKey(filePath) {
167
- return filePath;
168
- }
169
- /**
170
- * Check if the file path is a relative path
171
- * @method isRelativePath
172
- * @param filePath - The file path to check
173
- * @returns {boolean} if the file path is a relative path, false otherwise
174
- */
175
- isRelativePath(filePath) {
176
- return !path.isAbsolute(filePath);
177
- }
178
- /**
179
- * Delete the cache file from the disk
180
- * @method deleteCacheFile
181
- * @return {boolean} true if the file was deleted, false otherwise
182
- */
183
- deleteCacheFile() {
184
- return this._cache.removeCacheFile();
185
- }
186
- /**
187
- * Remove the cache from the file and clear the memory cache
188
- * @method destroy
189
- */
190
- destroy() {
191
- this._cache.destroy();
192
- }
193
- /**
194
- * Remove and Entry From the Cache
195
- * @method removeEntry
196
- * @param filePath - The file path to remove from the cache
197
- */
198
- removeEntry(filePath) {
199
- const key = this.createFileKey(filePath);
200
- this._cache.removeKey(key);
201
- }
202
- /**
203
- * Reconcile the cache
204
- * @method reconcile
205
- */
206
- reconcile() {
207
- const { items } = this._cache;
208
- for (const item of items) {
209
- const fileDescriptor = this.getFileDescriptor(item.key);
210
- if (fileDescriptor.notFound) {
211
- this._cache.removeKey(item.key);
212
- }
213
- }
214
- this._cache.save();
215
- }
216
- /**
217
- * Check if the file has changed
218
- * @method hasFileChanged
219
- * @param filePath - The file path to check
220
- * @returns {boolean} if the file has changed, false otherwise
221
- */
222
- hasFileChanged(filePath) {
223
- let result = false;
224
- const fileDescriptor = this.getFileDescriptor(filePath);
225
- if ((!fileDescriptor.err || !fileDescriptor.notFound) && fileDescriptor.changed) {
226
- result = true;
227
- }
228
- return result;
229
- }
230
- /**
231
- * Get the file descriptor for the file path
232
- * @method getFileDescriptor
233
- * @param filePath - The file path to get the file descriptor for
234
- * @param options - The options for getting the file descriptor
235
- * @returns The file descriptor
236
- */
237
- getFileDescriptor(filePath, options) {
238
- let fstat;
239
- const result = {
240
- key: this.createFileKey(filePath),
241
- changed: false,
242
- meta: {}
243
- };
244
- result.meta = this._cache.getKey(result.key) ?? {};
245
- const absolutePath = this.getAbsolutePath(filePath);
246
- const useCheckSumValue = options?.useCheckSum ?? this._useCheckSum;
247
- const useModifiedTimeValue = options?.useModifiedTime ?? this._useModifiedTime;
248
- try {
249
- fstat = fs.statSync(absolutePath);
250
- result.meta = {
251
- size: fstat.size
252
- };
253
- result.meta.mtime = fstat.mtime.getTime();
254
- if (useCheckSumValue) {
255
- const buffer = fs.readFileSync(absolutePath);
256
- result.meta.hash = this.getHash(buffer);
257
- }
258
- } catch (error) {
259
- this.removeEntry(filePath);
260
- let notFound = false;
261
- if (error.message.includes("ENOENT")) {
262
- notFound = true;
263
- }
264
- return {
265
- key: result.key,
266
- err: error,
267
- notFound,
268
- meta: {}
269
- };
270
- }
271
- const metaCache = this._cache.getKey(result.key);
272
- if (!metaCache) {
273
- result.changed = true;
274
- this._cache.setKey(result.key, result.meta);
275
- return result;
276
- }
277
- if (result.meta.data === void 0) {
278
- result.meta.data = metaCache.data;
279
- }
280
- if (useModifiedTimeValue && metaCache?.mtime !== result.meta?.mtime) {
281
- result.changed = true;
282
- }
283
- if (metaCache?.size !== result.meta?.size) {
284
- result.changed = true;
285
- }
286
- if (useCheckSumValue && metaCache?.hash !== result.meta?.hash) {
287
- result.changed = true;
288
- }
289
- this._cache.setKey(result.key, result.meta);
290
- return result;
291
- }
292
- /**
293
- * Get the file descriptors for the files
294
- * @method normalizeEntries
295
- * @param files?: string[] - The files to get the file descriptors for
296
- * @returns The file descriptors
297
- */
298
- normalizeEntries(files) {
299
- const result = [];
300
- if (files) {
301
- for (const file of files) {
302
- const fileDescriptor = this.getFileDescriptor(file);
303
- result.push(fileDescriptor);
304
- }
305
- return result;
306
- }
307
- const keys = this.cache.keys();
308
- for (const key of keys) {
309
- const fileDescriptor = this.getFileDescriptor(key);
310
- if (!fileDescriptor.notFound && !fileDescriptor.err) {
311
- result.push(fileDescriptor);
312
- }
313
- }
314
- return result;
315
- }
316
- /**
317
- * Analyze the files
318
- * @method analyzeFiles
319
- * @param files - The files to analyze
320
- * @returns {AnalyzedFiles} The analysis of the files
321
- */
322
- analyzeFiles(files) {
323
- const result = {
324
- changedFiles: [],
325
- notFoundFiles: [],
326
- notChangedFiles: []
327
- };
328
- const fileDescriptors = this.normalizeEntries(files);
329
- for (const fileDescriptor of fileDescriptors) {
330
- if (fileDescriptor.notFound) {
331
- result.notFoundFiles.push(fileDescriptor.key);
332
- } else if (fileDescriptor.changed) {
333
- result.changedFiles.push(fileDescriptor.key);
334
- } else {
335
- result.notChangedFiles.push(fileDescriptor.key);
336
- }
337
- }
338
- return result;
339
- }
340
- /**
341
- * Get the updated files
342
- * @method getUpdatedFiles
343
- * @param files - The files to get the updated files for
344
- * @returns {string[]} The updated files
345
- */
346
- getUpdatedFiles(files) {
347
- const result = [];
348
- const fileDescriptors = this.normalizeEntries(files);
349
- for (const fileDescriptor of fileDescriptors) {
350
- if (fileDescriptor.changed) {
351
- result.push(fileDescriptor.key);
352
- }
353
- }
354
- return result;
355
- }
356
- /**
357
- * Get the file descriptors by path prefix
358
- * @method getFileDescriptorsByPath
359
- * @param filePath - the path prefix to match
360
- * @returns {FileDescriptor[]} The file descriptors
361
- */
362
- getFileDescriptorsByPath(filePath) {
363
- const result = [];
364
- const keys = this._cache.keys();
365
- for (const key of keys) {
366
- if (key.startsWith(filePath)) {
367
- const fileDescriptor = this.getFileDescriptor(key);
368
- result.push(fileDescriptor);
369
- }
370
- }
371
- return result;
372
- }
373
- /**
374
- * Get the Absolute Path. If it is already absolute it will return the path as is.
375
- * When strictPaths is enabled, ensures the resolved path stays within cwd boundaries.
376
- * @method getAbsolutePath
377
- * @param filePath - The file path to get the absolute path for
378
- * @returns {string}
379
- * @throws {Error} When strictPaths is true and path would resolve outside cwd
380
- */
381
- getAbsolutePath(filePath) {
382
- if (this.isRelativePath(filePath)) {
383
- const sanitizedPath = filePath.replace(/\0/g, "");
384
- const resolved = path.resolve(this._cwd, sanitizedPath);
385
- if (this._strictPaths) {
386
- const normalizedResolved = path.normalize(resolved);
387
- const normalizedCwd = path.normalize(this._cwd);
388
- const isWithinCwd = normalizedResolved === normalizedCwd || normalizedResolved.startsWith(normalizedCwd + path.sep);
389
- if (!isWithinCwd) {
390
- throw new Error(
391
- `Path traversal attempt blocked: "${filePath}" resolves outside of working directory "${this._cwd}"`
392
- );
393
- }
394
- }
395
- return resolved;
396
- }
397
- return filePath;
398
- }
399
- /**
400
- * Get the Absolute Path with a custom working directory. If it is already absolute it will return the path as is.
401
- * When strictPaths is enabled, ensures the resolved path stays within the provided cwd boundaries.
402
- * @method getAbsolutePathWithCwd
403
- * @param filePath - The file path to get the absolute path for
404
- * @param cwd - The custom working directory to resolve relative paths from
405
- * @returns {string}
406
- * @throws {Error} When strictPaths is true and path would resolve outside the provided cwd
407
- */
408
- getAbsolutePathWithCwd(filePath, cwd) {
409
- if (this.isRelativePath(filePath)) {
410
- const sanitizedPath = filePath.replace(/\0/g, "");
411
- const resolved = path.resolve(cwd, sanitizedPath);
412
- if (this._strictPaths) {
413
- const normalizedResolved = path.normalize(resolved);
414
- const normalizedCwd = path.normalize(cwd);
415
- const isWithinCwd = normalizedResolved === normalizedCwd || normalizedResolved.startsWith(normalizedCwd + path.sep);
416
- if (!isWithinCwd) {
417
- throw new Error(
418
- `Path traversal attempt blocked: "${filePath}" resolves outside of working directory "${cwd}"`
419
- );
420
- }
421
- }
422
- return resolved;
423
- }
424
- return filePath;
425
- }
426
- /**
427
- * Rename cache keys that start with a given path prefix.
428
- * @method renameCacheKeys
429
- * @param oldPath - The old path prefix to rename
430
- * @param newPath - The new path prefix to rename to
431
- */
432
- renameCacheKeys(oldPath, newPath) {
433
- const keys = this._cache.keys();
434
- for (const key of keys) {
435
- if (key.startsWith(oldPath)) {
436
- const newKey = key.replace(oldPath, newPath);
437
- const meta = this._cache.getKey(key);
438
- this._cache.removeKey(key);
439
- this._cache.setKey(newKey, meta);
440
- }
441
- }
442
- }
443
- };
444
- export {
445
- FileEntryCache,
446
- create,
447
- createFromFile,
448
- FileEntryDefault as default
449
- };
1
+ import f from"crypto";import l from"fs";import c from"path";import{createFromFile as F,FlatCache as m}from"flat-cache";function b(n,e,s){let r=c.basename(n),t=c.dirname(n);return p(r,t,e,s)}function p(n,e,s,r){let t={useCheckSum:s,cwd:r,cache:{cacheId:n,cacheDir:e}},i=new u(t);if(e){let o=`${e}/${n}`;l.existsSync(o)&&(i.cache=F(o,t.cache))}return i}var g=class{static create=p;static createFromFile=b},u=class{_cache=new m({useClone:!1});_useCheckSum=!1;_hashAlgorithm="md5";_cwd=process.cwd();_strictPaths=!0;_logger;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?.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}getHash(e){return f.createHash(this._hashAlgorithm).update(e).digest("hex")}createFileKey(e){return e}isRelativePath(e){return!c.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 o=this.getAbsolutePath(e);this._logger?.trace({absolutePath:o},"Resolved absolute path");let a=s?.useCheckSum??this._useCheckSum;this._logger?.debug({useCheckSum:a},"Using checksum setting");try{if(r=l.statSync(o),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"),a){let h=l.readFileSync(o);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?(a===!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")),a&&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=c.resolve(this._cwd,s);if(this._strictPaths){let t=c.normalize(r),i=c.normalize(this._cwd);if(!(t===i||t.startsWith(i+c.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=c.resolve(s,r);if(this._strictPaths){let i=c.normalize(t),o=c.normalize(s);if(!(i===o||i.startsWith(o+c.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),o=this._cache.getKey(t);this._cache.removeKey(t),this._cache.setKey(i,o)}}};export{u as FileEntryCache,p as create,b 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.2",
3
+ "version": "11.0.0-beta.4",
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",
@@ -29,12 +29,13 @@
29
29
  "cache"
30
30
  ],
31
31
  "devDependencies": {
32
- "@biomejs/biome": "^2.2.4",
33
- "@types/node": "^24.5.0",
32
+ "@biomejs/biome": "^2.2.5",
33
+ "@types/node": "^24.7.0",
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
- "typescript": "^5.9.2",
38
+ "typescript": "^5.9.3",
38
39
  "vitest": "^3.2.4"
39
40
  },
40
41
  "dependencies": {
@@ -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",