file-entry-cache 11.1.3 → 11.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,559 +1,551 @@
1
- "use strict";
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
5
+ //#region \0rolldown/runtime.js
2
6
  var __create = Object.create;
3
7
  var __defProp = Object.defineProperty;
4
8
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
9
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
10
  var __getProtoOf = Object.getPrototypeOf;
7
11
  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
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;
13
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
14
+ key = keys[i];
15
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
16
+ get: ((k) => from[k]).bind(null, key),
17
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
18
+ });
19
+ }
20
+ return to;
19
21
  };
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");
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
23
+ value: mod,
24
+ enumerable: true
25
+ }) : target, mod));
26
+ //#endregion
27
+ let node_crypto = require("node:crypto");
28
+ node_crypto = __toESM(node_crypto, 1);
29
+ let node_fs = require("node:fs");
30
+ node_fs = __toESM(node_fs, 1);
31
+ let node_path = require("node:path");
32
+ node_path = __toESM(node_path, 1);
33
+ let flat_cache = require("flat-cache");
34
+ //#region src/index.ts
35
+ /**
36
+ * Create a new FileEntryCache instance from a file path
37
+ * @param filePath - The path to the cache file
38
+ * @param options - create options such as useChecksum, cwd, and more
39
+ * @returns A new FileEntryCache instance
40
+ */
43
41
  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);
42
+ return create(node_path.default.basename(filePath), node_path.default.dirname(filePath), options);
47
43
  }
44
+ /**
45
+ * Create a new FileEntryCache instance
46
+ * @param cacheId - The cache file name
47
+ * @param cacheDirectory - The directory to store the cache file (default: undefined, cache won't be persisted)
48
+ * @param options - Whether to use checksum to detect file changes (default: false)
49
+ * @returns A new FileEntryCache instance
50
+ */
48
51
  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;
52
+ const opts = {
53
+ ...options,
54
+ cache: {
55
+ cacheId,
56
+ cacheDir: cacheDirectory
57
+ }
58
+ };
59
+ const fileEntryCache = new FileEntryCache(opts);
60
+ if (cacheDirectory) {
61
+ const cachePath = `${cacheDirectory}/${cacheId}`;
62
+ if (node_fs.default.existsSync(cachePath)) try {
63
+ fileEntryCache.cache = (0, flat_cache.createFromFile)(cachePath, opts.cache);
64
+ } catch (error) {
65
+ if (error instanceof SyntaxError || error instanceof TypeError) fileEntryCache.cache = new flat_cache.FlatCache(opts.cache);
66
+ else throw error;
67
+ }
68
+ }
69
+ return fileEntryCache;
64
70
  }
65
71
  var FileEntryDefault = class {
66
- static create = create;
67
- static createFromFile = createFromFile;
72
+ static create = create;
73
+ static createFromFile = createFromFile;
68
74
  };
69
75
  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
- }
76
+ _cache = new flat_cache.FlatCache({ useClone: false });
77
+ _useCheckSum = false;
78
+ _hashAlgorithm = "md5";
79
+ _cwd = process.cwd();
80
+ _restrictAccessToCwd = false;
81
+ _logger;
82
+ _useAbsolutePathAsKey = false;
83
+ _useModifiedTime = true;
84
+ /**
85
+ * Snapshot of the persisted meta for each key as of the last load/reconcile.
86
+ * Change detection compares against this baseline (not the working cache) so
87
+ * that repeated `getFileDescriptor()` calls keep reporting a file as changed
88
+ * until the cache is reconciled. The set of keys also tracks which files were
89
+ * visited during the current session so that `reconcile()` only updates those.
90
+ */
91
+ _originalMeta = /* @__PURE__ */ new Map();
92
+ /**
93
+ * Create a new FileEntryCache instance
94
+ * @param options - The options for the FileEntryCache (all properties are optional with defaults)
95
+ */
96
+ constructor(options) {
97
+ if (options?.cache) this._cache = new flat_cache.FlatCache(options.cache);
98
+ if (options?.useCheckSum) this._useCheckSum = options.useCheckSum;
99
+ if (options?.hashAlgorithm) this._hashAlgorithm = options.hashAlgorithm;
100
+ if (options?.cwd) this._cwd = options.cwd;
101
+ if (options?.useModifiedTime !== void 0) this._useModifiedTime = options.useModifiedTime;
102
+ if (options?.restrictAccessToCwd !== void 0) this._restrictAccessToCwd = options.restrictAccessToCwd;
103
+ if (options?.useAbsolutePathAsKey !== void 0) this._useAbsolutePathAsKey = options.useAbsolutePathAsKey;
104
+ if (options?.logger) this._logger = options.logger;
105
+ }
106
+ /**
107
+ * Get the cache
108
+ * @returns {FlatCache} The cache
109
+ */
110
+ get cache() {
111
+ return this._cache;
112
+ }
113
+ /**
114
+ * Set the cache
115
+ * @param {FlatCache} cache - The cache to set
116
+ */
117
+ set cache(cache) {
118
+ this._cache = cache;
119
+ this._originalMeta = /* @__PURE__ */ new Map();
120
+ }
121
+ /**
122
+ * Get the logger
123
+ * @returns {ILogger | undefined} The logger instance
124
+ */
125
+ get logger() {
126
+ return this._logger;
127
+ }
128
+ /**
129
+ * Set the logger
130
+ * @param {ILogger | undefined} logger - The logger to set
131
+ */
132
+ set logger(logger) {
133
+ this._logger = logger;
134
+ }
135
+ /**
136
+ * Use the hash to check if the file has changed
137
+ * @returns {boolean} if the hash is used to check if the file has changed (default: false)
138
+ */
139
+ get useCheckSum() {
140
+ return this._useCheckSum;
141
+ }
142
+ /**
143
+ * Set the useCheckSum value
144
+ * @param {boolean} value - The value to set
145
+ */
146
+ set useCheckSum(value) {
147
+ this._useCheckSum = value;
148
+ }
149
+ /**
150
+ * Get the hash algorithm
151
+ * @returns {string} The hash algorithm (default: 'md5')
152
+ */
153
+ get hashAlgorithm() {
154
+ return this._hashAlgorithm;
155
+ }
156
+ /**
157
+ * Set the hash algorithm
158
+ * @param {string} value - The value to set
159
+ */
160
+ set hashAlgorithm(value) {
161
+ this._hashAlgorithm = value;
162
+ }
163
+ /**
164
+ * Get the current working directory
165
+ * @returns {string} The current working directory (default: process.cwd())
166
+ */
167
+ get cwd() {
168
+ return this._cwd;
169
+ }
170
+ /**
171
+ * Set the current working directory
172
+ *
173
+ * Note: when relative paths are used as cache keys (the default), `cwd` must
174
+ * stay stable across a `getFileDescriptor()` / `reconcile()` cycle. Relative
175
+ * keys are resolved against the *current* `cwd` each time, so changing it
176
+ * mid-run can cause `reconcile()` to resolve a key to a different (missing)
177
+ * path and drop the entry. Use absolute keys (`useAbsolutePathAsKey: true`)
178
+ * if `cwd` must change during a run.
179
+ * @param {string} value - The value to set
180
+ */
181
+ set cwd(value) {
182
+ this._cwd = value;
183
+ }
184
+ /**
185
+ * Get whether to use modified time for change detection
186
+ * @returns {boolean} Whether modified time (mtime) is used for change detection (default: true)
187
+ */
188
+ get useModifiedTime() {
189
+ return this._useModifiedTime;
190
+ }
191
+ /**
192
+ * Set whether to use modified time for change detection
193
+ * @param {boolean} value - The value to set
194
+ */
195
+ set useModifiedTime(value) {
196
+ this._useModifiedTime = value;
197
+ }
198
+ /**
199
+ * Get whether to restrict paths to cwd boundaries
200
+ * @returns {boolean} Whether strict path checking is enabled (default: true)
201
+ */
202
+ get restrictAccessToCwd() {
203
+ return this._restrictAccessToCwd;
204
+ }
205
+ /**
206
+ * Set whether to restrict paths to cwd boundaries
207
+ * @param {boolean} value - The value to set
208
+ */
209
+ set restrictAccessToCwd(value) {
210
+ this._restrictAccessToCwd = value;
211
+ }
212
+ /**
213
+ * Get whether to use absolute path as cache key
214
+ * @returns {boolean} Whether cache keys use absolute paths (default: false)
215
+ */
216
+ get useAbsolutePathAsKey() {
217
+ return this._useAbsolutePathAsKey;
218
+ }
219
+ /**
220
+ * Set whether to use absolute path as cache key
221
+ * @param {boolean} value - The value to set
222
+ */
223
+ set useAbsolutePathAsKey(value) {
224
+ this._useAbsolutePathAsKey = value;
225
+ }
226
+ /**
227
+ * Given a buffer, calculate md5 hash of its content.
228
+ * @method getHash
229
+ * @param {Buffer} buffer buffer to calculate hash on
230
+ * @return {String} content hash digest
231
+ */
232
+ getHash(buffer) {
233
+ return node_crypto.default.createHash(this._hashAlgorithm).update(buffer).digest("hex");
234
+ }
235
+ /**
236
+ * Create the key for the file path used for caching.
237
+ * @method createFileKey
238
+ * @param {String} filePath
239
+ * @return {String}
240
+ */
241
+ createFileKey(filePath) {
242
+ let result = filePath;
243
+ if (this._useAbsolutePathAsKey && this.isRelativePath(filePath)) result = this.getAbsolutePathWithCwd(filePath, this._cwd);
244
+ return result;
245
+ }
246
+ /**
247
+ * Check if the file path is a relative path
248
+ * @method isRelativePath
249
+ * @param filePath - The file path to check
250
+ * @returns {boolean} if the file path is a relative path, false otherwise
251
+ */
252
+ isRelativePath(filePath) {
253
+ return !node_path.default.isAbsolute(filePath);
254
+ }
255
+ /**
256
+ * Delete the cache file from the disk
257
+ * @method deleteCacheFile
258
+ * @return {boolean} true if the file was deleted, false otherwise
259
+ */
260
+ deleteCacheFile() {
261
+ return this._cache.removeCacheFile();
262
+ }
263
+ /**
264
+ * Remove the cache from the file and clear the memory cache
265
+ * @method destroy
266
+ */
267
+ destroy() {
268
+ this._cache.destroy();
269
+ this._originalMeta = /* @__PURE__ */ new Map();
270
+ }
271
+ /**
272
+ * Remove and Entry From the Cache
273
+ * @method removeEntry
274
+ * @param filePath - The file path to remove from the cache
275
+ */
276
+ removeEntry(filePath) {
277
+ const key = this.createFileKey(filePath);
278
+ this._cache.removeKey(key);
279
+ this._originalMeta.delete(key);
280
+ }
281
+ /**
282
+ * Reconcile the cache
283
+ * @method reconcile
284
+ */
285
+ reconcile() {
286
+ for (const key of [...this._cache.keys()]) try {
287
+ node_fs.default.statSync(this.getAbsolutePath(key));
288
+ } catch (error) {
289
+ if (error.code === "ENOENT") {
290
+ this._cache.removeKey(key);
291
+ this._originalMeta.delete(key);
292
+ } else this._logger?.error({
293
+ key,
294
+ error
295
+ }, "reconcile: unable to stat file; keeping cached entry");
296
+ }
297
+ for (const key of [...this._originalMeta.keys()]) {
298
+ const meta = this._cache.getKey(key);
299
+ if (meta) this._originalMeta.set(key, { ...meta });
300
+ else this._originalMeta.delete(key);
301
+ }
302
+ this._cache.save();
303
+ }
304
+ /**
305
+ * Check if the file has changed
306
+ * @method hasFileChanged
307
+ * @param filePath - The file path to check
308
+ * @returns {boolean} if the file has changed, false otherwise
309
+ */
310
+ hasFileChanged(filePath) {
311
+ let result = false;
312
+ const fileDescriptor = this.getFileDescriptor(filePath);
313
+ if ((!fileDescriptor.err || !fileDescriptor.notFound) && fileDescriptor.changed) result = true;
314
+ return result;
315
+ }
316
+ /**
317
+ * Get the file descriptor for the file path
318
+ * @method getFileDescriptor
319
+ * @param filePath - The file path to get the file descriptor for
320
+ * @param options - The options for getting the file descriptor
321
+ * @returns The file descriptor
322
+ */
323
+ getFileDescriptor(filePath, options) {
324
+ this._logger?.debug({
325
+ filePath,
326
+ options
327
+ }, "Getting file descriptor");
328
+ let fstat;
329
+ const result = {
330
+ key: this.createFileKey(filePath),
331
+ changed: false,
332
+ meta: {}
333
+ };
334
+ this._logger?.trace({ key: result.key }, "Created file key");
335
+ const metaCache = this._cache.getKey(result.key);
336
+ if (metaCache) this._logger?.trace({ metaCache }, "Found cached meta");
337
+ else this._logger?.trace("No cached meta found");
338
+ result.meta = metaCache ? { ...metaCache } : {};
339
+ const absolutePath = this.getAbsolutePath(filePath);
340
+ this._logger?.trace({ absolutePath }, "Resolved absolute path");
341
+ const useCheckSumValue = options?.useCheckSum ?? this._useCheckSum;
342
+ this._logger?.debug({ useCheckSum: useCheckSumValue }, "Using checksum setting");
343
+ const useModifiedTimeValue = options?.useModifiedTime ?? this.useModifiedTime;
344
+ this._logger?.debug({ useModifiedTime: useModifiedTimeValue }, "Using modified time (mtime) setting");
345
+ try {
346
+ fstat = node_fs.default.statSync(absolutePath);
347
+ result.meta.size = fstat.size;
348
+ result.meta.mtime = fstat.mtime.getTime();
349
+ this._logger?.trace({
350
+ size: result.meta.size,
351
+ mtime: result.meta.mtime
352
+ }, "Read file stats");
353
+ if (useCheckSumValue) {
354
+ const buffer = node_fs.default.readFileSync(absolutePath);
355
+ result.meta.hash = this.getHash(buffer);
356
+ this._logger?.trace({ hash: result.meta.hash }, "Calculated file hash");
357
+ }
358
+ } catch (error) {
359
+ this._logger?.error({
360
+ filePath,
361
+ error
362
+ }, "Error reading file");
363
+ this.removeEntry(filePath);
364
+ let notFound = false;
365
+ if (error.message.includes("ENOENT")) {
366
+ notFound = true;
367
+ this._logger?.debug({ filePath }, "File not found");
368
+ }
369
+ return {
370
+ key: result.key,
371
+ err: error,
372
+ notFound,
373
+ meta: {}
374
+ };
375
+ }
376
+ if (!this._originalMeta.has(result.key)) this._originalMeta.set(result.key, metaCache ? { ...metaCache } : void 0);
377
+ const baseline = this._originalMeta.get(result.key);
378
+ if (baseline === void 0) {
379
+ result.changed = true;
380
+ this._cache.setKey(result.key, result.meta);
381
+ this._logger?.debug({ filePath }, "File not in cache, marked as changed");
382
+ return result;
383
+ }
384
+ if (useModifiedTimeValue && baseline.mtime !== result.meta?.mtime) {
385
+ result.changed = true;
386
+ this._logger?.debug({
387
+ filePath,
388
+ oldMtime: baseline.mtime,
389
+ newMtime: result.meta.mtime
390
+ }, "File changed: mtime differs");
391
+ }
392
+ if (baseline.size !== result.meta?.size) {
393
+ result.changed = true;
394
+ this._logger?.debug({
395
+ filePath,
396
+ oldSize: baseline.size,
397
+ newSize: result.meta.size
398
+ }, "File changed: size differs");
399
+ }
400
+ if (useCheckSumValue && baseline.hash !== result.meta?.hash) {
401
+ result.changed = true;
402
+ this._logger?.debug({
403
+ filePath,
404
+ oldHash: baseline.hash,
405
+ newHash: result.meta.hash
406
+ }, "File changed: hash differs");
407
+ }
408
+ this._cache.setKey(result.key, result.meta);
409
+ if (result.changed) this._logger?.info({ filePath }, "File has changed");
410
+ else this._logger?.debug({ filePath }, "File unchanged");
411
+ return result;
412
+ }
413
+ /**
414
+ * Get the file descriptors for the files
415
+ * @method normalizeEntries
416
+ * @param files?: string[] - The files to get the file descriptors for
417
+ * @returns The file descriptors
418
+ */
419
+ normalizeEntries(files) {
420
+ const result = [];
421
+ if (files) {
422
+ for (const file of files) {
423
+ const fileDescriptor = this.getFileDescriptor(file);
424
+ result.push(fileDescriptor);
425
+ }
426
+ return result;
427
+ }
428
+ const keys = this.cache.keys();
429
+ for (const key of keys) {
430
+ const fileDescriptor = this.getFileDescriptor(key);
431
+ if (!fileDescriptor.notFound && !fileDescriptor.err) result.push(fileDescriptor);
432
+ }
433
+ return result;
434
+ }
435
+ /**
436
+ * Analyze the files
437
+ * @method analyzeFiles
438
+ * @param files - The files to analyze
439
+ * @returns {AnalyzedFiles} The analysis of the files
440
+ */
441
+ analyzeFiles(files) {
442
+ const result = {
443
+ changedFiles: [],
444
+ notFoundFiles: [],
445
+ notChangedFiles: []
446
+ };
447
+ const fileDescriptors = this.normalizeEntries(files);
448
+ for (const fileDescriptor of fileDescriptors) if (fileDescriptor.notFound) result.notFoundFiles.push(fileDescriptor.key);
449
+ else if (fileDescriptor.changed) result.changedFiles.push(fileDescriptor.key);
450
+ else result.notChangedFiles.push(fileDescriptor.key);
451
+ return result;
452
+ }
453
+ /**
454
+ * Get the updated files
455
+ * @method getUpdatedFiles
456
+ * @param files - The files to get the updated files for
457
+ * @returns {string[]} The updated files
458
+ */
459
+ getUpdatedFiles(files) {
460
+ const result = [];
461
+ const fileDescriptors = this.normalizeEntries(files);
462
+ for (const fileDescriptor of fileDescriptors) if (fileDescriptor.changed) result.push(fileDescriptor.key);
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
+ /* v8 ignore next -- @preserve */
476
+ if (key.startsWith(filePath)) {
477
+ const fileDescriptor = this.getFileDescriptor(key);
478
+ result.push(fileDescriptor);
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 = node_path.default.resolve(this._cwd, sanitizedPath);
494
+ if (this._restrictAccessToCwd) {
495
+ const normalizedResolved = node_path.default.normalize(resolved);
496
+ const normalizedCwd = node_path.default.normalize(this._cwd);
497
+ if (!(normalizedResolved === normalizedCwd || normalizedResolved.startsWith(normalizedCwd + node_path.default.sep))) throw new Error(`Path traversal attempt blocked: "${filePath}" resolves outside of working directory "${this._cwd}"`);
498
+ }
499
+ return resolved;
500
+ }
501
+ return filePath;
502
+ }
503
+ /**
504
+ * Get the Absolute Path with a custom working directory. If it is already absolute it will return the path as is.
505
+ * When restrictAccessToCwd is enabled, ensures the resolved path stays within the provided cwd boundaries.
506
+ * @method getAbsolutePathWithCwd
507
+ * @param filePath - The file path to get the absolute path for
508
+ * @param cwd - The custom working directory to resolve relative paths from
509
+ * @returns {string}
510
+ * @throws {Error} When restrictAccessToCwd is true and path would resolve outside the provided cwd
511
+ */
512
+ getAbsolutePathWithCwd(filePath, cwd) {
513
+ if (this.isRelativePath(filePath)) {
514
+ const sanitizedPath = filePath.replace(/\0/g, "");
515
+ const resolved = node_path.default.resolve(cwd, sanitizedPath);
516
+ if (this._restrictAccessToCwd) {
517
+ const normalizedResolved = node_path.default.normalize(resolved);
518
+ const normalizedCwd = node_path.default.normalize(cwd);
519
+ if (!(normalizedResolved === normalizedCwd || normalizedResolved.startsWith(normalizedCwd + node_path.default.sep))) throw new Error(`Path traversal attempt blocked: "${filePath}" resolves outside of working directory "${cwd}"`);
520
+ }
521
+ return resolved;
522
+ }
523
+ return filePath;
524
+ }
525
+ /**
526
+ * Rename cache keys that start with a given path prefix.
527
+ * @method renameCacheKeys
528
+ * @param oldPath - The old path prefix to rename
529
+ * @param newPath - The new path prefix to rename to
530
+ */
531
+ renameCacheKeys(oldPath, newPath) {
532
+ const keys = this._cache.keys();
533
+ for (const key of keys)
534
+ /* v8 ignore next -- @preserve */
535
+ if (key.startsWith(oldPath)) {
536
+ const newKey = key.replace(oldPath, newPath);
537
+ const meta = this._cache.getKey(key);
538
+ this._cache.removeKey(key);
539
+ this._cache.setKey(newKey, meta);
540
+ if (this._originalMeta.has(key)) {
541
+ this._originalMeta.set(newKey, this._originalMeta.get(key));
542
+ this._originalMeta.delete(key);
543
+ }
544
+ }
545
+ }
552
546
  };
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 */
547
+ //#endregion
548
+ exports.FileEntryCache = FileEntryCache;
549
+ exports.create = create;
550
+ exports.createFromFile = createFromFile;
551
+ exports.default = FileEntryDefault;