@monorepolint/utils 0.5.0-alpha.13 → 0.5.0-alpha.131

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 (87) hide show
  1. package/.turbo/turbo-clean.log +4 -0
  2. package/.turbo/turbo-compile-typescript.log +4 -0
  3. package/.turbo/turbo-lint.log +56 -0
  4. package/.turbo/turbo-test.log +26 -0
  5. package/.turbo/turbo-transpile-typescript.log +14 -0
  6. package/build/js/index.js +893 -0
  7. package/build/js/index.js.map +1 -0
  8. package/build/tsconfig.tsbuildinfo +1 -0
  9. package/build/types/AggregateTiming.d.ts +15 -0
  10. package/build/types/AggregateTiming.d.ts.map +1 -0
  11. package/build/types/CachingHost.d.ts +39 -0
  12. package/build/types/CachingHost.d.ts.map +1 -0
  13. package/build/types/Host.d.ts +38 -0
  14. package/build/types/Host.d.ts.map +1 -0
  15. package/build/types/PackageJson.d.ts.map +1 -0
  16. package/build/types/SimpleHost.d.ts +35 -0
  17. package/build/types/SimpleHost.d.ts.map +1 -0
  18. package/build/types/Table.d.ts +53 -0
  19. package/build/types/Table.d.ts.map +1 -0
  20. package/build/types/Timing.d.ts +9 -0
  21. package/build/types/Timing.d.ts.map +1 -0
  22. package/build/types/__tests__/CachingHost.spec.d.ts +2 -0
  23. package/build/types/__tests__/CachingHost.spec.d.ts.map +1 -0
  24. package/build/types/findWorkspaceDir.d.ts +10 -0
  25. package/build/types/findWorkspaceDir.d.ts.map +1 -0
  26. package/{lib → build/types}/getPackageNameToDir.d.ts +2 -1
  27. package/build/types/getPackageNameToDir.d.ts.map +1 -0
  28. package/build/types/getWorkspacePackageDirs.d.ts +9 -0
  29. package/build/types/getWorkspacePackageDirs.d.ts.map +1 -0
  30. package/build/types/index.d.ts +20 -0
  31. package/build/types/index.d.ts.map +1 -0
  32. package/build/types/matchesAnyGlob.d.ts +17 -0
  33. package/build/types/matchesAnyGlob.d.ts.map +1 -0
  34. package/{lib → build/types}/mutateJson.d.ts +2 -1
  35. package/build/types/mutateJson.d.ts.map +1 -0
  36. package/build/types/nanosecondsToSanity.d.ts +8 -0
  37. package/build/types/nanosecondsToSanity.d.ts.map +1 -0
  38. package/package.json +42 -14
  39. package/src/AggregateTiming.ts +70 -0
  40. package/src/CachingHost.ts +490 -0
  41. package/src/Host.ts +34 -0
  42. package/src/SimpleHost.ts +56 -0
  43. package/src/Table.ts +318 -0
  44. package/src/Timing.ts +54 -0
  45. package/src/__tests__/CachingHost.spec.ts +244 -0
  46. package/src/findWorkspaceDir.ts +25 -7
  47. package/src/getPackageNameToDir.ts +11 -7
  48. package/src/getWorkspacePackageDirs.ts +39 -11
  49. package/src/index.ts +13 -7
  50. package/src/matchesAnyGlob.ts +146 -0
  51. package/src/mutateJson.ts +4 -6
  52. package/src/nanosecondsToSanity.ts +10 -0
  53. package/tsconfig.json +7 -2
  54. package/lib/PackageJson.d.ts.map +0 -1
  55. package/lib/PackageJson.js +0 -9
  56. package/lib/PackageJson.js.map +0 -1
  57. package/lib/findWorkspaceDir.d.ts +0 -8
  58. package/lib/findWorkspaceDir.d.ts.map +0 -1
  59. package/lib/findWorkspaceDir.js +0 -28
  60. package/lib/findWorkspaceDir.js.map +0 -1
  61. package/lib/getPackageNameToDir.d.ts.map +0 -1
  62. package/lib/getPackageNameToDir.js +0 -30
  63. package/lib/getPackageNameToDir.js.map +0 -1
  64. package/lib/getWorkspacePackageDirs.d.ts +0 -8
  65. package/lib/getWorkspacePackageDirs.d.ts.map +0 -1
  66. package/lib/getWorkspacePackageDirs.js +0 -39
  67. package/lib/getWorkspacePackageDirs.js.map +0 -1
  68. package/lib/index.d.ts +0 -14
  69. package/lib/index.d.ts.map +0 -1
  70. package/lib/index.js +0 -21
  71. package/lib/index.js.map +0 -1
  72. package/lib/mutateJson.d.ts.map +0 -1
  73. package/lib/mutateJson.js +0 -17
  74. package/lib/mutateJson.js.map +0 -1
  75. package/lib/readJson.d.ts +0 -8
  76. package/lib/readJson.d.ts.map +0 -1
  77. package/lib/readJson.js +0 -15
  78. package/lib/readJson.js.map +0 -1
  79. package/lib/writeJson.d.ts +0 -8
  80. package/lib/writeJson.d.ts.map +0 -1
  81. package/lib/writeJson.js +0 -14
  82. package/lib/writeJson.js.map +0 -1
  83. package/src/readJson.ts +0 -13
  84. package/src/writeJson.ts +0 -12
  85. package/tsconfig.tsbuildinfo +0 -1586
  86. /package/{lib → build/types}/PackageJson.d.ts +0 -0
  87. /package/{jest.config.js → jest.config.cjs} +0 -0
@@ -0,0 +1,490 @@
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 "node:fs";
9
+ import { Host } from "./Host.js";
10
+ import * as path from "node: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
+ assertType(curNode, "dir");
242
+ curNode = curNode.dir.get(part) ?? this.#stubify(path.join(curNode.fullPath, part), curNode as any);
243
+ curPath = path.join(curPath, part);
244
+ }
245
+ } catch (e) {
246
+ // This error is expected when things done exist.
247
+ // console.log(`Got EXPECTED error when trying to getNearestAncestorNode(${canonicalPath}): `, (e as any).message);
248
+ }
249
+ return { pathWithSymlinks: curPath, node: curNode };
250
+ }
251
+
252
+ #getNode(filePath: string) {
253
+ const canonicalPath = path.resolve(filePath);
254
+ const { pathWithSymlinks, node } = this.#getNearestAncestorNode(canonicalPath);
255
+ if (pathWithSymlinks === canonicalPath) {
256
+ return node;
257
+ }
258
+ return undefined;
259
+ }
260
+
261
+ #getNodeResolvingSymlinks(filePath: string, follows: number = 100): Exclude<Node, SymlinkNode> | undefined {
262
+ const node = this.#getNode(filePath); // canonicalizes for us
263
+ if (!node || node.type !== "symlink") return node;
264
+ // this is a really poor mans way of doing this. but who has 100's of symlinks hanging around?
265
+ if (follows === 0) throw new Error("Exhausted symlink follows");
266
+
267
+ return this.#getNodeResolvingSymlinks(node.symlink, follows--);
268
+ }
269
+
270
+ mkdir(filePath: string, opts: { recursive: boolean } = { recursive: false }): void {
271
+ const canonicalPath = path.resolve(filePath);
272
+ const { node, pathWithSymlinks } = this.#getNearestAncestorNode(canonicalPath);
273
+ if (filePath === pathWithSymlinks) {
274
+ assertType(node, "dir");
275
+ assertHasParent(node);
276
+ if (!node.tombstone) return; // already done
277
+ } else if (path.dirname(filePath) === pathWithSymlinks) {
278
+ assertType(node, "dir");
279
+ assertNoTombstone(node);
280
+ node.dir.set(path.basename(filePath), {
281
+ type: "dir",
282
+ fullPath: filePath,
283
+ parent: node,
284
+ dir: new Map(),
285
+ needsFlush: true,
286
+ });
287
+ return;
288
+ }
289
+
290
+ if (!opts.recursive && path.dirname(canonicalPath) !== pathWithSymlinks) {
291
+ throw new Error("no such file or directory");
292
+ }
293
+
294
+ const rootPath = pathWithSymlinks;
295
+ let maybePath = canonicalPath;
296
+ const toMake: string[] = [];
297
+ while (maybePath !== rootPath) {
298
+ toMake.unshift(path.resolve(node.fullPath, path.relative(rootPath, maybePath)));
299
+ maybePath = path.dirname(maybePath);
300
+ }
301
+
302
+ for (const dirToMake of toMake) {
303
+ this.mkdir(dirToMake);
304
+ }
305
+ }
306
+
307
+ rmdir(directoryPath: string): void {
308
+ const node = this.#getNode(directoryPath);
309
+ if (!node || node.tombstone) {
310
+ return; // this isnt how FS usually work but its what we are doing
311
+ }
312
+ assertType(node, "dir");
313
+ if (node.stub) {
314
+ this.#unstubDirectory(node);
315
+ }
316
+
317
+ if (node.dir.size === 0) {
318
+ this.#replaceNode(node, {
319
+ type: "dir",
320
+ tombstone: true,
321
+ needsFlush: true,
322
+ });
323
+ } else {
324
+ throw new Error("directory not empty");
325
+ }
326
+ }
327
+
328
+ exists(filePath: string): boolean {
329
+ const node = this.#getNode(filePath); // canonicalizes for us
330
+ return !!node && !node.tombstone;
331
+ }
332
+
333
+ readFile(filePath: string, opts?: undefined): Buffer;
334
+ readFile(filePath: string, opts: { encoding: BufferEncoding }): string;
335
+ readFile(filePath: string, opts: { asJson: true }): object;
336
+ readFile(
337
+ filePath: string,
338
+ opts: undefined | { encoding: BufferEncoding; asJson?: false } | { encoding?: never; asJson: true }
339
+ ) {
340
+ let node = this.#getNodeResolvingSymlinks(filePath); // canonicalizes for us
341
+
342
+ if (!node) {
343
+ return undefined;
344
+ }
345
+ assertNotType(node, "dir");
346
+ assertNoTombstone(node);
347
+
348
+ if (node.stub) {
349
+ node = this.#replaceNode(node, {
350
+ type: "file",
351
+ file: this.fs.readFileSync(filePath),
352
+ needsFlush: false,
353
+ });
354
+ }
355
+
356
+ if (!opts) {
357
+ return Buffer.from(node.file);
358
+ } else if (opts.asJson) {
359
+ return JSON.parse(node.file.toString("utf-8"));
360
+ } else {
361
+ return node.file.toString(opts.encoding);
362
+ }
363
+ }
364
+
365
+ writeFile(filePath: string, buffer: Buffer): void;
366
+ writeFile(filePath: string, body: string, opts: { encoding: BufferEncoding }): void;
367
+ writeFile(filePath: string, body: string, opts: { encoding: BufferEncoding }): void;
368
+ writeFile(filePath: string, body: string | Buffer, opts?: { encoding: BufferEncoding }) {
369
+ const fileContentsAsBuffer = typeof body === "string" ? Buffer.from(body, opts?.encoding) : Buffer.from(body);
370
+
371
+ const canonicalPath = path.resolve(filePath);
372
+ const existingNode = this.#getNodeResolvingSymlinks(canonicalPath);
373
+ if (existingNode) {
374
+ if (existingNode.type === "dir") {
375
+ throw new Error("cant write file to a dir");
376
+ }
377
+ this.#replaceNode(existingNode, {
378
+ file: fileContentsAsBuffer,
379
+ type: "file",
380
+ needsFlush: true,
381
+ });
382
+ return;
383
+ }
384
+
385
+ const maybeDirNode = this.#getNodeResolvingSymlinks(path.dirname(canonicalPath));
386
+ assertExists(maybeDirNode);
387
+ assertType(maybeDirNode, "dir");
388
+ assertNoTombstone(maybeDirNode);
389
+
390
+ maybeDirNode.dir.set(path.basename(canonicalPath), {
391
+ type: "file",
392
+ fullPath: canonicalPath,
393
+ parent: maybeDirNode,
394
+ file: fileContentsAsBuffer,
395
+ needsFlush: true,
396
+ });
397
+ }
398
+
399
+ deleteFile(filePath: string) {
400
+ const canonicalPath = path.resolve(filePath);
401
+ const node = this.#getNode(canonicalPath);
402
+ if (!node || (node.type === "file" && node.tombstone === true)) return;
403
+ assertNotType(node, "dir");
404
+ this.#replaceNode(node, {
405
+ type: "file",
406
+ tombstone: true,
407
+ needsFlush: true,
408
+ });
409
+ }
410
+
411
+ readJson(filePath: string) {
412
+ return this.readFile(filePath, { asJson: true });
413
+ }
414
+
415
+ writeJson(filePath: string, o: object): void {
416
+ return this.writeFile(filePath, JSON.stringify(o, undefined, 2) + "\n", {
417
+ encoding: "utf-8",
418
+ });
419
+ }
420
+
421
+ async #flushFileNode(node: FileNode | FileStubNode | FileTombstoneNode): Promise<unknown> {
422
+ // FIXME all tombstones need a flush, so we can get rid of needsFlush for them
423
+ if (node.tombstone) {
424
+ try {
425
+ await this.fs.promises.access(node.fullPath);
426
+ return this.fs.promises.unlink(node.fullPath);
427
+ } catch (e) {
428
+ // should only throw if file doesnt exist which is no op
429
+ return;
430
+ }
431
+ } else if (node.stub === true || node.needsFlush === false) {
432
+ return;
433
+ } else {
434
+ // we dont do things with file stubs
435
+ return this.fs.promises.writeFile(node.fullPath, node.file);
436
+ }
437
+ }
438
+
439
+ async #flushSymlinkNode(node: SymlinkNode) {
440
+ if (!node.needsFlush) return;
441
+ try {
442
+ const linkValue = await this.fs.promises.readlink(node.fullPath);
443
+ if (linkValue === node.symlink) {
444
+ return;
445
+ }
446
+ } catch (e) {
447
+ // expected when the link doesnt exist
448
+ }
449
+ return this.fs.promises.symlink(node.symlink, node.fullPath);
450
+ }
451
+
452
+ async #flushDirNode(node: DirNode | DirStubNode | DirTombstoneNode): Promise<unknown> {
453
+ if (!node.tombstone && node.needsFlush) {
454
+ try {
455
+ await this.fs.promises.access(node.fullPath); // throws if the file doesnt exist
456
+ } catch (e) {
457
+ await this.fs.promises.mkdir(node.fullPath); // throws if it does :(
458
+ }
459
+ }
460
+
461
+ const promises: Promise<unknown>[] = [];
462
+ for (const child of node.dir.values()) {
463
+ if (node.tombstone && !child.tombstone) {
464
+ throw new Error("Unexpected failure during sanity check. A non-deleted child is on a deleted dir");
465
+ }
466
+ if (child.type === "dir") {
467
+ promises.push(this.#flushDirNode(child));
468
+ } else if (child.type === "file") {
469
+ promises.push(this.#flushFileNode(child));
470
+ } else if (child.type === "symlink") {
471
+ promises.push(this.#flushSymlinkNode(child));
472
+ } else {
473
+ throw new Error("should never happen");
474
+ }
475
+ }
476
+ await Promise.all(promises);
477
+ if (node.tombstone) {
478
+ return this.fs.promises.rmdir(node.fullPath);
479
+ }
480
+ return;
481
+ }
482
+
483
+ flush() {
484
+ const promises: Promise<any>[] = [];
485
+ for (const rootNode of this.#trees.values()) {
486
+ promises.push(this.#flushDirNode(rootNode));
487
+ }
488
+ return Promise.all(promises);
489
+ }
490
+ }
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,56 @@
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.js";
11
+ export class SimpleHost implements Host {
12
+ constructor(private fs: typeof realFs = realFs) {}
13
+ mkdir(directoryPath: string, opts?: { recursive: boolean }): void {
14
+ this.fs.mkdirSync(directoryPath, { recursive: opts?.recursive ?? false });
15
+ }
16
+ rmdir(directoryPath: string): void {
17
+ this.fs.rmdirSync(directoryPath);
18
+ }
19
+
20
+ exists(path: string): boolean {
21
+ return this.fs.existsSync(path);
22
+ }
23
+
24
+ writeFile(path: string, buffer: Buffer): void;
25
+ writeFile(path: string, body: string, opts: { encoding: BufferEncoding }): void;
26
+ writeFile(path: any, body: any, opts?: { encoding: BufferEncoding }): any {
27
+ if (opts) {
28
+ this.fs.writeFileSync(path, body, { encoding: opts.encoding });
29
+ } else {
30
+ this.fs.writeFileSync(path, body);
31
+ }
32
+ }
33
+ readFile(path: string, opts?: undefined): Buffer;
34
+ readFile(path: string, opts: { encoding: BufferEncoding }): string;
35
+ readFile(path: string, opts: { asJson: true }): object;
36
+ readFile(path: any, opts?: { encoding?: BufferEncoding; asJson?: boolean }): string | object | Buffer {
37
+ if (opts?.asJson) {
38
+ return JSON.parse(this.fs.readFileSync(path, "utf-8"));
39
+ }
40
+ return this.fs.readFileSync(path, opts?.encoding);
41
+ }
42
+ deleteFile(path: string) {
43
+ this.fs.unlinkSync(path);
44
+ }
45
+ readJson(filename: string) {
46
+ const contents = this.fs.readFileSync(filename, "utf-8");
47
+ return JSON.parse(contents);
48
+ }
49
+ writeJson(path: string, o: object): void {
50
+ return this.fs.writeFileSync(path, JSON.stringify(o, undefined, 2) + "\n");
51
+ }
52
+ flush() {
53
+ // noop in the simple case
54
+ return Promise.resolve();
55
+ }
56
+ }