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

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
@@ -72,6 +72,23 @@ fileDescriptor = cache.getFileDescriptor('./src/file.txt');
72
72
  console.log(fileDescriptor.changed); // true
73
73
  ```
74
74
 
75
+ Using `create()` with options for more control:
76
+ ```javascript
77
+ import fileEntryCache from 'file-entry-cache';
78
+
79
+ // Create cache with options
80
+ const cache = fileEntryCache.create('myCache', './.cache', {
81
+ useCheckSum: true, // Use checksums for more reliable change detection
82
+ cwd: '/path/to/project', // Custom working directory
83
+ restrictAccessToCwd: false, // Allow access outside cwd (use with caution)
84
+ useAbsolutePathAsKey: false // Store relative paths in cache
85
+ });
86
+
87
+ let fileDescriptor = cache.getFileDescriptor('./src/file.txt');
88
+ console.log(fileDescriptor.changed); // true
89
+ console.log(fileDescriptor.meta.hash); // checksum hash
90
+ ```
91
+
75
92
  Save it to Disk and Reconcile files that are no longer found
76
93
  ```javascript
77
94
  import fileEntryCache from 'file-entry-cache';
@@ -85,20 +102,80 @@ Load the cache from a file:
85
102
 
86
103
  ```javascript
87
104
  import fileEntryCache from 'file-entry-cache';
105
+
106
+ // Basic usage
88
107
  const cache = fileEntryCache.createFromFile('/path/to/cache/file');
89
108
  let fileDescriptor = cache.getFileDescriptor('./src/file.txt');
90
109
  console.log(fileDescriptor.changed); // false as it has not changed from the saved cache.
110
+
111
+ // With options
112
+ const cache2 = fileEntryCache.createFromFile('/path/to/cache/file', {
113
+ useCheckSum: true,
114
+ cwd: '/path/to/project'
115
+ });
91
116
  ```
92
117
 
93
118
 
94
119
  # Changes from v10 to v11
95
120
 
96
121
  **BREAKING CHANGES:**
97
- - **`strictPaths` now defaults to `true`** - Path traversal protection is enabled by default for security. To restore v10 behavior, explicitly set `strictPaths: false`
122
+
123
+ - **`create()` and `createFromFile()` now use `CreateOptions` object** - The function signatures have changed to accept an options object instead of individual parameters for better extensibility and clarity.
124
+
125
+ **Old API (v10):**
126
+ ```javascript
127
+ // v10 - positional parameters
128
+ const cache = fileEntryCache.create(cacheId, cacheDirectory, useCheckSum, cwd);
129
+ const cache2 = fileEntryCache.createFromFile(filePath, useCheckSum, cwd);
130
+ ```
131
+
132
+ **New API (v11):**
133
+ ```javascript
134
+ // v11 - options object
135
+ const cache = fileEntryCache.create(cacheId, cacheDirectory, {
136
+ useCheckSum: true,
137
+ cwd: '/path/to/project',
138
+ restrictAccessToCwd: false
139
+ });
140
+
141
+ const cache2 = fileEntryCache.createFromFile(filePath, {
142
+ useCheckSum: true,
143
+ cwd: '/path/to/project',
144
+ restrictAccessToCwd: false
145
+ });
146
+ ```
147
+
148
+ - **Renamed `strictPaths` to `restrictAccessToCwd`** - For better clarity and self-documentation, the option that restricts file access to the current working directory has been renamed.
149
+
150
+ **Migration:**
151
+ ```javascript
152
+ // Old
153
+ const cache = new FileEntryCache({ strictPaths: true });
154
+ cache.strictPaths = false;
155
+
156
+ // New
157
+ const cache = new FileEntryCache({ restrictAccessToCwd: true });
158
+ cache.restrictAccessToCwd = false;
159
+ ```
160
+
161
+ - **Renamed `currentWorkingDirectory` to `cwd`** - For consistency with common conventions and brevity, the property has been renamed to `cwd`.
162
+
163
+ **Migration:**
164
+ ```javascript
165
+ // Old
166
+ const cache = new FileEntryCache({ currentWorkingDirectory: '/path/to/project' });
167
+ cache.currentWorkingDirectory = '/new/path';
168
+
169
+ // New
170
+ const cache = new FileEntryCache({ cwd: '/path/to/project' });
171
+ cache.cwd = '/new/path';
172
+ ```
98
173
 
99
174
  **NEW FEATURES:**
100
175
  - **Added `cwd` option** - You can now specify a custom current working directory for resolving relative paths
101
- - **Added `strictPaths` option** - Provides protection against path traversal attacks (enabled by default)
176
+ - **Added `restrictAccessToCwd` option** - Provides protection against path traversal attacks (disabled by default for backwards compatibility)
177
+ - **Added `useAbsolutePathAsKey` option** - When `true`, cache keys use absolute paths instead of the provided paths. Default is `false` for better cache portability with relative paths
178
+ - **Added `logger` option** - Support for Pino-compatible logger instances to enable debugging and monitoring of cache operations. See [Logger Support](#logger-support) section for details
102
179
  - **Improved cache portability** - When using relative paths with the same `cwd`, cache files are portable across different environments
103
180
 
104
181
  # Changes from v9 to v10
@@ -114,15 +191,24 @@ There have been many features added and changes made to the `file-entry-cache` c
114
191
  - On `FileEntryDescriptor.meta` if using typescript you need to use the `meta.data` to set additional information. This is to allow for better type checking and to avoid conflicts with the `meta` object which was `any`.
115
192
 
116
193
  # Global Default Functions
117
- - `create(cacheId: string, cacheDirectory?: string, useCheckSum?: boolean, cwd?: string)` - Creates a new instance of the `FileEntryCache` class
118
- - `createFromFile(cachePath: string, useCheckSum?: boolean, cwd?: string)` - Creates a new instance of the `FileEntryCache` class and loads the cache from a file.
194
+ - `create(cacheId: string, cacheDirectory?: string, options?: CreateOptions)` - Creates a new instance of the `FileEntryCache` class
195
+ - `createFromFile(filePath: string, options?: CreateOptions)` - Creates a new instance of the `FileEntryCache` class and loads the cache from a file.
196
+
197
+ ## CreateOptions Type
198
+ All options from `FileEntryCacheOptions` except `cache`:
199
+ - `useCheckSum?` - If `true` it will use a checksum to determine if the file has changed. Default is `false`
200
+ - `hashAlgorithm?` - The algorithm to use for the checksum. Default is `md5`
201
+ - `cwd?` - The current working directory for resolving relative paths. Default is `process.cwd()`
202
+ - `restrictAccessToCwd?` - If `true` restricts file access to within `cwd` boundaries. Default is `false`
203
+ - `useAbsolutePathAsKey?` - If `true` uses absolute paths as cache keys. Default is `false`
204
+ - `logger?` - A logger instance for debugging. Default is `undefined`
119
205
 
120
206
  # FileEntryCache Options (FileEntryCacheOptions)
121
207
  - `useModifiedTime?` - If `true` it will use the modified time to determine if the file has changed. Default is `true`
122
208
  - `useCheckSum?` - If `true` it will use a checksum to determine if the file has changed. Default is `false`
123
209
  - `hashAlgorithm?` - The algorithm to use for the checksum. Default is `md5` but can be any algorithm supported by `crypto.createHash`
124
210
  - `cwd?` - The current working directory for resolving relative paths. Default is `process.cwd()`
125
- - `strictPaths?` - If `true` restricts file access to within `cwd` boundaries, preventing path traversal attacks. Default is `true`
211
+ - `restrictAccessToCwd?` - If `true` restricts file access to within `cwd` boundaries, preventing path traversal attacks. Default is `true`
126
212
  - `logger?` - A logger instance compatible with Pino logger interface for debugging and monitoring. Default is `undefined`
127
213
  - `cache.ttl?` - The time to live for the cache in milliseconds. Default is `0` which means no expiration
128
214
  - `cache.lruSize?` - The number of items to keep in the cache. Default is `0` which means no limit
@@ -141,9 +227,10 @@ There have been many features added and changes made to the `file-entry-cache` c
141
227
  - `hashAlgorithm: string` - The algorithm to use for the checksum. Default is `md5` but can be any algorithm supported by `crypto.createHash`
142
228
  - `getHash(buffer: Buffer): string` - Gets the hash of a buffer used for checksums
143
229
  - `cwd: string` - The current working directory for resolving relative paths. Default is `process.cwd()`
144
- - `strictPaths: boolean` - If `true` restricts file access to within `cwd` boundaries. Default is `true`
230
+ - `restrictAccessToCwd: boolean` - If `true` restricts file access to within `cwd` boundaries. Default is `true`
231
+ - `useAbsolutePathAsKey: boolean` - If `true` uses absolute paths as cache keys. Default is `false` to maintain better cache portability
145
232
  - `logger: ILogger | undefined` - A logger instance for debugging and monitoring cache operations
146
- - `createFileKey(filePath: string): string` - Returns the cache key for the file path (returns the path exactly as provided).
233
+ - `createFileKey(filePath: string): string` - Returns the cache key for the file path (returns the path exactly as provided when `useAbsolutePathAsKey` is `false`, otherwise returns the absolute path).
147
234
  - `deleteCacheFile(): boolean` - Deletes the cache file from disk
148
235
  - `destroy(): void` - Destroys the cache. This will clear the cache in memory. If using cache persistence it will stop the interval.
149
236
  - `removeEntry(filePath: string): void` - Removes an entry from the cache.
@@ -154,8 +241,8 @@ There have been many features added and changes made to the `file-entry-cache` c
154
241
  - `analyzeFiles(files: string[])` will return `AnalyzedFiles` object with `changedFiles`, `notFoundFiles`, and `notChangedFiles` as FileDescriptor arrays.
155
242
  - `getUpdatedFiles(files: string[])` will return an array of `FileEntryDescriptor` objects that have changed.
156
243
  - `getFileDescriptorsByPath(filePath: string): FileEntryDescriptor[]` will return an array of `FileEntryDescriptor` objects that starts with the path prefix specified.
157
- - `getAbsolutePath(filePath: string): string` - Resolves a relative path to absolute using the configured `cwd`. Returns absolute paths unchanged. When `strictPaths` is enabled, throws an error if the path resolves outside `cwd`.
158
- - `getAbsolutePathWithCwd(filePath: string, cwd: string): string` - Resolves a relative path to absolute using a custom working directory. When `strictPaths` is enabled, throws an error if the path resolves outside the provided `cwd`.
244
+ - `getAbsolutePath(filePath: string): string` - Resolves a relative path to absolute using the configured `cwd`. Returns absolute paths unchanged. When `restrictAccessToCwd` is enabled, throws an error if the path resolves outside `cwd`.
245
+ - `getAbsolutePathWithCwd(filePath: string, cwd: string): string` - Resolves a relative path to absolute using a custom working directory. When `restrictAccessToCwd` is enabled, throws an error if the path resolves outside the provided `cwd`.
159
246
 
160
247
  # Get File Descriptor
161
248
 
@@ -175,9 +262,12 @@ The cache stores paths exactly as they are provided (relative or absolute). When
175
262
  // Default: uses process.cwd()
176
263
  const cache1 = fileEntryCache.create('cache1');
177
264
 
178
- // Custom working directory
179
- const cache2 = fileEntryCache.create('cache2', './cache', false, '/project/root');
180
- // Or with options object
265
+ // Custom working directory using options object
266
+ const cache2 = fileEntryCache.create('cache2', './cache', {
267
+ cwd: '/project/root'
268
+ });
269
+
270
+ // Or using the class constructor directly
181
271
  const cache3 = new FileEntryCache({ cwd: '/project/root' });
182
272
 
183
273
  // The cache key is always the provided path
@@ -192,12 +282,16 @@ Using relative paths with a consistent `cwd` (defaults to `process.cwd()`) makes
192
282
 
193
283
  ```javascript
194
284
  // On machine A (project at /home/user/project)
195
- const cacheA = fileEntryCache.create('build-cache', './cache', false, '/home/user/project');
285
+ const cacheA = fileEntryCache.create('build-cache', './cache', {
286
+ cwd: '/home/user/project'
287
+ });
196
288
  cacheA.getFileDescriptor('./src/index.js'); // Resolves to /home/user/project/src/index.js
197
289
  cacheA.reconcile();
198
290
 
199
291
  // On machine B (project at /workspace/project)
200
- const cacheB = fileEntryCache.create('build-cache', './cache', false, '/workspace/project');
292
+ const cacheB = fileEntryCache.create('build-cache', './cache', {
293
+ cwd: '/workspace/project'
294
+ });
201
295
  cacheB.getFileDescriptor('./src/index.js'); // Resolves to /workspace/project/src/index.js
202
296
  // Cache hit! File hasn't changed since machine A
203
297
  ```
@@ -208,11 +302,9 @@ For maximum cache portability across different environments, use checksums (`use
208
302
 
209
303
  ```javascript
210
304
  // Development machine
211
- const devCache = fileEntryCache.create(
212
- '.buildcache',
213
- './cache', // cache directory
214
- true // Use checksums for content-based comparison
215
- );
305
+ const devCache = fileEntryCache.create('.buildcache', './cache', {
306
+ useCheckSum: true // Use checksums for content-based comparison
307
+ });
216
308
 
217
309
  // Process files using relative paths
218
310
  const descriptor = devCache.getFileDescriptor('./src/index.js');
@@ -223,12 +315,10 @@ if (descriptor.changed) {
223
315
  devCache.reconcile(); // Save cache
224
316
 
225
317
  // CI/CD Pipeline or another developer's machine
226
- const ciCache = fileEntryCache.create(
227
- '.buildcache',
228
- './node_modules/.cache',
229
- true, // Same checksum setting
230
- process.cwd() // Different absolute path, same relative structure
231
- );
318
+ const ciCache = fileEntryCache.create('.buildcache', './node_modules/.cache', {
319
+ useCheckSum: true, // Same checksum setting
320
+ cwd: process.cwd() // Different absolute path, same relative structure
321
+ });
232
322
 
233
323
  // Same relative path works across environments
234
324
  const descriptor2 = ciCache.getFileDescriptor('./src/index.js');
@@ -244,12 +334,18 @@ Cache remains valid even when projects are moved or renamed:
244
334
 
245
335
  ```javascript
246
336
  // Original location: /projects/my-app
247
- const cache1 = fileEntryCache.create('.cache', './cache', true, '/projects/my-app');
337
+ const cache1 = fileEntryCache.create('.cache', './cache', {
338
+ useCheckSum: true,
339
+ cwd: '/projects/my-app'
340
+ });
248
341
  cache1.getFileDescriptor('./src/app.js');
249
342
  cache1.reconcile();
250
343
 
251
344
  // After moving project to: /archived/2024/my-app
252
- const cache2 = fileEntryCache.create('.cache', './cache', true, '/archived/2024/my-app');
345
+ const cache2 = fileEntryCache.create('.cache', './cache', {
346
+ useCheckSum: true,
347
+ cwd: '/archived/2024/my-app'
348
+ });
253
349
  cache2.getFileDescriptor('./src/app.js'); // Still finds cached entry!
254
350
  // Cache valid as long as relative structure unchanged
255
351
  ```
@@ -270,12 +366,12 @@ if (fileDescriptor.notFound) {
270
366
 
271
367
  # Path Security and Traversal Prevention
272
368
 
273
- The `strictPaths` option provides security against path traversal attacks by restricting file access to within the configured `cwd` boundaries. **This is enabled by default (since v11)** to ensure secure defaults when processing untrusted input or when running in security-sensitive environments.
369
+ The `restrictAccessToCwd` option provides security against path traversal attacks by restricting file access to within the configured `cwd` boundaries. **This is enabled by default (since v11)** to ensure secure defaults when processing untrusted input or when running in security-sensitive environments.
274
370
 
275
371
  ## Basic Usage
276
372
 
277
373
  ```javascript
278
- // strictPaths is enabled by default for security
374
+ // restrictAccessToCwd is enabled by default for security
279
375
  const cache = new FileEntryCache({
280
376
  cwd: '/project/root'
281
377
  });
@@ -293,13 +389,13 @@ try {
293
389
  // To allow parent directory access (not recommended for untrusted input)
294
390
  const unsafeCache = new FileEntryCache({
295
391
  cwd: '/project/root',
296
- strictPaths: false // Explicitly disable protection
392
+ restrictAccessToCwd: false // Explicitly disable protection
297
393
  });
298
394
  ```
299
395
 
300
396
  ## Security Features
301
397
 
302
- When `strictPaths` is enabled:
398
+ When `restrictAccessToCwd` is enabled:
303
399
  - **Path Traversal Prevention**: Blocks attempts to access files outside the working directory using `../` sequences
304
400
  - **Null Byte Protection**: Automatically removes null bytes from paths to prevent injection attacks
305
401
  - **Path Normalization**: Cleans and normalizes paths to prevent bypass attempts
@@ -309,15 +405,11 @@ When `strictPaths` is enabled:
309
405
  ### Build Tools with Untrusted Input
310
406
  ```javascript
311
407
  // Secure build tool configuration
312
- const cache = fileEntryCache.create(
313
- '.buildcache',
314
- './cache',
315
- true, // useCheckSum
316
- process.cwd()
317
- );
318
-
319
- // Enable strict path checking for security
320
- cache.strictPaths = true;
408
+ const cache = fileEntryCache.create('.buildcache', './cache', {
409
+ useCheckSum: true,
410
+ cwd: process.cwd(),
411
+ restrictAccessToCwd: true // Enable strict path checking for security
412
+ });
321
413
 
322
414
  // Process user-provided file paths safely
323
415
  function processUserFile(userProvidedPath) {
@@ -340,7 +432,7 @@ function processUserFile(userProvidedPath) {
340
432
  // Strict security for CI/CD pipelines
341
433
  const cache = new FileEntryCache({
342
434
  cwd: process.env.GITHUB_WORKSPACE || process.cwd(),
343
- strictPaths: true, // Prevent access outside workspace
435
+ restrictAccessToCwd: true, // Prevent access outside workspace
344
436
  useCheckSum: true // Content-based validation
345
437
  });
346
438
 
@@ -355,20 +447,20 @@ cache.getFileDescriptor('../../../root'); // ✗ Blocked (path traversal)
355
447
  const cache = new FileEntryCache({ cwd: '/safe/directory' });
356
448
 
357
449
  // Start with relaxed mode for trusted operations
358
- cache.strictPaths = false;
450
+ cache.restrictAccessToCwd = false;
359
451
  processInternalFiles();
360
452
 
361
453
  // Enable strict mode for untrusted input
362
- cache.strictPaths = true;
454
+ cache.restrictAccessToCwd = true;
363
455
  processUserUploadedPaths();
364
456
 
365
457
  // Return to relaxed mode if needed
366
- cache.strictPaths = false;
458
+ cache.restrictAccessToCwd = false;
367
459
  ```
368
460
 
369
461
  ## Default Behavior
370
462
 
371
- **As of v11, `strictPaths` is enabled by default** to provide secure defaults. This means:
463
+ **As of v11, `restrictAccessToCwd` is enabled by default** to provide secure defaults. This means:
372
464
  - Path traversal attempts using `../` are blocked
373
465
  - File access is restricted to within the configured `cwd`
374
466
  - Null bytes in paths are automatically sanitized
@@ -380,11 +472,11 @@ If you're upgrading from v10 or earlier and need to maintain the previous behavi
380
472
  ```javascript
381
473
  const cache = new FileEntryCache({
382
474
  cwd: process.cwd(),
383
- strictPaths: false // Restore v10 behavior
475
+ restrictAccessToCwd: false // Restore v10 behavior
384
476
  });
385
477
  ```
386
478
 
387
- However, we strongly recommend keeping `strictPaths: true` and adjusting your code to work within the security boundaries, especially when processing any untrusted input.
479
+ However, we strongly recommend keeping `restrictAccessToCwd: true` and adjusting your code to work within the security boundaries, especially when processing any untrusted input.
388
480
 
389
481
  # Using Checksums to Determine if a File has Changed (useCheckSum)
390
482
 
package/dist/index.cjs CHANGED
@@ -1 +1 @@
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});
1
+ "use strict";var k=Object.create;var u=Object.defineProperty;var w=Object.getOwnPropertyDescriptor;var A=Object.getOwnPropertyNames;var v=Object.getPrototypeOf,z=Object.prototype.hasOwnProperty;var D=(o,e)=>{for(var s in e)u(o,s,{get:e[s],enumerable:!0})},F=(o,e,s,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of A(e))!z.call(o,t)&&t!==s&&u(o,t,{get:()=>e[t],enumerable:!(i=w(e,t))||i.enumerable});return o};var p=(o,e,s)=>(s=o!=null?k(v(o)):{},F(e||!o||!o.__esModule?u(s,"default",{value:o,enumerable:!0}):s,o)),T=o=>F(u({},"__esModule",{value:!0}),o);var K={};D(K,{FileEntryCache:()=>m,create:()=>f,createFromFile:()=>C,default:()=>d});module.exports=T(K);var _=p(require("crypto"),1),g=p(require("fs"),1),c=p(require("path"),1),h=require("flat-cache");function C(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}`;g.default.existsSync(r)&&(t.cache=(0,h.createFromFile)(r,i.cache))}return t}var d=class{static create=f;static createFromFile=C},m=class{_cache=new h.FlatCache({useClone:!1});_useCheckSum=!1;_hashAlgorithm="md5";_cwd=process.cwd();_restrictAccessToCwd=!1;_logger;_useAbsolutePathAsKey=!1;_useModifiedTime=!0;constructor(e){e?.cache&&(this._cache=new h.FlatCache(e.cache)),e?.useCheckSum&&(this._useCheckSum=e.useCheckSum),e?.hashAlgorithm&&(this._hashAlgorithm=e.hashAlgorithm),e?.cwd&&(this._cwd=e.cwd),e?.useModifiedTime!==void 0&&(this._useModifiedTime=e.useModifiedTime),e?.restrictAccessToCwd!==void 0&&(this._restrictAccessToCwd=e.restrictAccessToCwd),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 useModifiedTime(){return this._useModifiedTime}set useModifiedTime(e){this._useModifiedTime=e}get restrictAccessToCwd(){return this._restrictAccessToCwd}set restrictAccessToCwd(e){this._restrictAccessToCwd=e}get useAbsolutePathAsKey(){return this._useAbsolutePathAsKey}set useAbsolutePathAsKey(e){this._useAbsolutePathAsKey=e}getHash(e){return _.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 n=this.getAbsolutePath(e);this._logger?.trace({absolutePath:n},"Resolved absolute path");let l=s?.useCheckSum??this._useCheckSum;this._logger?.debug({useCheckSum:l},"Using checksum setting");let b=s?.useModifiedTime??this.useModifiedTime;this._logger?.debug({useModifiedTime:b},"Using modified time (mtime) setting");try{if(i=g.default.statSync(n),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"),l){let a=g.default.readFileSync(n);t.meta.hash=this.getHash(a),this._logger?.trace({hash:t.meta.hash},"Calculated file hash")}}catch(a){this._logger?.error({filePath:e,error:a},"Error reading file"),this.removeEntry(e);let y=!1;return a.message.includes("ENOENT")&&(y=!0,this._logger?.debug({filePath:e},"File not found")),{key:t.key,err:a,notFound:y,meta:{}}}return r?(b&&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")),l&&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._restrictAccessToCwd){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._restrictAccessToCwd){let r=c.default.normalize(t),n=c.default.normalize(s);if(!(r===n||r.startsWith(n+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),n=this._cache.getKey(t);this._cache.removeKey(t),this._cache.setKey(r,n)}}};0&&(module.exports={FileEntryCache,create,createFromFile});
package/dist/index.d.cts CHANGED
@@ -27,7 +27,7 @@ type FileEntryCacheOptions = {
27
27
  /** Current working directory for resolving relative paths (default: process.cwd()) */
28
28
  cwd?: string;
29
29
  /** Restrict file access to within cwd boundaries (default: true) */
30
- strictPaths?: boolean;
30
+ restrictAccessToCwd?: boolean;
31
31
  /** Whether to use absolute path as cache key (default: false) */
32
32
  useAbsolutePathAsKey?: boolean;
33
33
  /** Logger instance for logging (default: undefined) */
@@ -38,6 +38,8 @@ type FileEntryCacheOptions = {
38
38
  type GetFileDescriptorOptions = {
39
39
  /** Whether to use checksum for this specific file check instead of modified time (mtime) (overrides instance setting) */
40
40
  useCheckSum?: boolean;
41
+ /** Whether to use file modified time for change detection (default: true) */
42
+ useModifiedTime?: boolean;
41
43
  };
42
44
  type FileDescriptor = {
43
45
  /** The cache key for this file (typically the file path) */
@@ -96,9 +98,10 @@ declare class FileEntryCache {
96
98
  private _useCheckSum;
97
99
  private _hashAlgorithm;
98
100
  private _cwd;
99
- private _strictPaths;
101
+ private _restrictAccessToCwd;
100
102
  private _logger?;
101
103
  private _useAbsolutePathAsKey;
104
+ private _useModifiedTime;
102
105
  /**
103
106
  * Create a new FileEntryCache instance
104
107
  * @param options - The options for the FileEntryCache (all properties are optional with defaults)
@@ -154,16 +157,26 @@ declare class FileEntryCache {
154
157
  * @param {string} value - The value to set
155
158
  */
156
159
  set cwd(value: string);
160
+ /**
161
+ * Get whether to use modified time for change detection
162
+ * @returns {boolean} Whether modified time (mtime) is used for change detection (default: true)
163
+ */
164
+ get useModifiedTime(): boolean;
165
+ /**
166
+ * Set whether to use modified time for change detection
167
+ * @param {boolean} value - The value to set
168
+ */
169
+ set useModifiedTime(value: boolean);
157
170
  /**
158
171
  * Get whether to restrict paths to cwd boundaries
159
172
  * @returns {boolean} Whether strict path checking is enabled (default: true)
160
173
  */
161
- get strictPaths(): boolean;
174
+ get restrictAccessToCwd(): boolean;
162
175
  /**
163
176
  * Set whether to restrict paths to cwd boundaries
164
177
  * @param {boolean} value - The value to set
165
178
  */
166
- set strictPaths(value: boolean);
179
+ set restrictAccessToCwd(value: boolean);
167
180
  /**
168
181
  * Get whether to use absolute path as cache key
169
182
  * @returns {boolean} Whether cache keys use absolute paths (default: false)
@@ -262,21 +275,21 @@ declare class FileEntryCache {
262
275
  getFileDescriptorsByPath(filePath: string): FileDescriptor[];
263
276
  /**
264
277
  * Get the Absolute Path. If it is already absolute it will return the path as is.
265
- * When strictPaths is enabled, ensures the resolved path stays within cwd boundaries.
278
+ * When restrictAccessToCwd is enabled, ensures the resolved path stays within cwd boundaries.
266
279
  * @method getAbsolutePath
267
280
  * @param filePath - The file path to get the absolute path for
268
281
  * @returns {string}
269
- * @throws {Error} When strictPaths is true and path would resolve outside cwd
282
+ * @throws {Error} When restrictAccessToCwd is true and path would resolve outside cwd
270
283
  */
271
284
  getAbsolutePath(filePath: string): string;
272
285
  /**
273
286
  * Get the Absolute Path with a custom working directory. If it is already absolute it will return the path as is.
274
- * When strictPaths is enabled, ensures the resolved path stays within the provided cwd boundaries.
287
+ * When restrictAccessToCwd is enabled, ensures the resolved path stays within the provided cwd boundaries.
275
288
  * @method getAbsolutePathWithCwd
276
289
  * @param filePath - The file path to get the absolute path for
277
290
  * @param cwd - The custom working directory to resolve relative paths from
278
291
  * @returns {string}
279
- * @throws {Error} When strictPaths is true and path would resolve outside the provided cwd
292
+ * @throws {Error} When restrictAccessToCwd is true and path would resolve outside the provided cwd
280
293
  */
281
294
  getAbsolutePathWithCwd(filePath: string, cwd: string): string;
282
295
  /**
package/dist/index.d.ts CHANGED
@@ -27,7 +27,7 @@ type FileEntryCacheOptions = {
27
27
  /** Current working directory for resolving relative paths (default: process.cwd()) */
28
28
  cwd?: string;
29
29
  /** Restrict file access to within cwd boundaries (default: true) */
30
- strictPaths?: boolean;
30
+ restrictAccessToCwd?: boolean;
31
31
  /** Whether to use absolute path as cache key (default: false) */
32
32
  useAbsolutePathAsKey?: boolean;
33
33
  /** Logger instance for logging (default: undefined) */
@@ -38,6 +38,8 @@ type FileEntryCacheOptions = {
38
38
  type GetFileDescriptorOptions = {
39
39
  /** Whether to use checksum for this specific file check instead of modified time (mtime) (overrides instance setting) */
40
40
  useCheckSum?: boolean;
41
+ /** Whether to use file modified time for change detection (default: true) */
42
+ useModifiedTime?: boolean;
41
43
  };
42
44
  type FileDescriptor = {
43
45
  /** The cache key for this file (typically the file path) */
@@ -96,9 +98,10 @@ declare class FileEntryCache {
96
98
  private _useCheckSum;
97
99
  private _hashAlgorithm;
98
100
  private _cwd;
99
- private _strictPaths;
101
+ private _restrictAccessToCwd;
100
102
  private _logger?;
101
103
  private _useAbsolutePathAsKey;
104
+ private _useModifiedTime;
102
105
  /**
103
106
  * Create a new FileEntryCache instance
104
107
  * @param options - The options for the FileEntryCache (all properties are optional with defaults)
@@ -154,16 +157,26 @@ declare class FileEntryCache {
154
157
  * @param {string} value - The value to set
155
158
  */
156
159
  set cwd(value: string);
160
+ /**
161
+ * Get whether to use modified time for change detection
162
+ * @returns {boolean} Whether modified time (mtime) is used for change detection (default: true)
163
+ */
164
+ get useModifiedTime(): boolean;
165
+ /**
166
+ * Set whether to use modified time for change detection
167
+ * @param {boolean} value - The value to set
168
+ */
169
+ set useModifiedTime(value: boolean);
157
170
  /**
158
171
  * Get whether to restrict paths to cwd boundaries
159
172
  * @returns {boolean} Whether strict path checking is enabled (default: true)
160
173
  */
161
- get strictPaths(): boolean;
174
+ get restrictAccessToCwd(): boolean;
162
175
  /**
163
176
  * Set whether to restrict paths to cwd boundaries
164
177
  * @param {boolean} value - The value to set
165
178
  */
166
- set strictPaths(value: boolean);
179
+ set restrictAccessToCwd(value: boolean);
167
180
  /**
168
181
  * Get whether to use absolute path as cache key
169
182
  * @returns {boolean} Whether cache keys use absolute paths (default: false)
@@ -262,21 +275,21 @@ declare class FileEntryCache {
262
275
  getFileDescriptorsByPath(filePath: string): FileDescriptor[];
263
276
  /**
264
277
  * Get the Absolute Path. If it is already absolute it will return the path as is.
265
- * When strictPaths is enabled, ensures the resolved path stays within cwd boundaries.
278
+ * When restrictAccessToCwd is enabled, ensures the resolved path stays within cwd boundaries.
266
279
  * @method getAbsolutePath
267
280
  * @param filePath - The file path to get the absolute path for
268
281
  * @returns {string}
269
- * @throws {Error} When strictPaths is true and path would resolve outside cwd
282
+ * @throws {Error} When restrictAccessToCwd is true and path would resolve outside cwd
270
283
  */
271
284
  getAbsolutePath(filePath: string): string;
272
285
  /**
273
286
  * Get the Absolute Path with a custom working directory. If it is already absolute it will return the path as is.
274
- * When strictPaths is enabled, ensures the resolved path stays within the provided cwd boundaries.
287
+ * When restrictAccessToCwd is enabled, ensures the resolved path stays within the provided cwd boundaries.
275
288
  * @method getAbsolutePathWithCwd
276
289
  * @param filePath - The file path to get the absolute path for
277
290
  * @param cwd - The custom working directory to resolve relative paths from
278
291
  * @returns {string}
279
- * @throws {Error} When strictPaths is true and path would resolve outside the provided cwd
292
+ * @throws {Error} When restrictAccessToCwd is true and path would resolve outside the provided cwd
280
293
  */
281
294
  getAbsolutePathWithCwd(filePath: string, cwd: string): string;
282
295
  /**
package/dist/index.js CHANGED
@@ -1 +1 @@
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};
1
+ import b from"crypto";import l from"fs";import o from"path";import{createFromFile as y,FlatCache as p}from"flat-cache";function F(n,e){let s=o.basename(n),r=o.dirname(n);return f(s,r,e)}function f(n,e,s){let r={...s,cache:{cacheId:n,cacheDir:e}},t=new g(r);if(e){let i=`${e}/${n}`;l.existsSync(i)&&(t.cache=y(i,r.cache))}return t}var u=class{static create=f;static createFromFile=F},g=class{_cache=new p({useClone:!1});_useCheckSum=!1;_hashAlgorithm="md5";_cwd=process.cwd();_restrictAccessToCwd=!1;_logger;_useAbsolutePathAsKey=!1;_useModifiedTime=!0;constructor(e){e?.cache&&(this._cache=new p(e.cache)),e?.useCheckSum&&(this._useCheckSum=e.useCheckSum),e?.hashAlgorithm&&(this._hashAlgorithm=e.hashAlgorithm),e?.cwd&&(this._cwd=e.cwd),e?.useModifiedTime!==void 0&&(this._useModifiedTime=e.useModifiedTime),e?.restrictAccessToCwd!==void 0&&(this._restrictAccessToCwd=e.restrictAccessToCwd),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 useModifiedTime(){return this._useModifiedTime}set useModifiedTime(e){this._useModifiedTime=e}get restrictAccessToCwd(){return this._restrictAccessToCwd}set restrictAccessToCwd(e){this._restrictAccessToCwd=e}get useAbsolutePathAsKey(){return this._useAbsolutePathAsKey}set useAbsolutePathAsKey(e){this._useAbsolutePathAsKey=e}getHash(e){return b.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 h=s?.useCheckSum??this._useCheckSum;this._logger?.debug({useCheckSum:h},"Using checksum setting");let d=s?.useModifiedTime??this.useModifiedTime;this._logger?.debug({useModifiedTime:d},"Using modified time (mtime) 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"),h){let a=l.readFileSync(c);t.meta.hash=this.getHash(a),this._logger?.trace({hash:t.meta.hash},"Calculated file hash")}}catch(a){this._logger?.error({filePath:e,error:a},"Error reading file"),this.removeEntry(e);let m=!1;return a.message.includes("ENOENT")&&(m=!0,this._logger?.debug({filePath:e},"File not found")),{key:t.key,err:a,notFound:m,meta:{}}}return i?(d&&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")),h&&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._restrictAccessToCwd){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._restrictAccessToCwd){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{g as FileEntryCache,f as create,F as createFromFile,u as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "file-entry-cache",
3
- "version": "11.0.0-beta.5",
3
+ "version": "11.1.0",
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,7 +30,7 @@
30
30
  ],
31
31
  "devDependencies": {
32
32
  "@biomejs/biome": "^2.2.5",
33
- "@types/node": "^24.7.1",
33
+ "@types/node": "^24.7.2",
34
34
  "@vitest/coverage-v8": "^3.2.4",
35
35
  "pino": "^10.0.0",
36
36
  "rimraf": "^6.0.1",