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