cspell-io 8.2.1 → 8.2.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.
@@ -1,5 +1,5 @@
1
1
  import type { CSpellIO } from './CSpellIO.js';
2
- import { type DirEntry, type Disposable, type FileReference, type FileResource, type Stats } from './models/index.js';
2
+ import type { BufferEncoding, DirEntry, Disposable, FileReference, FileResource, Stats, TextFileResource } from './models/index.js';
3
3
  type UrlOrReference = URL | FileReference;
4
4
  type NextProvider = (url: URL) => VProviderFileSystem | undefined;
5
5
  export interface VirtualFS extends Disposable {
@@ -16,6 +16,11 @@ export interface VirtualFS extends Disposable {
16
16
  * Clear the cache of file systems.
17
17
  */
18
18
  reset(): void;
19
+ /**
20
+ * Indicates that logging has been enabled.
21
+ */
22
+ loggingEnabled: boolean;
23
+ enableLogging(value?: boolean): void;
19
24
  }
20
25
  export declare enum FSCapabilityFlags {
21
26
  None = 0,
@@ -30,22 +35,54 @@ export declare enum FSCapabilityFlags {
30
35
  interface FileSystemProviderInfo {
31
36
  name: string;
32
37
  }
33
- interface FileSystemBase {
34
- readFile(url: UrlOrReference): Promise<FileResource>;
38
+ export interface VFileSystem {
39
+ /**
40
+ * Read a file.
41
+ * @param url - URL to read
42
+ * @param encoding - optional encoding
43
+ * @returns A FileResource, the content will not be decoded. Use `.getText()` to get the decoded text.
44
+ */
45
+ readFile(url: UrlOrReference, encoding?: BufferEncoding): Promise<TextFileResource>;
46
+ /**
47
+ * Write a file
48
+ * @param file - the file to write
49
+ */
35
50
  writeFile(file: FileResource): Promise<FileReference>;
36
51
  /**
37
- * Information about the provider.
38
- * It is up to the provider to define what information is available.
52
+ * Get the stats for a url.
53
+ * @param url - Url to fetch stats for.
39
54
  */
40
- providerInfo: FileSystemProviderInfo;
41
- }
42
- export interface VFileSystem extends FileSystemBase {
43
55
  stat(url: UrlOrReference): Promise<VfsStat>;
56
+ /**
57
+ * Read the directory entries for a url.
58
+ * The url should end with `/` to indicate a directory.
59
+ * @param url - the url to read the directory entries for.
60
+ */
44
61
  readDirectory(url: URL): Promise<VfsDirEntry[]>;
62
+ /**
63
+ * Get the capabilities for a URL.
64
+ * The capabilities can be more restrictive than the general capabilities of the provider.
65
+ * @param url - the url to try
66
+ */
45
67
  getCapabilities(url: URL): FSCapabilities;
68
+ /**
69
+ * Information about the provider.
70
+ * It is up to the provider to define what information is available.
71
+ */
72
+ providerInfo: FileSystemProviderInfo;
73
+ /**
74
+ * Indicates that a provider was found for the url.
75
+ */
46
76
  hasProvider: boolean;
47
77
  }
48
- export interface VProviderFileSystem extends FileSystemBase, Disposable {
78
+ export interface VProviderFileSystem extends Disposable {
79
+ readFile(url: UrlOrReference): Promise<FileResource>;
80
+ writeFile(file: FileResource): Promise<FileReference>;
81
+ /**
82
+ * Information about the provider.
83
+ * It is up to the provider to define what information is available.
84
+ */
85
+ providerInfo: FileSystemProviderInfo;
49
86
  stat(url: UrlOrReference): Stats | Promise<Stats>;
50
87
  readDirectory(url: URL): Promise<DirEntry[]>;
51
88
  /**
@@ -97,11 +134,13 @@ export interface VfsStat extends Stats {
97
134
  isDirectory(): boolean;
98
135
  isFile(): boolean;
99
136
  isUnknown(): boolean;
137
+ isSymbolicLink(): boolean;
100
138
  }
101
139
  export interface VfsDirEntry extends DirEntry {
102
140
  isDirectory(): boolean;
103
141
  isFile(): boolean;
104
142
  isUnknown(): boolean;
143
+ isSymbolicLink(): boolean;
105
144
  }
106
145
  export {};
107
146
  //# sourceMappingURL=VirtualFS.d.ts.map
@@ -1,6 +1,7 @@
1
- import { urlOrReferenceToUrl } from './common/index.js';
1
+ import { createTextFileResource, urlOrReferenceToUrl } from './common/index.js';
2
2
  import { getDefaultCSpellIO } from './CSpellIONode.js';
3
- import { FileType, } from './models/index.js';
3
+ import { FileType } from './models/index.js';
4
+ const debug = false;
4
5
  export var FSCapabilityFlags;
5
6
  (function (FSCapabilityFlags) {
6
7
  FSCapabilityFlags[FSCapabilityFlags["None"] = 0] = "None";
@@ -17,9 +18,22 @@ class CVirtualFS {
17
18
  cachedFs = new Map();
18
19
  revCacheFs = new Map();
19
20
  fs;
21
+ loggingEnabled = debug;
20
22
  constructor() {
21
23
  this.fs = fsPassThrough((url) => this._getFS(url));
22
24
  }
25
+ enableLogging(value) {
26
+ this.loggingEnabled = value ?? true;
27
+ }
28
+ log = console.log;
29
+ logEvent = (event) => {
30
+ if (this.loggingEnabled) {
31
+ const id = event.traceID.toFixed(13).replace(/\d{4}(?=\d)/g, '$&.');
32
+ const msg = event.message ? `\n\t\t${event.message}` : '';
33
+ const method = rPad(`${event.method}-${event.event}`, 16);
34
+ this.log(`${method} ID:${id} ts:${event.ts.toFixed(13)} ${chopUrl(event.url)}${msg}`);
35
+ }
36
+ };
23
37
  registerFileSystemProvider(...providers) {
24
38
  providers.forEach((provider) => this.providers.add(provider));
25
39
  this.reset();
@@ -67,7 +81,7 @@ class CVirtualFS {
67
81
  for (const provider of this.providers) {
68
82
  next = fnNext(provider, next);
69
83
  }
70
- const fs = new WrappedProviderFs(next(url));
84
+ const fs = new WrappedProviderFs(next(url), this.logEvent);
71
85
  this.cachedFs.set(key, fs);
72
86
  return fs;
73
87
  }
@@ -211,57 +225,89 @@ export function fsCapabilities(flags) {
211
225
  }
212
226
  class WrappedProviderFs {
213
227
  fs;
228
+ eventLogger;
214
229
  hasProvider;
215
230
  capabilities;
216
231
  providerInfo;
217
232
  _capabilities;
218
- constructor(fs) {
233
+ constructor(fs, eventLogger) {
219
234
  this.fs = fs;
235
+ this.eventLogger = eventLogger;
220
236
  this.hasProvider = !!fs;
221
237
  this.capabilities = fs?.capabilities || FSCapabilityFlags.None;
222
238
  this._capabilities = fsCapabilities(this.capabilities);
223
239
  this.providerInfo = fs?.providerInfo || { name: 'unknown' };
224
240
  }
241
+ logEvent(method, event, traceID, url, message) {
242
+ this.eventLogger({ method, event, url, traceID, ts: performance.now(), message });
243
+ }
225
244
  getCapabilities(url) {
226
245
  if (this.fs?.getCapabilities)
227
246
  return this.fs.getCapabilities(url);
228
247
  return this._capabilities;
229
248
  }
230
- async stat(url) {
249
+ async stat(urlRef) {
250
+ const traceID = performance.now();
251
+ const url = urlOrReferenceToUrl(urlRef);
252
+ this.logEvent('stat', 'start', traceID, url);
231
253
  try {
232
- checkCapabilityOrThrow(this.fs, this.capabilities, FSCapabilityFlags.Stat, 'stat', urlOrReferenceToUrl(url));
233
- return new CVfsStat(await this.fs.stat(url));
254
+ checkCapabilityOrThrow(this.fs, this.capabilities, FSCapabilityFlags.Stat, 'stat', url);
255
+ return new CVfsStat(await this.fs.stat(urlRef));
234
256
  }
235
257
  catch (e) {
258
+ this.logEvent('stat', 'error', traceID, url, e instanceof Error ? e.message : '');
236
259
  throw wrapError(e);
237
260
  }
261
+ finally {
262
+ this.logEvent('stat', 'end', traceID, url);
263
+ }
238
264
  }
239
- async readFile(url) {
265
+ async readFile(urlRef, encoding) {
266
+ const traceID = performance.now();
267
+ const url = urlOrReferenceToUrl(urlRef);
268
+ this.logEvent('readFile', 'start', traceID, url);
240
269
  try {
241
- checkCapabilityOrThrow(this.fs, this.capabilities, FSCapabilityFlags.Read, 'readFile', urlOrReferenceToUrl(url));
242
- return await this.fs.readFile(url);
270
+ checkCapabilityOrThrow(this.fs, this.capabilities, FSCapabilityFlags.Read, 'readFile', url);
271
+ return createTextFileResource(await this.fs.readFile(urlRef), encoding);
243
272
  }
244
273
  catch (e) {
274
+ this.logEvent('readFile', 'error', traceID, url, e instanceof Error ? e.message : '');
245
275
  throw wrapError(e);
246
276
  }
277
+ finally {
278
+ this.logEvent('readFile', 'end', traceID, url);
279
+ }
247
280
  }
248
281
  async readDirectory(url) {
282
+ const traceID = performance.now();
283
+ this.logEvent('readDir', 'start', traceID, url);
249
284
  try {
250
285
  checkCapabilityOrThrow(this.fs, this.capabilities, FSCapabilityFlags.ReadDir, 'readDirectory', url);
251
286
  return (await this.fs.readDirectory(url)).map((e) => new CVfsDirEntry(e));
252
287
  }
253
288
  catch (e) {
289
+ this.logEvent('readDir', 'error', traceID, url, e instanceof Error ? e.message : '');
254
290
  throw wrapError(e);
255
291
  }
292
+ finally {
293
+ this.logEvent('readDir', 'end', traceID, url);
294
+ }
256
295
  }
257
296
  async writeFile(file) {
297
+ const traceID = performance.now();
298
+ const url = file.url;
299
+ this.logEvent('writeFile', 'start', traceID, url);
258
300
  try {
259
301
  checkCapabilityOrThrow(this.fs, this.capabilities, FSCapabilityFlags.Write, 'writeFile', file.url);
260
302
  return await this.fs.writeFile(file);
261
303
  }
262
304
  catch (e) {
305
+ this.logEvent('writeFile', 'error', traceID, url, e instanceof Error ? e.message : '');
263
306
  throw wrapError(e);
264
307
  }
308
+ finally {
309
+ this.logEvent('writeFile', 'end', traceID, url);
310
+ }
265
311
  }
266
312
  static disposeOf(fs) {
267
313
  fs instanceof WrappedProviderFs && fs.fs?.dispose();
@@ -286,6 +332,9 @@ class CFileType {
286
332
  isUnknown() {
287
333
  return !this.fileType;
288
334
  }
335
+ isSymbolicLink() {
336
+ return !!(this.fileType & FileType.SymbolicLink);
337
+ }
289
338
  }
290
339
  class CVfsStat extends CFileType {
291
340
  stat;
@@ -322,5 +371,27 @@ class CVfsDirEntry extends CFileType {
322
371
  this._url = new URL(this.entry.name, this.entry.dir);
323
372
  return this._url;
324
373
  }
374
+ toJSON() {
375
+ return {
376
+ name: this.name,
377
+ dir: this.dir,
378
+ fileType: this.fileType,
379
+ };
380
+ }
381
+ }
382
+ function chopUrl(url) {
383
+ if (!url)
384
+ return '';
385
+ const href = url.href;
386
+ const parts = href.split('/');
387
+ const n = parts.indexOf('node_modules');
388
+ if (n > 0) {
389
+ const tail = parts.slice(Math.max(parts.length - 3, n + 1));
390
+ return parts.slice(0, n + 1).join('/') + '/…/' + tail.join('/');
391
+ }
392
+ return href;
393
+ }
394
+ function rPad(str, len, ch = ' ') {
395
+ return str + ch.repeat(Math.max(0, len - str.length));
325
396
  }
326
397
  //# sourceMappingURL=VirtualFS.js.map
@@ -9,7 +9,7 @@ export declare class CFileResource implements TextFileResource {
9
9
  private _gz?;
10
10
  constructor(url: URL, content: string | ArrayBufferView, encoding: BufferEncoding | undefined, baseFilename: string | undefined, gz: boolean | undefined);
11
11
  get gz(): boolean;
12
- getText(): string;
12
+ getText(encoding?: BufferEncoding): string;
13
13
  toJson(): {
14
14
  url: string;
15
15
  content: string;
@@ -23,6 +23,6 @@ export declare class CFileResource implements TextFileResource {
23
23
  static from(fileReference: FileReference | URL, content: string | ArrayBufferView): CFileResource;
24
24
  static from(url: URL, content: string | ArrayBufferView, encoding?: BufferEncoding | undefined, baseFilename?: string | undefined, gz?: boolean): CFileResource;
25
25
  }
26
- export declare function fromFileResource(fileResource: FileResource): TextFileResource;
26
+ export declare function fromFileResource(fileResource: FileResource, encoding?: BufferEncoding): TextFileResource;
27
27
  export declare function renameFileResource(fileResource: FileResource, url: URL): FileResource;
28
28
  //# sourceMappingURL=CFileResource.d.ts.map
@@ -23,10 +23,10 @@ export class CFileResource {
23
23
  return false;
24
24
  return isGZipped(this.content);
25
25
  }
26
- getText() {
26
+ getText(encoding) {
27
27
  if (this._text !== undefined)
28
28
  return this._text;
29
- const text = typeof this.content === 'string' ? this.content : decode(this.content, this.encoding);
29
+ const text = typeof this.content === 'string' ? this.content : decode(this.content, encoding ?? this.encoding);
30
30
  this._text = text;
31
31
  return text;
32
32
  }
@@ -63,10 +63,10 @@ export class CFileResource {
63
63
  return new CFileResource(fileResource.url, fileResource.content, fileResource.encoding, fileResource.baseFilename, fileResource.gz);
64
64
  }
65
65
  }
66
- export function fromFileResource(fileResource) {
67
- return CFileResource.from(fileResource);
66
+ export function fromFileResource(fileResource, encoding) {
67
+ return CFileResource.from(encoding ? { ...fileResource, encoding } : fileResource);
68
68
  }
69
69
  export function renameFileResource(fileResource, url) {
70
- return CFileResource.from(url, fileResource.content, fileResource.encoding, fileResource.baseFilename, fileResource.gz);
70
+ return CFileResource.from({ ...fileResource, url });
71
71
  }
72
72
  //# sourceMappingURL=CFileResource.js.map
@@ -1,4 +1,5 @@
1
1
  export { CFileReference, renameFileReference } from './CFileReference.js';
2
2
  export { CFileResource, fromFileResource as createTextFileResource, renameFileResource } from './CFileResource.js';
3
+ export { compareStats } from './stat.js';
3
4
  export { urlOrReferenceToUrl } from './urlOrReferenceToUrl.js';
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1,4 +1,5 @@
1
1
  export { CFileReference, renameFileReference } from './CFileReference.js';
2
2
  export { CFileResource, fromFileResource as createTextFileResource, renameFileResource } from './CFileResource.js';
3
+ export { compareStats } from './stat.js';
3
4
  export { urlOrReferenceToUrl } from './urlOrReferenceToUrl.js';
4
5
  //# sourceMappingURL=index.js.map
@@ -208,6 +208,7 @@ function direntToDirEntry(dir, dirent) {
208
208
  };
209
209
  }
210
210
  function toFileType(statLike) {
211
- return statLike.isFile() ? FileType.File : statLike.isDirectory() ? FileType.Directory : FileType.Unknown;
211
+ const t = statLike.isFile() ? FileType.File : statLike.isDirectory() ? FileType.Directory : FileType.Unknown;
212
+ return statLike.isSymbolicLink() ? t | FileType.SymbolicLink : t;
212
213
  }
213
214
  //# sourceMappingURL=file.js.map
@@ -1,6 +1,6 @@
1
1
  export { toArray as asyncIterableToArray } from './async/asyncIterable.js';
2
2
  export * from './common/index.js';
3
- export { createTextFileResource } from './common/index.js';
3
+ export { compareStats, createTextFileResource } from './common/index.js';
4
4
  export type { CSpellIO } from './CSpellIO.js';
5
5
  export { CSpellIONode, getDefaultCSpellIO } from './CSpellIONode.js';
6
6
  export { getStat, getStatSync, readFileText, readFileTextSync, writeToFile, writeToFileIterable, writeToFileIterableP, } from './file/index.js';
package/dist/esm/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export { toArray as asyncIterableToArray } from './async/asyncIterable.js';
2
2
  export * from './common/index.js';
3
- export { createTextFileResource } from './common/index.js';
3
+ export { compareStats, createTextFileResource } from './common/index.js';
4
4
  export { CSpellIONode, getDefaultCSpellIO } from './CSpellIONode.js';
5
5
  export { getStat, getStatSync, readFileText, readFileTextSync, writeToFile, writeToFileIterable, writeToFileIterableP, } from './file/index.js';
6
6
  export { FileType as VFileType } from './models/Stats.js';
@@ -27,7 +27,13 @@ export interface FileResource extends FileReference {
27
27
  readonly content: string | ArrayBufferView;
28
28
  }
29
29
  export interface TextFileResource extends FileResource {
30
- getText(): string;
30
+ /**
31
+ * Extract the text of the file.
32
+ * @param encoding - optional encoding to use when decoding the content.
33
+ * by default it uses the encoding of the file if one was specified, otherwise it uses `utf8`.
34
+ * If the content is a string, then the encoding is ignored.
35
+ */
36
+ getText(encoding?: BufferEncoding): string;
31
37
  }
32
38
  export type UrlOrFilename = string | URL;
33
39
  export type UrlOrReference = UrlOrFilename | FileReference;
@@ -31,7 +31,11 @@ export declare enum FileType {
31
31
  /**
32
32
  * A directory.
33
33
  */
34
- Directory = 2
34
+ Directory = 2,
35
+ /**
36
+ * A symbolic link.
37
+ */
38
+ SymbolicLink = 64
35
39
  }
36
40
  export interface DirEntry {
37
41
  name: string;
@@ -12,5 +12,9 @@ export var FileType;
12
12
  * A directory.
13
13
  */
14
14
  FileType[FileType["Directory"] = 2] = "Directory";
15
+ /**
16
+ * A symbolic link.
17
+ */
18
+ FileType[FileType["SymbolicLink"] = 64] = "SymbolicLink";
15
19
  })(FileType || (FileType = {}));
16
20
  //# sourceMappingURL=Stats.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cspell-io",
3
- "version": "8.2.1",
3
+ "version": "8.2.4",
4
4
  "description": "A library of useful I/O functions used across various cspell tools.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -51,7 +51,7 @@
51
51
  "typescript": "^5.3.3"
52
52
  },
53
53
  "dependencies": {
54
- "@cspell/cspell-service-bus": "8.2.1"
54
+ "@cspell/cspell-service-bus": "8.2.4"
55
55
  },
56
- "gitHead": "b0c889ee4068aa8a2447106c5c7f449debc85bdd"
56
+ "gitHead": "d3c5ff685b3aa2bf984f557d81380f2c994547e0"
57
57
  }