@monorepolint/utils 0.5.0-alpha.83 → 0.5.0-alpha.86

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/lib/AggregateTiming.d.ts +15 -0
  2. package/lib/AggregateTiming.d.ts.map +1 -0
  3. package/lib/AggregateTiming.js +70 -0
  4. package/lib/AggregateTiming.js.map +1 -0
  5. package/lib/CachingHost.d.ts +39 -0
  6. package/lib/CachingHost.d.ts.map +1 -0
  7. package/lib/CachingHost.js +366 -0
  8. package/lib/CachingHost.js.map +1 -0
  9. package/lib/Host.d.ts +38 -0
  10. package/lib/Host.d.ts.map +1 -0
  11. package/lib/Host.js +9 -0
  12. package/lib/Host.js.map +1 -0
  13. package/lib/SimpleHost.d.ts +35 -0
  14. package/lib/SimpleHost.d.ts.map +1 -0
  15. package/lib/SimpleHost.js +56 -0
  16. package/lib/SimpleHost.js.map +1 -0
  17. package/lib/Table.d.ts +53 -0
  18. package/lib/Table.d.ts.map +1 -0
  19. package/lib/Table.js +234 -0
  20. package/lib/Table.js.map +1 -0
  21. package/lib/Timing.d.ts +9 -0
  22. package/lib/Timing.d.ts.map +1 -0
  23. package/lib/Timing.js +57 -0
  24. package/lib/Timing.js.map +1 -0
  25. package/lib/__tests__/CachingHost.spec.d.ts +8 -0
  26. package/lib/__tests__/CachingHost.spec.d.ts.map +1 -0
  27. package/lib/__tests__/CachingHost.spec.js +178 -0
  28. package/lib/__tests__/CachingHost.spec.js.map +1 -0
  29. package/lib/findWorkspaceDir.d.ts +2 -1
  30. package/lib/findWorkspaceDir.d.ts.map +1 -1
  31. package/lib/findWorkspaceDir.js +4 -6
  32. package/lib/findWorkspaceDir.js.map +1 -1
  33. package/lib/getPackageNameToDir.d.ts +2 -1
  34. package/lib/getPackageNameToDir.d.ts.map +1 -1
  35. package/lib/getPackageNameToDir.js +4 -5
  36. package/lib/getPackageNameToDir.js.map +1 -1
  37. package/lib/getWorkspacePackageDirs.d.ts +2 -1
  38. package/lib/getWorkspacePackageDirs.d.ts.map +1 -1
  39. package/lib/getWorkspacePackageDirs.js +5 -6
  40. package/lib/getWorkspacePackageDirs.js.map +1 -1
  41. package/lib/index.d.ts +8 -2
  42. package/lib/index.d.ts.map +1 -1
  43. package/lib/index.js +15 -4
  44. package/lib/index.js.map +1 -1
  45. package/lib/matchesAnyGlob.d.ts +17 -0
  46. package/lib/matchesAnyGlob.d.ts.map +1 -0
  47. package/lib/matchesAnyGlob.js +131 -0
  48. package/lib/matchesAnyGlob.js.map +1 -0
  49. package/lib/mutateJson.d.ts +2 -1
  50. package/lib/mutateJson.d.ts.map +1 -1
  51. package/lib/mutateJson.js +3 -5
  52. package/lib/mutateJson.js.map +1 -1
  53. package/lib/nanosecondsToSanity.d.ts +8 -0
  54. package/lib/nanosecondsToSanity.d.ts.map +1 -0
  55. package/lib/nanosecondsToSanity.js +14 -0
  56. package/lib/nanosecondsToSanity.js.map +1 -0
  57. package/package.json +8 -6
  58. package/src/AggregateTiming.ts +71 -0
  59. package/src/CachingHost.ts +489 -0
  60. package/src/Host.ts +34 -0
  61. package/src/SimpleHost.ts +57 -0
  62. package/src/Table.ts +319 -0
  63. package/src/Timing.ts +55 -0
  64. package/src/__tests__/CachingHost.spec.ts +244 -0
  65. package/src/findWorkspaceDir.ts +5 -6
  66. package/src/getPackageNameToDir.ts +4 -4
  67. package/src/getWorkspacePackageDirs.ts +7 -3
  68. package/src/index.ts +8 -2
  69. package/src/matchesAnyGlob.ts +145 -0
  70. package/src/mutateJson.ts +4 -5
  71. package/src/nanosecondsToSanity.ts +10 -0
  72. package/tsconfig.tsbuildinfo +1 -4046
  73. package/lib/readJson.d.ts +0 -8
  74. package/lib/readJson.d.ts.map +0 -1
  75. package/lib/readJson.js +0 -16
  76. package/lib/readJson.js.map +0 -1
  77. package/lib/writeJson.d.ts +0 -8
  78. package/lib/writeJson.d.ts.map +0 -1
  79. package/lib/writeJson.js +0 -15
  80. package/lib/writeJson.js.map +0 -1
  81. package/src/readJson.ts +0 -13
  82. package/src/writeJson.ts +0 -12
@@ -0,0 +1,489 @@
1
+ /*!
2
+ * Copyright 2022 Palantir Technologies, Inc.
3
+ *
4
+ * Licensed under the MIT license. See LICENSE file in the project root for details.
5
+ *
6
+ */
7
+
8
+ import * as realFs from "fs";
9
+ import { Host } from "./Host";
10
+ import * as path from "path";
11
+
12
+ function assertNoTombstone(node: Node): asserts node is Node & { tombstone?: false } {
13
+ if (node.tombstone) {
14
+ throw new Error(`Unexpected tombstone ${JSON.stringify(node)}`);
15
+ }
16
+ }
17
+
18
+ function assertNotType<N extends Node, T extends Node["type"]>(
19
+ node: N,
20
+ type: T
21
+ ): asserts node is N & { type: Exclude<N["type"], T> } {
22
+ if (node.type === type) {
23
+ throw new Error(`Unexpected node type ${JSON.stringify(node)}`);
24
+ }
25
+ }
26
+
27
+ function assertType<N extends Node, T extends Node["type"]>(node: N, type: T): asserts node is N & { type: T } {
28
+ if (node.type !== type) {
29
+ throw new Error(`Unexpected node type ${JSON.stringify(node)}`);
30
+ }
31
+ }
32
+
33
+ function assertExists<T extends Node>(node: T | undefined): asserts node is T {
34
+ if (!node) {
35
+ throw new Error(`Expected node to exist`);
36
+ }
37
+ }
38
+
39
+ function assertHasParent(node: Node) {
40
+ if (!node.parent) {
41
+ throw new Error("Expected node to have a parent directory");
42
+ }
43
+ }
44
+
45
+ interface BaseNode<T extends string> {
46
+ type: T;
47
+ fullPath: string;
48
+ tombstone?: boolean;
49
+ parent?: DirNode | DirStubNode;
50
+ needsFlush: boolean;
51
+ }
52
+ interface DirNode extends BaseNode<"dir"> {
53
+ stub?: false;
54
+ tombstone?: false;
55
+ dir: Map<string, Node>;
56
+ }
57
+
58
+ interface DirStubNode extends BaseNode<"dir"> {
59
+ stub: true;
60
+ tombstone?: false;
61
+ dir: Map<string, Node>;
62
+ }
63
+
64
+ interface DirTombstoneNode extends BaseNode<"dir"> {
65
+ stub?: false;
66
+ tombstone: true;
67
+ dir: Map<string, Node>;
68
+ }
69
+
70
+ interface FileNode extends BaseNode<"file"> {
71
+ stub?: false;
72
+ tombstone?: false;
73
+ file: Buffer;
74
+ }
75
+
76
+ interface FileTombstoneNode extends BaseNode<"file"> {
77
+ stub?: false;
78
+ tombstone: true;
79
+ file?: never;
80
+ }
81
+
82
+ interface FileStubNode extends BaseNode<"file"> {
83
+ stub: true;
84
+ tombstone?: false;
85
+ file?: never;
86
+ }
87
+
88
+ interface SymlinkNode extends BaseNode<"symlink"> {
89
+ tombstone?: false;
90
+ symlink: string;
91
+ }
92
+
93
+ type Node = DirNode | FileNode | SymlinkNode | DirTombstoneNode | FileTombstoneNode | DirStubNode | FileStubNode;
94
+
95
+ export class CachingHost implements Host {
96
+ // We need many trees because of windows, key is the `root`
97
+ #trees = new Map<string, DirNode | DirStubNode>();
98
+
99
+ constructor(
100
+ private fs: Pick<
101
+ typeof realFs,
102
+ | "existsSync"
103
+ | "lstatSync"
104
+ | "mkdirSync"
105
+ | "promises"
106
+ | "readdirSync"
107
+ | "readFileSync"
108
+ | "readlinkSync"
109
+ | "realpathSync"
110
+ | "rmdirSync"
111
+ | "statSync"
112
+ | "unlinkSync"
113
+ | "writeFileSync"
114
+ > = realFs
115
+ ) {}
116
+
117
+ #replaceNode(
118
+ node: FileNode | FileStubNode | SymlinkNode,
119
+ newNode: Omit<FileTombstoneNode, "fullPath" | "parent">
120
+ ): FileTombstoneNode;
121
+ #replaceNode(
122
+ node: FileNode | FileStubNode | FileTombstoneNode,
123
+ newNode: Omit<FileNode, "fullPath" | "parent">
124
+ ): FileNode;
125
+ #replaceNode(
126
+ node: DirTombstoneNode | DirStubNode,
127
+ newNode: Omit<DirNode, "fullPath" | "parent" | "dir">
128
+ ): DirStubNode;
129
+ #replaceNode(node: DirNode, newNode: Omit<DirTombstoneNode, "fullPath" | "parent" | "dir">): DirTombstoneNode;
130
+ #replaceNode(node: Node, partialNewNode: Omit<Node, "fullPath" | "parent">): Node {
131
+ if (!node.parent) throw new Error("Cannot replace root node");
132
+ const newNode = {
133
+ ...partialNewNode,
134
+ fullPath: node.fullPath,
135
+ parent: node.parent,
136
+ dir: (node as any).dir,
137
+ } as Node;
138
+ node.parent.dir.set(path.basename(node.fullPath), newNode);
139
+ return newNode;
140
+ }
141
+
142
+ #unstubDirectory(node: DirNode | DirStubNode): asserts node is DirNode {
143
+ // So the rules for our stub dirs. We assume the things in the map are authority but
144
+ // for things not in the map, the real FS is the authority
145
+ for (const child of this.fs.readdirSync(node.fullPath)) {
146
+ // just makign this call will populate the structure but its a little expensive.
147
+ // TODO: make an unknown stub
148
+ this.#getNode(path.join(node.fullPath, child));
149
+ }
150
+ node.stub = false;
151
+ }
152
+
153
+ /**
154
+ * Assumes no parent -> path is root
155
+ *
156
+ * Throws if the path doesnt exist!
157
+ */
158
+ #stubify(filePath: string, parent: undefined): DirStubNode;
159
+ #stubify(filePath: string, parent: DirNode | DirStubNode | undefined): DirStubNode | SymlinkNode | FileStubNode;
160
+ #stubify(
161
+ filePath: string,
162
+ parent: DirNode | DirStubNode | undefined
163
+ ): typeof parent extends undefined ? DirNode | DirStubNode : DirNode | DirStubNode | SymlinkNode | FileStubNode {
164
+ const canonicalPath = path.resolve(filePath);
165
+
166
+ if (!parent && canonicalPath !== path.parse(canonicalPath).root) {
167
+ throw new Error(`parent can only be null if path is root. Instead got: ${canonicalPath}`);
168
+ }
169
+ const stat = this.fs.lstatSync(canonicalPath); // may throw
170
+
171
+ let node: SymlinkNode | FileStubNode | DirStubNode;
172
+
173
+ if (stat.isDirectory()) {
174
+ node = {
175
+ fullPath: canonicalPath,
176
+ type: "dir",
177
+ stub: true,
178
+ dir: new Map(),
179
+ parent,
180
+ needsFlush: false,
181
+ };
182
+ } else if (stat.isSymbolicLink()) {
183
+ node = {
184
+ fullPath: canonicalPath,
185
+ type: "symlink",
186
+ symlink: this.fs.readlinkSync(canonicalPath),
187
+ parent,
188
+ needsFlush: false,
189
+ };
190
+ } else if (stat.isFile()) {
191
+ node = {
192
+ fullPath: canonicalPath,
193
+ type: "file",
194
+ stub: true,
195
+ parent,
196
+ needsFlush: false,
197
+ };
198
+ } else {
199
+ throw new Error(`what is not a file nor symlink nor directory? nothing we care about: ${canonicalPath}`);
200
+ }
201
+
202
+ if (!parent && node.type === "dir") {
203
+ this.#trees.set(canonicalPath, node);
204
+ return node;
205
+ } else if (parent) {
206
+ parent.dir.set(path.basename(canonicalPath), node);
207
+ } else {
208
+ throw new Error(`root can only be a dir, got ${JSON.stringify(node)} for path: ${canonicalPath}`);
209
+ }
210
+ return node;
211
+ }
212
+
213
+ /**
214
+ * Note: may return the node itself!
215
+ * You should check the `fullPath` of the result.
216
+ */
217
+ #getNearestAncestorNode(filePath: string) {
218
+ const canonicalPath = path.resolve(filePath);
219
+ const { root } = path.parse(canonicalPath);
220
+ const parts = [];
221
+
222
+ let maybePath = canonicalPath;
223
+ while (maybePath !== root) {
224
+ parts.unshift(path.basename(maybePath));
225
+ maybePath = path.dirname(maybePath);
226
+ }
227
+
228
+ let curPath = root;
229
+ let curNode: Node = this.#trees.get(root) ?? this.#stubify(curPath, undefined); // its okay to throw if there is no root
230
+ try {
231
+ for (const part of parts) {
232
+ assertNoTombstone(curNode);
233
+ assertNotType(curNode, "file");
234
+ if (curNode.type === "symlink") {
235
+ const linkedNode = this.#getNodeResolvingSymlinks(path.resolve(path.dirname(curPath), curNode.symlink));
236
+ assertExists(linkedNode);
237
+ assertNoTombstone(linkedNode);
238
+ assertType(linkedNode, "dir");
239
+ curNode = linkedNode;
240
+ }
241
+ curNode = curNode.dir.get(part) ?? this.#stubify(path.join(curNode.fullPath, part), curNode as any);
242
+ curPath = path.join(curPath, part);
243
+ }
244
+ } catch (e) {
245
+ // This error is expected when things done exist.
246
+ // console.log(`Got EXPECTED error when trying to getNearestAncestorNode(${canonicalPath}): `, (e as any).message);
247
+ }
248
+ return { pathWithSymlinks: curPath, node: curNode };
249
+ }
250
+
251
+ #getNode(filePath: string) {
252
+ const canonicalPath = path.resolve(filePath);
253
+ const { pathWithSymlinks, node } = this.#getNearestAncestorNode(canonicalPath);
254
+ if (pathWithSymlinks === canonicalPath) {
255
+ return node;
256
+ }
257
+ return undefined;
258
+ }
259
+
260
+ #getNodeResolvingSymlinks(filePath: string, follows: number = 100): Exclude<Node, SymlinkNode> | undefined {
261
+ const node = this.#getNode(filePath); // canonicalizes for us
262
+ if (!node || node.type !== "symlink") return node;
263
+ // this is a really poor mans way of doing this. but who has 100's of symlinks hanging around?
264
+ if (follows === 0) throw new Error("Exhausted symlink follows");
265
+
266
+ return this.#getNodeResolvingSymlinks(node.symlink, follows--);
267
+ }
268
+
269
+ mkdir(filePath: string, opts: { recursive: boolean } = { recursive: false }): void {
270
+ const canonicalPath = path.resolve(filePath);
271
+ const { node, pathWithSymlinks } = this.#getNearestAncestorNode(canonicalPath);
272
+ if (filePath === pathWithSymlinks) {
273
+ assertType(node, "dir");
274
+ assertHasParent(node);
275
+ if (!node.tombstone) return; // already done
276
+ } else if (path.dirname(filePath) === pathWithSymlinks) {
277
+ assertType(node, "dir");
278
+ assertNoTombstone(node);
279
+ node.dir.set(path.basename(filePath), {
280
+ type: "dir",
281
+ fullPath: filePath,
282
+ parent: node,
283
+ dir: new Map(),
284
+ needsFlush: true,
285
+ });
286
+ return;
287
+ }
288
+
289
+ if (!opts.recursive && path.dirname(canonicalPath) !== pathWithSymlinks) {
290
+ throw new Error("no such file or directory");
291
+ }
292
+
293
+ const rootPath = pathWithSymlinks;
294
+ let maybePath = canonicalPath;
295
+ const toMake: string[] = [];
296
+ while (maybePath !== rootPath) {
297
+ toMake.unshift(path.resolve(node.fullPath, path.relative(rootPath, maybePath)));
298
+ maybePath = path.dirname(maybePath);
299
+ }
300
+
301
+ for (const dirToMake of toMake) {
302
+ this.mkdir(dirToMake);
303
+ }
304
+ }
305
+
306
+ rmdir(directoryPath: string): void {
307
+ const node = this.#getNode(directoryPath);
308
+ if (!node || node.tombstone) {
309
+ return; // this isnt how FS usually work but its what we are doing
310
+ }
311
+ assertType(node, "dir");
312
+ if (node.stub) {
313
+ this.#unstubDirectory(node);
314
+ }
315
+
316
+ if (node.dir.size === 0) {
317
+ this.#replaceNode(node, {
318
+ type: "dir",
319
+ tombstone: true,
320
+ needsFlush: true,
321
+ });
322
+ } else {
323
+ throw new Error("directory not empty");
324
+ }
325
+ }
326
+
327
+ exists(filePath: string): boolean {
328
+ const node = this.#getNode(filePath); // canonicalizes for us
329
+ return !!node && !node.tombstone;
330
+ }
331
+
332
+ readFile(filePath: string, opts?: undefined): Buffer;
333
+ readFile(filePath: string, opts: { encoding: BufferEncoding }): string;
334
+ readFile(filePath: string, opts: { asJson: true }): object;
335
+ readFile(
336
+ filePath: string,
337
+ opts: undefined | { encoding: BufferEncoding; asJson?: false } | { encoding?: never; asJson: true }
338
+ ) {
339
+ let node = this.#getNodeResolvingSymlinks(filePath); // canonicalizes for us
340
+
341
+ if (!node) {
342
+ return undefined;
343
+ }
344
+ assertNotType(node, "dir");
345
+ assertNoTombstone(node);
346
+
347
+ if (node.stub) {
348
+ node = this.#replaceNode(node, {
349
+ type: "file",
350
+ file: this.fs.readFileSync(filePath),
351
+ needsFlush: false,
352
+ });
353
+ }
354
+
355
+ if (!opts) {
356
+ return Buffer.from(node.file);
357
+ } else if (opts.asJson) {
358
+ return JSON.parse(node.file.toString("utf-8"));
359
+ } else {
360
+ return node.file.toString(opts.encoding);
361
+ }
362
+ }
363
+
364
+ writeFile(filePath: string, buffer: Buffer): void;
365
+ writeFile(filePath: string, body: string, opts: { encoding: BufferEncoding }): void;
366
+ writeFile(filePath: string, body: string, opts: { encoding: BufferEncoding }): void;
367
+ writeFile(filePath: string, body: string | Buffer, opts?: { encoding: BufferEncoding }) {
368
+ const fileContentsAsBuffer = typeof body === "string" ? Buffer.from(body, opts?.encoding) : Buffer.from(body);
369
+
370
+ const canonicalPath = path.resolve(filePath);
371
+ const existingNode = this.#getNodeResolvingSymlinks(canonicalPath);
372
+ if (existingNode) {
373
+ if (existingNode.type === "dir") {
374
+ throw new Error("cant write file to a dir");
375
+ }
376
+ this.#replaceNode(existingNode, {
377
+ file: fileContentsAsBuffer,
378
+ type: "file",
379
+ needsFlush: true,
380
+ });
381
+ return;
382
+ }
383
+
384
+ const maybeDirNode = this.#getNodeResolvingSymlinks(path.dirname(canonicalPath));
385
+ assertExists(maybeDirNode);
386
+ assertType(maybeDirNode, "dir");
387
+ assertNoTombstone(maybeDirNode);
388
+
389
+ maybeDirNode.dir.set(path.basename(canonicalPath), {
390
+ type: "file",
391
+ fullPath: canonicalPath,
392
+ parent: maybeDirNode,
393
+ file: fileContentsAsBuffer,
394
+ needsFlush: true,
395
+ });
396
+ }
397
+
398
+ deleteFile(filePath: string) {
399
+ const canonicalPath = path.resolve(filePath);
400
+ const node = this.#getNode(canonicalPath);
401
+ if (!node || (node.type === "file" && node.tombstone === true)) return;
402
+ assertNotType(node, "dir");
403
+ this.#replaceNode(node, {
404
+ type: "file",
405
+ tombstone: true,
406
+ needsFlush: true,
407
+ });
408
+ }
409
+
410
+ readJson(filePath: string) {
411
+ return this.readFile(filePath, { asJson: true });
412
+ }
413
+
414
+ writeJson(filePath: string, o: object): void {
415
+ return this.writeFile(filePath, JSON.stringify(o, undefined, 2) + "\n", {
416
+ encoding: "utf-8",
417
+ });
418
+ }
419
+
420
+ async #flushFileNode(node: FileNode | FileStubNode | FileTombstoneNode): Promise<unknown> {
421
+ // FIXME all tombstones need a flush, so we can get rid of needsFlush for them
422
+ if (node.tombstone) {
423
+ try {
424
+ await this.fs.promises.access(node.fullPath);
425
+ return this.fs.promises.unlink(node.fullPath);
426
+ } catch (e) {
427
+ // should only throw if file doesnt exist which is no op
428
+ return;
429
+ }
430
+ } else if (node.stub === true || node.needsFlush === false) {
431
+ return;
432
+ } else {
433
+ // we dont do things with file stubs
434
+ return this.fs.promises.writeFile(node.fullPath, node.file);
435
+ }
436
+ }
437
+
438
+ async #flushSymlinkNode(node: SymlinkNode) {
439
+ if (!node.needsFlush) return;
440
+ try {
441
+ const linkValue = await this.fs.promises.readlink(node.fullPath);
442
+ if (linkValue === node.symlink) {
443
+ return;
444
+ }
445
+ } catch (e) {
446
+ // expected when the link doesnt exist
447
+ }
448
+ return this.fs.promises.symlink(node.symlink, node.fullPath);
449
+ }
450
+
451
+ async #flushDirNode(node: DirNode | DirStubNode | DirTombstoneNode): Promise<unknown> {
452
+ if (!node.tombstone && node.needsFlush) {
453
+ try {
454
+ await this.fs.promises.access(node.fullPath); // throws if the file doesnt exist
455
+ } catch (e) {
456
+ await this.fs.promises.mkdir(node.fullPath); // throws if it does :(
457
+ }
458
+ }
459
+
460
+ const promises: Promise<unknown>[] = [];
461
+ for (const child of node.dir.values()) {
462
+ if (node.tombstone && !child.tombstone) {
463
+ throw new Error("Unexpected failure during sanity check. A non-deleted child is on a deleted dir");
464
+ }
465
+ if (child.type === "dir") {
466
+ promises.push(this.#flushDirNode(child));
467
+ } else if (child.type === "file") {
468
+ promises.push(this.#flushFileNode(child));
469
+ } else if (child.type === "symlink") {
470
+ promises.push(this.#flushSymlinkNode(child));
471
+ } else {
472
+ throw new Error("should never happen");
473
+ }
474
+ }
475
+ await Promise.all(promises);
476
+ if (node.tombstone) {
477
+ return this.fs.promises.rmdir(node.fullPath);
478
+ }
479
+ return;
480
+ }
481
+
482
+ flush() {
483
+ const promises: Promise<any>[] = [];
484
+ for (const rootNode of this.#trees.values()) {
485
+ promises.push(this.#flushDirNode(rootNode));
486
+ }
487
+ return Promise.all(promises);
488
+ }
489
+ }
package/src/Host.ts ADDED
@@ -0,0 +1,34 @@
1
+ /*!
2
+ * Copyright 2022 Palantir Technologies, Inc.
3
+ *
4
+ * Licensed under the MIT license. See LICENSE file in the project root for details.
5
+ *
6
+ */
7
+
8
+ export interface Host {
9
+ /**
10
+ * Reads and parses a json file.
11
+ */
12
+ readJson(filename: string): any;
13
+
14
+ /**
15
+ * Writes a json file.
16
+ * @param path path to write
17
+ * @param o object to write
18
+ */
19
+ writeJson(path: string, o: object): void;
20
+
21
+ writeFile(path: string, buffer: Buffer): void;
22
+ writeFile(path: string, body: string, opts: { encoding: BufferEncoding }): void;
23
+
24
+ readFile(path: string, opts?: undefined): Buffer;
25
+ readFile(path: string, opts: { encoding: BufferEncoding }): string;
26
+ readFile(path: string, opts: { asJson: true }): object;
27
+
28
+ exists(path: string): boolean;
29
+ mkdir(directoryPath: string, opts?: { recursive: boolean }): void;
30
+ rmdir(directoryPath: string): void;
31
+
32
+ deleteFile(path: string): void;
33
+ flush(): Promise<unknown>;
34
+ }
@@ -0,0 +1,57 @@
1
+ /*!
2
+ * Copyright 2022 Palantir Technologies, Inc.
3
+ *
4
+ * Licensed under the MIT license. See LICENSE file in the project root for details.
5
+ *
6
+ */
7
+
8
+ import * as realFs from "fs";
9
+
10
+ import { Host } from "./Host";
11
+
12
+ export class SimpleHost implements Host {
13
+ constructor(private fs: typeof realFs = realFs) {}
14
+ mkdir(directoryPath: string, opts?: { recursive: boolean }): void {
15
+ this.fs.mkdirSync(directoryPath, { recursive: opts?.recursive ?? false });
16
+ }
17
+ rmdir(directoryPath: string): void {
18
+ this.fs.rmdirSync(directoryPath);
19
+ }
20
+
21
+ exists(path: string): boolean {
22
+ return this.fs.existsSync(path);
23
+ }
24
+
25
+ writeFile(path: string, buffer: Buffer): void;
26
+ writeFile(path: string, body: string, opts: { encoding: BufferEncoding }): void;
27
+ writeFile(path: any, body: any, opts?: { encoding: BufferEncoding }): any {
28
+ if (opts) {
29
+ this.fs.writeFileSync(path, body, { encoding: opts.encoding });
30
+ } else {
31
+ this.fs.writeFileSync(path, body);
32
+ }
33
+ }
34
+ readFile(path: string, opts?: undefined): Buffer;
35
+ readFile(path: string, opts: { encoding: BufferEncoding }): string;
36
+ readFile(path: string, opts: { asJson: true }): object;
37
+ readFile(path: any, opts?: { encoding?: BufferEncoding; asJson?: boolean }): string | object | Buffer {
38
+ if (opts?.asJson) {
39
+ return JSON.parse(this.fs.readFileSync(path, "utf-8"));
40
+ }
41
+ return this.fs.readFileSync(path, opts?.encoding);
42
+ }
43
+ deleteFile(path: string) {
44
+ this.fs.unlinkSync(path);
45
+ }
46
+ readJson(filename: string) {
47
+ const contents = this.fs.readFileSync(filename, "utf-8");
48
+ return JSON.parse(contents);
49
+ }
50
+ writeJson(path: string, o: object): void {
51
+ return this.fs.writeFileSync(path, JSON.stringify(o, undefined, 2) + "\n");
52
+ }
53
+ flush() {
54
+ // noop in the simple case
55
+ return Promise.resolve();
56
+ }
57
+ }