file-entry-cache 11.1.0 → 11.1.2

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.
Files changed (3) hide show
  1. package/dist/index.cjs +559 -1
  2. package/dist/index.js +525 -1
  3. package/package.json +19 -13
package/dist/index.cjs CHANGED
@@ -1 +1,559 @@
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});
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, options) {
44
+ const fname = import_node_path.default.basename(filePath);
45
+ const directory = import_node_path.default.dirname(filePath);
46
+ return create(fname, directory, options);
47
+ }
48
+ function create(cacheId, cacheDirectory, options) {
49
+ const opts = {
50
+ ...options,
51
+ cache: {
52
+ cacheId,
53
+ cacheDir: cacheDirectory
54
+ }
55
+ };
56
+ const fileEntryCache = new FileEntryCache(opts);
57
+ if (cacheDirectory) {
58
+ const cachePath = `${cacheDirectory}/${cacheId}`;
59
+ if (import_node_fs.default.existsSync(cachePath)) {
60
+ fileEntryCache.cache = (0, import_flat_cache.createFromFile)(cachePath, opts.cache);
61
+ }
62
+ }
63
+ return fileEntryCache;
64
+ }
65
+ var FileEntryDefault = class {
66
+ static create = create;
67
+ static createFromFile = createFromFile;
68
+ };
69
+ var FileEntryCache = class {
70
+ _cache = new import_flat_cache.FlatCache({ useClone: false });
71
+ _useCheckSum = false;
72
+ _hashAlgorithm = "md5";
73
+ _cwd = process.cwd();
74
+ _restrictAccessToCwd = false;
75
+ _logger;
76
+ _useAbsolutePathAsKey = false;
77
+ _useModifiedTime = true;
78
+ /**
79
+ * Create a new FileEntryCache instance
80
+ * @param options - The options for the FileEntryCache (all properties are optional with defaults)
81
+ */
82
+ constructor(options) {
83
+ if (options?.cache) {
84
+ this._cache = new import_flat_cache.FlatCache(options.cache);
85
+ }
86
+ if (options?.useCheckSum) {
87
+ this._useCheckSum = options.useCheckSum;
88
+ }
89
+ if (options?.hashAlgorithm) {
90
+ this._hashAlgorithm = options.hashAlgorithm;
91
+ }
92
+ if (options?.cwd) {
93
+ this._cwd = options.cwd;
94
+ }
95
+ if (options?.useModifiedTime !== void 0) {
96
+ this._useModifiedTime = options.useModifiedTime;
97
+ }
98
+ if (options?.restrictAccessToCwd !== void 0) {
99
+ this._restrictAccessToCwd = options.restrictAccessToCwd;
100
+ }
101
+ if (options?.useAbsolutePathAsKey !== void 0) {
102
+ this._useAbsolutePathAsKey = options.useAbsolutePathAsKey;
103
+ }
104
+ if (options?.logger) {
105
+ this._logger = options.logger;
106
+ }
107
+ }
108
+ /**
109
+ * Get the cache
110
+ * @returns {FlatCache} The cache
111
+ */
112
+ get cache() {
113
+ return this._cache;
114
+ }
115
+ /**
116
+ * Set the cache
117
+ * @param {FlatCache} cache - The cache to set
118
+ */
119
+ set cache(cache) {
120
+ this._cache = cache;
121
+ }
122
+ /**
123
+ * Get the logger
124
+ * @returns {ILogger | undefined} The logger instance
125
+ */
126
+ get logger() {
127
+ return this._logger;
128
+ }
129
+ /**
130
+ * Set the logger
131
+ * @param {ILogger | undefined} logger - The logger to set
132
+ */
133
+ set logger(logger) {
134
+ this._logger = logger;
135
+ }
136
+ /**
137
+ * Use the hash to check if the file has changed
138
+ * @returns {boolean} if the hash is used to check if the file has changed (default: false)
139
+ */
140
+ get useCheckSum() {
141
+ return this._useCheckSum;
142
+ }
143
+ /**
144
+ * Set the useCheckSum value
145
+ * @param {boolean} value - The value to set
146
+ */
147
+ set useCheckSum(value) {
148
+ this._useCheckSum = value;
149
+ }
150
+ /**
151
+ * Get the hash algorithm
152
+ * @returns {string} The hash algorithm (default: 'md5')
153
+ */
154
+ get hashAlgorithm() {
155
+ return this._hashAlgorithm;
156
+ }
157
+ /**
158
+ * Set the hash algorithm
159
+ * @param {string} value - The value to set
160
+ */
161
+ set hashAlgorithm(value) {
162
+ this._hashAlgorithm = value;
163
+ }
164
+ /**
165
+ * Get the current working directory
166
+ * @returns {string} The current working directory (default: process.cwd())
167
+ */
168
+ get cwd() {
169
+ return this._cwd;
170
+ }
171
+ /**
172
+ * Set the current working directory
173
+ * @param {string} value - The value to set
174
+ */
175
+ set cwd(value) {
176
+ this._cwd = value;
177
+ }
178
+ /**
179
+ * Get whether to use modified time for change detection
180
+ * @returns {boolean} Whether modified time (mtime) is used for change detection (default: true)
181
+ */
182
+ get useModifiedTime() {
183
+ return this._useModifiedTime;
184
+ }
185
+ /**
186
+ * Set whether to use modified time for change detection
187
+ * @param {boolean} value - The value to set
188
+ */
189
+ set useModifiedTime(value) {
190
+ this._useModifiedTime = value;
191
+ }
192
+ /**
193
+ * Get whether to restrict paths to cwd boundaries
194
+ * @returns {boolean} Whether strict path checking is enabled (default: true)
195
+ */
196
+ get restrictAccessToCwd() {
197
+ return this._restrictAccessToCwd;
198
+ }
199
+ /**
200
+ * Set whether to restrict paths to cwd boundaries
201
+ * @param {boolean} value - The value to set
202
+ */
203
+ set restrictAccessToCwd(value) {
204
+ this._restrictAccessToCwd = value;
205
+ }
206
+ /**
207
+ * Get whether to use absolute path as cache key
208
+ * @returns {boolean} Whether cache keys use absolute paths (default: false)
209
+ */
210
+ get useAbsolutePathAsKey() {
211
+ return this._useAbsolutePathAsKey;
212
+ }
213
+ /**
214
+ * Set whether to use absolute path as cache key
215
+ * @param {boolean} value - The value to set
216
+ */
217
+ set useAbsolutePathAsKey(value) {
218
+ this._useAbsolutePathAsKey = value;
219
+ }
220
+ /**
221
+ * Given a buffer, calculate md5 hash of its content.
222
+ * @method getHash
223
+ * @param {Buffer} buffer buffer to calculate hash on
224
+ * @return {String} content hash digest
225
+ */
226
+ getHash(buffer) {
227
+ return import_node_crypto.default.createHash(this._hashAlgorithm).update(buffer).digest("hex");
228
+ }
229
+ /**
230
+ * Create the key for the file path used for caching.
231
+ * @method createFileKey
232
+ * @param {String} filePath
233
+ * @return {String}
234
+ */
235
+ createFileKey(filePath) {
236
+ let result = filePath;
237
+ if (this._useAbsolutePathAsKey && this.isRelativePath(filePath)) {
238
+ result = this.getAbsolutePathWithCwd(filePath, this._cwd);
239
+ }
240
+ return result;
241
+ }
242
+ /**
243
+ * Check if the file path is a relative path
244
+ * @method isRelativePath
245
+ * @param filePath - The file path to check
246
+ * @returns {boolean} if the file path is a relative path, false otherwise
247
+ */
248
+ isRelativePath(filePath) {
249
+ return !import_node_path.default.isAbsolute(filePath);
250
+ }
251
+ /**
252
+ * Delete the cache file from the disk
253
+ * @method deleteCacheFile
254
+ * @return {boolean} true if the file was deleted, false otherwise
255
+ */
256
+ deleteCacheFile() {
257
+ return this._cache.removeCacheFile();
258
+ }
259
+ /**
260
+ * Remove the cache from the file and clear the memory cache
261
+ * @method destroy
262
+ */
263
+ destroy() {
264
+ this._cache.destroy();
265
+ }
266
+ /**
267
+ * Remove and Entry From the Cache
268
+ * @method removeEntry
269
+ * @param filePath - The file path to remove from the cache
270
+ */
271
+ removeEntry(filePath) {
272
+ const key = this.createFileKey(filePath);
273
+ this._cache.removeKey(key);
274
+ }
275
+ /**
276
+ * Reconcile the cache
277
+ * @method reconcile
278
+ */
279
+ reconcile() {
280
+ const { items } = this._cache;
281
+ for (const item of items) {
282
+ const fileDescriptor = this.getFileDescriptor(item.key);
283
+ if (fileDescriptor.notFound) {
284
+ this._cache.removeKey(item.key);
285
+ }
286
+ }
287
+ this._cache.save();
288
+ }
289
+ /**
290
+ * Check if the file has changed
291
+ * @method hasFileChanged
292
+ * @param filePath - The file path to check
293
+ * @returns {boolean} if the file has changed, false otherwise
294
+ */
295
+ hasFileChanged(filePath) {
296
+ let result = false;
297
+ const fileDescriptor = this.getFileDescriptor(filePath);
298
+ if ((!fileDescriptor.err || !fileDescriptor.notFound) && fileDescriptor.changed) {
299
+ result = true;
300
+ }
301
+ return result;
302
+ }
303
+ /**
304
+ * Get the file descriptor for the file path
305
+ * @method getFileDescriptor
306
+ * @param filePath - The file path to get the file descriptor for
307
+ * @param options - The options for getting the file descriptor
308
+ * @returns The file descriptor
309
+ */
310
+ getFileDescriptor(filePath, options) {
311
+ this._logger?.debug({ filePath, options }, "Getting file descriptor");
312
+ let fstat;
313
+ const result = {
314
+ key: this.createFileKey(filePath),
315
+ changed: false,
316
+ meta: {}
317
+ };
318
+ this._logger?.trace({ key: result.key }, "Created file key");
319
+ const metaCache = this._cache.getKey(result.key);
320
+ if (metaCache) {
321
+ this._logger?.trace({ metaCache }, "Found cached meta");
322
+ } else {
323
+ this._logger?.trace("No cached meta found");
324
+ }
325
+ result.meta = metaCache ? { ...metaCache } : {};
326
+ const absolutePath = this.getAbsolutePath(filePath);
327
+ this._logger?.trace({ absolutePath }, "Resolved absolute path");
328
+ const useCheckSumValue = options?.useCheckSum ?? this._useCheckSum;
329
+ this._logger?.debug(
330
+ { useCheckSum: useCheckSumValue },
331
+ "Using checksum setting"
332
+ );
333
+ const useModifiedTimeValue = options?.useModifiedTime ?? this.useModifiedTime;
334
+ this._logger?.debug(
335
+ { useModifiedTime: useModifiedTimeValue },
336
+ "Using modified time (mtime) setting"
337
+ );
338
+ try {
339
+ fstat = import_node_fs.default.statSync(absolutePath);
340
+ result.meta.size = fstat.size;
341
+ result.meta.mtime = fstat.mtime.getTime();
342
+ this._logger?.trace(
343
+ { size: result.meta.size, mtime: result.meta.mtime },
344
+ "Read file stats"
345
+ );
346
+ if (useCheckSumValue) {
347
+ const buffer = import_node_fs.default.readFileSync(absolutePath);
348
+ result.meta.hash = this.getHash(buffer);
349
+ this._logger?.trace({ hash: result.meta.hash }, "Calculated file hash");
350
+ }
351
+ } catch (error) {
352
+ this._logger?.error({ filePath, error }, "Error reading file");
353
+ this.removeEntry(filePath);
354
+ let notFound = false;
355
+ if (error.message.includes("ENOENT")) {
356
+ notFound = true;
357
+ this._logger?.debug({ filePath }, "File not found");
358
+ }
359
+ return {
360
+ key: result.key,
361
+ err: error,
362
+ notFound,
363
+ meta: {}
364
+ };
365
+ }
366
+ if (!metaCache) {
367
+ result.changed = true;
368
+ this._cache.setKey(result.key, result.meta);
369
+ this._logger?.debug({ filePath }, "File not in cache, marked as changed");
370
+ return result;
371
+ }
372
+ if (useModifiedTimeValue && metaCache?.mtime !== result.meta?.mtime) {
373
+ result.changed = true;
374
+ this._logger?.debug(
375
+ { filePath, oldMtime: metaCache.mtime, newMtime: result.meta.mtime },
376
+ "File changed: mtime differs"
377
+ );
378
+ }
379
+ if (metaCache?.size !== result.meta?.size) {
380
+ result.changed = true;
381
+ this._logger?.debug(
382
+ { filePath, oldSize: metaCache.size, newSize: result.meta.size },
383
+ "File changed: size differs"
384
+ );
385
+ }
386
+ if (useCheckSumValue && metaCache?.hash !== result.meta?.hash) {
387
+ result.changed = true;
388
+ this._logger?.debug(
389
+ { filePath, oldHash: metaCache.hash, newHash: result.meta.hash },
390
+ "File changed: hash differs"
391
+ );
392
+ }
393
+ this._cache.setKey(result.key, result.meta);
394
+ if (result.changed) {
395
+ this._logger?.info({ filePath }, "File has changed");
396
+ } else {
397
+ this._logger?.debug({ filePath }, "File unchanged");
398
+ }
399
+ return result;
400
+ }
401
+ /**
402
+ * Get the file descriptors for the files
403
+ * @method normalizeEntries
404
+ * @param files?: string[] - The files to get the file descriptors for
405
+ * @returns The file descriptors
406
+ */
407
+ normalizeEntries(files) {
408
+ const result = [];
409
+ if (files) {
410
+ for (const file of files) {
411
+ const fileDescriptor = this.getFileDescriptor(file);
412
+ result.push(fileDescriptor);
413
+ }
414
+ return result;
415
+ }
416
+ const keys = this.cache.keys();
417
+ for (const key of keys) {
418
+ const fileDescriptor = this.getFileDescriptor(key);
419
+ if (!fileDescriptor.notFound && !fileDescriptor.err) {
420
+ result.push(fileDescriptor);
421
+ }
422
+ }
423
+ return result;
424
+ }
425
+ /**
426
+ * Analyze the files
427
+ * @method analyzeFiles
428
+ * @param files - The files to analyze
429
+ * @returns {AnalyzedFiles} The analysis of the files
430
+ */
431
+ analyzeFiles(files) {
432
+ const result = {
433
+ changedFiles: [],
434
+ notFoundFiles: [],
435
+ notChangedFiles: []
436
+ };
437
+ const fileDescriptors = this.normalizeEntries(files);
438
+ for (const fileDescriptor of fileDescriptors) {
439
+ if (fileDescriptor.notFound) {
440
+ result.notFoundFiles.push(fileDescriptor.key);
441
+ } else if (fileDescriptor.changed) {
442
+ result.changedFiles.push(fileDescriptor.key);
443
+ } else {
444
+ result.notChangedFiles.push(fileDescriptor.key);
445
+ }
446
+ }
447
+ return result;
448
+ }
449
+ /**
450
+ * Get the updated files
451
+ * @method getUpdatedFiles
452
+ * @param files - The files to get the updated files for
453
+ * @returns {string[]} The updated files
454
+ */
455
+ getUpdatedFiles(files) {
456
+ const result = [];
457
+ const fileDescriptors = this.normalizeEntries(files);
458
+ for (const fileDescriptor of fileDescriptors) {
459
+ if (fileDescriptor.changed) {
460
+ result.push(fileDescriptor.key);
461
+ }
462
+ }
463
+ return result;
464
+ }
465
+ /**
466
+ * Get the file descriptors by path prefix
467
+ * @method getFileDescriptorsByPath
468
+ * @param filePath - the path prefix to match
469
+ * @returns {FileDescriptor[]} The file descriptors
470
+ */
471
+ getFileDescriptorsByPath(filePath) {
472
+ const result = [];
473
+ const keys = this._cache.keys();
474
+ for (const key of keys) {
475
+ if (key.startsWith(filePath)) {
476
+ const fileDescriptor = this.getFileDescriptor(key);
477
+ result.push(fileDescriptor);
478
+ }
479
+ }
480
+ return result;
481
+ }
482
+ /**
483
+ * Get the Absolute Path. If it is already absolute it will return the path as is.
484
+ * When restrictAccessToCwd is enabled, ensures the resolved path stays within cwd boundaries.
485
+ * @method getAbsolutePath
486
+ * @param filePath - The file path to get the absolute path for
487
+ * @returns {string}
488
+ * @throws {Error} When restrictAccessToCwd is true and path would resolve outside cwd
489
+ */
490
+ getAbsolutePath(filePath) {
491
+ if (this.isRelativePath(filePath)) {
492
+ const sanitizedPath = filePath.replace(/\0/g, "");
493
+ const resolved = import_node_path.default.resolve(this._cwd, sanitizedPath);
494
+ if (this._restrictAccessToCwd) {
495
+ const normalizedResolved = import_node_path.default.normalize(resolved);
496
+ const normalizedCwd = import_node_path.default.normalize(this._cwd);
497
+ const isWithinCwd = normalizedResolved === normalizedCwd || normalizedResolved.startsWith(normalizedCwd + import_node_path.default.sep);
498
+ if (!isWithinCwd) {
499
+ throw new Error(
500
+ `Path traversal attempt blocked: "${filePath}" resolves outside of working directory "${this._cwd}"`
501
+ );
502
+ }
503
+ }
504
+ return resolved;
505
+ }
506
+ return filePath;
507
+ }
508
+ /**
509
+ * Get the Absolute Path with a custom working directory. If it is already absolute it will return the path as is.
510
+ * When restrictAccessToCwd is enabled, ensures the resolved path stays within the provided cwd boundaries.
511
+ * @method getAbsolutePathWithCwd
512
+ * @param filePath - The file path to get the absolute path for
513
+ * @param cwd - The custom working directory to resolve relative paths from
514
+ * @returns {string}
515
+ * @throws {Error} When restrictAccessToCwd is true and path would resolve outside the provided cwd
516
+ */
517
+ getAbsolutePathWithCwd(filePath, cwd) {
518
+ if (this.isRelativePath(filePath)) {
519
+ const sanitizedPath = filePath.replace(/\0/g, "");
520
+ const resolved = import_node_path.default.resolve(cwd, sanitizedPath);
521
+ if (this._restrictAccessToCwd) {
522
+ const normalizedResolved = import_node_path.default.normalize(resolved);
523
+ const normalizedCwd = import_node_path.default.normalize(cwd);
524
+ const isWithinCwd = normalizedResolved === normalizedCwd || normalizedResolved.startsWith(normalizedCwd + import_node_path.default.sep);
525
+ if (!isWithinCwd) {
526
+ throw new Error(
527
+ `Path traversal attempt blocked: "${filePath}" resolves outside of working directory "${cwd}"`
528
+ );
529
+ }
530
+ }
531
+ return resolved;
532
+ }
533
+ return filePath;
534
+ }
535
+ /**
536
+ * Rename cache keys that start with a given path prefix.
537
+ * @method renameCacheKeys
538
+ * @param oldPath - The old path prefix to rename
539
+ * @param newPath - The new path prefix to rename to
540
+ */
541
+ renameCacheKeys(oldPath, newPath) {
542
+ const keys = this._cache.keys();
543
+ for (const key of keys) {
544
+ if (key.startsWith(oldPath)) {
545
+ const newKey = key.replace(oldPath, newPath);
546
+ const meta = this._cache.getKey(key);
547
+ this._cache.removeKey(key);
548
+ this._cache.setKey(newKey, meta);
549
+ }
550
+ }
551
+ }
552
+ };
553
+ // Annotate the CommonJS export names for ESM import in node:
554
+ 0 && (module.exports = {
555
+ FileEntryCache,
556
+ create,
557
+ createFromFile
558
+ });
559
+ /* v8 ignore next -- @preserve */
package/dist/index.js CHANGED
@@ -1 +1,525 @@
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};
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, options) {
10
+ const fname = path.basename(filePath);
11
+ const directory = path.dirname(filePath);
12
+ return create(fname, directory, options);
13
+ }
14
+ function create(cacheId, cacheDirectory, options) {
15
+ const opts = {
16
+ ...options,
17
+ cache: {
18
+ cacheId,
19
+ cacheDir: cacheDirectory
20
+ }
21
+ };
22
+ const fileEntryCache = new FileEntryCache(opts);
23
+ if (cacheDirectory) {
24
+ const cachePath = `${cacheDirectory}/${cacheId}`;
25
+ if (fs.existsSync(cachePath)) {
26
+ fileEntryCache.cache = createFlatCacheFile(cachePath, opts.cache);
27
+ }
28
+ }
29
+ return fileEntryCache;
30
+ }
31
+ var FileEntryDefault = class {
32
+ static create = create;
33
+ static createFromFile = createFromFile;
34
+ };
35
+ var FileEntryCache = class {
36
+ _cache = new FlatCache({ useClone: false });
37
+ _useCheckSum = false;
38
+ _hashAlgorithm = "md5";
39
+ _cwd = process.cwd();
40
+ _restrictAccessToCwd = false;
41
+ _logger;
42
+ _useAbsolutePathAsKey = false;
43
+ _useModifiedTime = true;
44
+ /**
45
+ * Create a new FileEntryCache instance
46
+ * @param options - The options for the FileEntryCache (all properties are optional with defaults)
47
+ */
48
+ constructor(options) {
49
+ if (options?.cache) {
50
+ this._cache = new FlatCache(options.cache);
51
+ }
52
+ if (options?.useCheckSum) {
53
+ this._useCheckSum = options.useCheckSum;
54
+ }
55
+ if (options?.hashAlgorithm) {
56
+ this._hashAlgorithm = options.hashAlgorithm;
57
+ }
58
+ if (options?.cwd) {
59
+ this._cwd = options.cwd;
60
+ }
61
+ if (options?.useModifiedTime !== void 0) {
62
+ this._useModifiedTime = options.useModifiedTime;
63
+ }
64
+ if (options?.restrictAccessToCwd !== void 0) {
65
+ this._restrictAccessToCwd = options.restrictAccessToCwd;
66
+ }
67
+ if (options?.useAbsolutePathAsKey !== void 0) {
68
+ this._useAbsolutePathAsKey = options.useAbsolutePathAsKey;
69
+ }
70
+ if (options?.logger) {
71
+ this._logger = options.logger;
72
+ }
73
+ }
74
+ /**
75
+ * Get the cache
76
+ * @returns {FlatCache} The cache
77
+ */
78
+ get cache() {
79
+ return this._cache;
80
+ }
81
+ /**
82
+ * Set the cache
83
+ * @param {FlatCache} cache - The cache to set
84
+ */
85
+ set cache(cache) {
86
+ this._cache = cache;
87
+ }
88
+ /**
89
+ * Get the logger
90
+ * @returns {ILogger | undefined} The logger instance
91
+ */
92
+ get logger() {
93
+ return this._logger;
94
+ }
95
+ /**
96
+ * Set the logger
97
+ * @param {ILogger | undefined} logger - The logger to set
98
+ */
99
+ set logger(logger) {
100
+ this._logger = logger;
101
+ }
102
+ /**
103
+ * Use the hash to check if the file has changed
104
+ * @returns {boolean} if the hash is used to check if the file has changed (default: false)
105
+ */
106
+ get useCheckSum() {
107
+ return this._useCheckSum;
108
+ }
109
+ /**
110
+ * Set the useCheckSum value
111
+ * @param {boolean} value - The value to set
112
+ */
113
+ set useCheckSum(value) {
114
+ this._useCheckSum = value;
115
+ }
116
+ /**
117
+ * Get the hash algorithm
118
+ * @returns {string} The hash algorithm (default: 'md5')
119
+ */
120
+ get hashAlgorithm() {
121
+ return this._hashAlgorithm;
122
+ }
123
+ /**
124
+ * Set the hash algorithm
125
+ * @param {string} value - The value to set
126
+ */
127
+ set hashAlgorithm(value) {
128
+ this._hashAlgorithm = value;
129
+ }
130
+ /**
131
+ * Get the current working directory
132
+ * @returns {string} The current working directory (default: process.cwd())
133
+ */
134
+ get cwd() {
135
+ return this._cwd;
136
+ }
137
+ /**
138
+ * Set the current working directory
139
+ * @param {string} value - The value to set
140
+ */
141
+ set cwd(value) {
142
+ this._cwd = value;
143
+ }
144
+ /**
145
+ * Get whether to use modified time for change detection
146
+ * @returns {boolean} Whether modified time (mtime) is used for change detection (default: true)
147
+ */
148
+ get useModifiedTime() {
149
+ return this._useModifiedTime;
150
+ }
151
+ /**
152
+ * Set whether to use modified time for change detection
153
+ * @param {boolean} value - The value to set
154
+ */
155
+ set useModifiedTime(value) {
156
+ this._useModifiedTime = value;
157
+ }
158
+ /**
159
+ * Get whether to restrict paths to cwd boundaries
160
+ * @returns {boolean} Whether strict path checking is enabled (default: true)
161
+ */
162
+ get restrictAccessToCwd() {
163
+ return this._restrictAccessToCwd;
164
+ }
165
+ /**
166
+ * Set whether to restrict paths to cwd boundaries
167
+ * @param {boolean} value - The value to set
168
+ */
169
+ set restrictAccessToCwd(value) {
170
+ this._restrictAccessToCwd = value;
171
+ }
172
+ /**
173
+ * Get whether to use absolute path as cache key
174
+ * @returns {boolean} Whether cache keys use absolute paths (default: false)
175
+ */
176
+ get useAbsolutePathAsKey() {
177
+ return this._useAbsolutePathAsKey;
178
+ }
179
+ /**
180
+ * Set whether to use absolute path as cache key
181
+ * @param {boolean} value - The value to set
182
+ */
183
+ set useAbsolutePathAsKey(value) {
184
+ this._useAbsolutePathAsKey = value;
185
+ }
186
+ /**
187
+ * Given a buffer, calculate md5 hash of its content.
188
+ * @method getHash
189
+ * @param {Buffer} buffer buffer to calculate hash on
190
+ * @return {String} content hash digest
191
+ */
192
+ getHash(buffer) {
193
+ return crypto.createHash(this._hashAlgorithm).update(buffer).digest("hex");
194
+ }
195
+ /**
196
+ * Create the key for the file path used for caching.
197
+ * @method createFileKey
198
+ * @param {String} filePath
199
+ * @return {String}
200
+ */
201
+ createFileKey(filePath) {
202
+ let result = filePath;
203
+ if (this._useAbsolutePathAsKey && this.isRelativePath(filePath)) {
204
+ result = this.getAbsolutePathWithCwd(filePath, this._cwd);
205
+ }
206
+ return result;
207
+ }
208
+ /**
209
+ * Check if the file path is a relative path
210
+ * @method isRelativePath
211
+ * @param filePath - The file path to check
212
+ * @returns {boolean} if the file path is a relative path, false otherwise
213
+ */
214
+ isRelativePath(filePath) {
215
+ return !path.isAbsolute(filePath);
216
+ }
217
+ /**
218
+ * Delete the cache file from the disk
219
+ * @method deleteCacheFile
220
+ * @return {boolean} true if the file was deleted, false otherwise
221
+ */
222
+ deleteCacheFile() {
223
+ return this._cache.removeCacheFile();
224
+ }
225
+ /**
226
+ * Remove the cache from the file and clear the memory cache
227
+ * @method destroy
228
+ */
229
+ destroy() {
230
+ this._cache.destroy();
231
+ }
232
+ /**
233
+ * Remove and Entry From the Cache
234
+ * @method removeEntry
235
+ * @param filePath - The file path to remove from the cache
236
+ */
237
+ removeEntry(filePath) {
238
+ const key = this.createFileKey(filePath);
239
+ this._cache.removeKey(key);
240
+ }
241
+ /**
242
+ * Reconcile the cache
243
+ * @method reconcile
244
+ */
245
+ reconcile() {
246
+ const { items } = this._cache;
247
+ for (const item of items) {
248
+ const fileDescriptor = this.getFileDescriptor(item.key);
249
+ if (fileDescriptor.notFound) {
250
+ this._cache.removeKey(item.key);
251
+ }
252
+ }
253
+ this._cache.save();
254
+ }
255
+ /**
256
+ * Check if the file has changed
257
+ * @method hasFileChanged
258
+ * @param filePath - The file path to check
259
+ * @returns {boolean} if the file has changed, false otherwise
260
+ */
261
+ hasFileChanged(filePath) {
262
+ let result = false;
263
+ const fileDescriptor = this.getFileDescriptor(filePath);
264
+ if ((!fileDescriptor.err || !fileDescriptor.notFound) && fileDescriptor.changed) {
265
+ result = true;
266
+ }
267
+ return result;
268
+ }
269
+ /**
270
+ * Get the file descriptor for the file path
271
+ * @method getFileDescriptor
272
+ * @param filePath - The file path to get the file descriptor for
273
+ * @param options - The options for getting the file descriptor
274
+ * @returns The file descriptor
275
+ */
276
+ getFileDescriptor(filePath, options) {
277
+ this._logger?.debug({ filePath, options }, "Getting file descriptor");
278
+ let fstat;
279
+ const result = {
280
+ key: this.createFileKey(filePath),
281
+ changed: false,
282
+ meta: {}
283
+ };
284
+ this._logger?.trace({ key: result.key }, "Created file key");
285
+ const metaCache = this._cache.getKey(result.key);
286
+ if (metaCache) {
287
+ this._logger?.trace({ metaCache }, "Found cached meta");
288
+ } else {
289
+ this._logger?.trace("No cached meta found");
290
+ }
291
+ result.meta = metaCache ? { ...metaCache } : {};
292
+ const absolutePath = this.getAbsolutePath(filePath);
293
+ this._logger?.trace({ absolutePath }, "Resolved absolute path");
294
+ const useCheckSumValue = options?.useCheckSum ?? this._useCheckSum;
295
+ this._logger?.debug(
296
+ { useCheckSum: useCheckSumValue },
297
+ "Using checksum setting"
298
+ );
299
+ const useModifiedTimeValue = options?.useModifiedTime ?? this.useModifiedTime;
300
+ this._logger?.debug(
301
+ { useModifiedTime: useModifiedTimeValue },
302
+ "Using modified time (mtime) setting"
303
+ );
304
+ try {
305
+ fstat = fs.statSync(absolutePath);
306
+ result.meta.size = fstat.size;
307
+ result.meta.mtime = fstat.mtime.getTime();
308
+ this._logger?.trace(
309
+ { size: result.meta.size, mtime: result.meta.mtime },
310
+ "Read file stats"
311
+ );
312
+ if (useCheckSumValue) {
313
+ const buffer = fs.readFileSync(absolutePath);
314
+ result.meta.hash = this.getHash(buffer);
315
+ this._logger?.trace({ hash: result.meta.hash }, "Calculated file hash");
316
+ }
317
+ } catch (error) {
318
+ this._logger?.error({ filePath, error }, "Error reading file");
319
+ this.removeEntry(filePath);
320
+ let notFound = false;
321
+ if (error.message.includes("ENOENT")) {
322
+ notFound = true;
323
+ this._logger?.debug({ filePath }, "File not found");
324
+ }
325
+ return {
326
+ key: result.key,
327
+ err: error,
328
+ notFound,
329
+ meta: {}
330
+ };
331
+ }
332
+ if (!metaCache) {
333
+ result.changed = true;
334
+ this._cache.setKey(result.key, result.meta);
335
+ this._logger?.debug({ filePath }, "File not in cache, marked as changed");
336
+ return result;
337
+ }
338
+ if (useModifiedTimeValue && metaCache?.mtime !== result.meta?.mtime) {
339
+ result.changed = true;
340
+ this._logger?.debug(
341
+ { filePath, oldMtime: metaCache.mtime, newMtime: result.meta.mtime },
342
+ "File changed: mtime differs"
343
+ );
344
+ }
345
+ if (metaCache?.size !== result.meta?.size) {
346
+ result.changed = true;
347
+ this._logger?.debug(
348
+ { filePath, oldSize: metaCache.size, newSize: result.meta.size },
349
+ "File changed: size differs"
350
+ );
351
+ }
352
+ if (useCheckSumValue && metaCache?.hash !== result.meta?.hash) {
353
+ result.changed = true;
354
+ this._logger?.debug(
355
+ { filePath, oldHash: metaCache.hash, newHash: result.meta.hash },
356
+ "File changed: hash differs"
357
+ );
358
+ }
359
+ this._cache.setKey(result.key, result.meta);
360
+ if (result.changed) {
361
+ this._logger?.info({ filePath }, "File has changed");
362
+ } else {
363
+ this._logger?.debug({ filePath }, "File unchanged");
364
+ }
365
+ return result;
366
+ }
367
+ /**
368
+ * Get the file descriptors for the files
369
+ * @method normalizeEntries
370
+ * @param files?: string[] - The files to get the file descriptors for
371
+ * @returns The file descriptors
372
+ */
373
+ normalizeEntries(files) {
374
+ const result = [];
375
+ if (files) {
376
+ for (const file of files) {
377
+ const fileDescriptor = this.getFileDescriptor(file);
378
+ result.push(fileDescriptor);
379
+ }
380
+ return result;
381
+ }
382
+ const keys = this.cache.keys();
383
+ for (const key of keys) {
384
+ const fileDescriptor = this.getFileDescriptor(key);
385
+ if (!fileDescriptor.notFound && !fileDescriptor.err) {
386
+ result.push(fileDescriptor);
387
+ }
388
+ }
389
+ return result;
390
+ }
391
+ /**
392
+ * Analyze the files
393
+ * @method analyzeFiles
394
+ * @param files - The files to analyze
395
+ * @returns {AnalyzedFiles} The analysis of the files
396
+ */
397
+ analyzeFiles(files) {
398
+ const result = {
399
+ changedFiles: [],
400
+ notFoundFiles: [],
401
+ notChangedFiles: []
402
+ };
403
+ const fileDescriptors = this.normalizeEntries(files);
404
+ for (const fileDescriptor of fileDescriptors) {
405
+ if (fileDescriptor.notFound) {
406
+ result.notFoundFiles.push(fileDescriptor.key);
407
+ } else if (fileDescriptor.changed) {
408
+ result.changedFiles.push(fileDescriptor.key);
409
+ } else {
410
+ result.notChangedFiles.push(fileDescriptor.key);
411
+ }
412
+ }
413
+ return result;
414
+ }
415
+ /**
416
+ * Get the updated files
417
+ * @method getUpdatedFiles
418
+ * @param files - The files to get the updated files for
419
+ * @returns {string[]} The updated files
420
+ */
421
+ getUpdatedFiles(files) {
422
+ const result = [];
423
+ const fileDescriptors = this.normalizeEntries(files);
424
+ for (const fileDescriptor of fileDescriptors) {
425
+ if (fileDescriptor.changed) {
426
+ result.push(fileDescriptor.key);
427
+ }
428
+ }
429
+ return result;
430
+ }
431
+ /**
432
+ * Get the file descriptors by path prefix
433
+ * @method getFileDescriptorsByPath
434
+ * @param filePath - the path prefix to match
435
+ * @returns {FileDescriptor[]} The file descriptors
436
+ */
437
+ getFileDescriptorsByPath(filePath) {
438
+ const result = [];
439
+ const keys = this._cache.keys();
440
+ for (const key of keys) {
441
+ if (key.startsWith(filePath)) {
442
+ const fileDescriptor = this.getFileDescriptor(key);
443
+ result.push(fileDescriptor);
444
+ }
445
+ }
446
+ return result;
447
+ }
448
+ /**
449
+ * Get the Absolute Path. If it is already absolute it will return the path as is.
450
+ * When restrictAccessToCwd is enabled, ensures the resolved path stays within cwd boundaries.
451
+ * @method getAbsolutePath
452
+ * @param filePath - The file path to get the absolute path for
453
+ * @returns {string}
454
+ * @throws {Error} When restrictAccessToCwd is true and path would resolve outside cwd
455
+ */
456
+ getAbsolutePath(filePath) {
457
+ if (this.isRelativePath(filePath)) {
458
+ const sanitizedPath = filePath.replace(/\0/g, "");
459
+ const resolved = path.resolve(this._cwd, sanitizedPath);
460
+ if (this._restrictAccessToCwd) {
461
+ const normalizedResolved = path.normalize(resolved);
462
+ const normalizedCwd = path.normalize(this._cwd);
463
+ const isWithinCwd = normalizedResolved === normalizedCwd || normalizedResolved.startsWith(normalizedCwd + path.sep);
464
+ if (!isWithinCwd) {
465
+ throw new Error(
466
+ `Path traversal attempt blocked: "${filePath}" resolves outside of working directory "${this._cwd}"`
467
+ );
468
+ }
469
+ }
470
+ return resolved;
471
+ }
472
+ return filePath;
473
+ }
474
+ /**
475
+ * Get the Absolute Path with a custom working directory. If it is already absolute it will return the path as is.
476
+ * When restrictAccessToCwd is enabled, ensures the resolved path stays within the provided cwd boundaries.
477
+ * @method getAbsolutePathWithCwd
478
+ * @param filePath - The file path to get the absolute path for
479
+ * @param cwd - The custom working directory to resolve relative paths from
480
+ * @returns {string}
481
+ * @throws {Error} When restrictAccessToCwd is true and path would resolve outside the provided cwd
482
+ */
483
+ getAbsolutePathWithCwd(filePath, cwd) {
484
+ if (this.isRelativePath(filePath)) {
485
+ const sanitizedPath = filePath.replace(/\0/g, "");
486
+ const resolved = path.resolve(cwd, sanitizedPath);
487
+ if (this._restrictAccessToCwd) {
488
+ const normalizedResolved = path.normalize(resolved);
489
+ const normalizedCwd = path.normalize(cwd);
490
+ const isWithinCwd = normalizedResolved === normalizedCwd || normalizedResolved.startsWith(normalizedCwd + path.sep);
491
+ if (!isWithinCwd) {
492
+ throw new Error(
493
+ `Path traversal attempt blocked: "${filePath}" resolves outside of working directory "${cwd}"`
494
+ );
495
+ }
496
+ }
497
+ return resolved;
498
+ }
499
+ return filePath;
500
+ }
501
+ /**
502
+ * Rename cache keys that start with a given path prefix.
503
+ * @method renameCacheKeys
504
+ * @param oldPath - The old path prefix to rename
505
+ * @param newPath - The new path prefix to rename to
506
+ */
507
+ renameCacheKeys(oldPath, newPath) {
508
+ const keys = this._cache.keys();
509
+ for (const key of keys) {
510
+ if (key.startsWith(oldPath)) {
511
+ const newKey = key.replace(oldPath, newPath);
512
+ const meta = this._cache.getKey(key);
513
+ this._cache.removeKey(key);
514
+ this._cache.setKey(newKey, meta);
515
+ }
516
+ }
517
+ }
518
+ };
519
+ export {
520
+ FileEntryCache,
521
+ create,
522
+ createFromFile,
523
+ FileEntryDefault as default
524
+ };
525
+ /* v8 ignore next -- @preserve */
package/package.json CHANGED
@@ -1,15 +1,21 @@
1
1
  {
2
2
  "name": "file-entry-cache",
3
- "version": "11.1.0",
3
+ "version": "11.1.2",
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
- "main": "./dist/index.cjs",
6
+ "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
9
9
  "exports": {
10
10
  ".": {
11
- "require": "./dist/index.cjs",
12
- "import": "./dist/index.js"
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
13
19
  }
14
20
  },
15
21
  "repository": {
@@ -29,24 +35,24 @@
29
35
  "cache"
30
36
  ],
31
37
  "devDependencies": {
32
- "@biomejs/biome": "^2.2.5",
33
- "@types/node": "^24.7.2",
34
- "@vitest/coverage-v8": "^3.2.4",
35
- "pino": "^10.0.0",
36
- "rimraf": "^6.0.1",
37
- "tsup": "^8.5.0",
38
+ "@biomejs/biome": "^2.3.11",
39
+ "@types/node": "^25.0.8",
40
+ "@vitest/coverage-v8": "^4.0.17",
41
+ "pino": "^10.1.1",
42
+ "rimraf": "^6.1.2",
43
+ "tsup": "^8.5.1",
38
44
  "typescript": "^5.9.3",
39
- "vitest": "^3.2.4"
45
+ "vitest": "^4.0.17"
40
46
  },
41
47
  "dependencies": {
42
- "flat-cache": "^6.1.17"
48
+ "flat-cache": "^6.1.20"
43
49
  },
44
50
  "files": [
45
51
  "dist",
46
52
  "license"
47
53
  ],
48
54
  "scripts": {
49
- "build": "rimraf ./dist && tsup src/index.ts --format cjs,esm --dts --clean --minify",
55
+ "build": "rimraf ./dist && tsup src/index.ts --format cjs,esm --dts --clean",
50
56
  "prepublish": "pnpm build",
51
57
  "lint": "biome check --write --error-on-warnings",
52
58
  "test": "pnpm lint && vitest run --coverage",