@monorepolint/utils 0.5.0-alpha.10 → 0.5.0-alpha.103

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 (90) 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 +61 -0
  4. package/.turbo/turbo-test.log +26 -0
  5. package/.turbo/turbo-transpile-typescript.log +18 -0
  6. package/build/js/index.cjs +999 -0
  7. package/build/js/index.cjs.map +1 -0
  8. package/build/js/index.js +957 -0
  9. package/build/js/index.js.map +1 -0
  10. package/build/tsconfig.tsbuildinfo +1 -0
  11. package/build/types/AggregateTiming.d.ts +15 -0
  12. package/build/types/AggregateTiming.d.ts.map +1 -0
  13. package/build/types/CachingHost.d.ts +39 -0
  14. package/build/types/CachingHost.d.ts.map +1 -0
  15. package/build/types/Host.d.ts +38 -0
  16. package/build/types/Host.d.ts.map +1 -0
  17. package/{lib → build/types}/PackageJson.d.ts +0 -0
  18. package/build/types/PackageJson.d.ts.map +1 -0
  19. package/build/types/SimpleHost.d.ts +34 -0
  20. package/build/types/SimpleHost.d.ts.map +1 -0
  21. package/build/types/Table.d.ts +53 -0
  22. package/build/types/Table.d.ts.map +1 -0
  23. package/build/types/Timing.d.ts +9 -0
  24. package/build/types/Timing.d.ts.map +1 -0
  25. package/build/types/__tests__/CachingHost.spec.d.ts +2 -0
  26. package/build/types/__tests__/CachingHost.spec.d.ts.map +1 -0
  27. package/build/types/findWorkspaceDir.d.ts +10 -0
  28. package/build/types/findWorkspaceDir.d.ts.map +1 -0
  29. package/{lib → build/types}/getPackageNameToDir.d.ts +2 -1
  30. package/build/types/getPackageNameToDir.d.ts.map +1 -0
  31. package/build/types/getWorkspacePackageDirs.d.ts +9 -0
  32. package/build/types/getWorkspacePackageDirs.d.ts.map +1 -0
  33. package/build/types/index.d.ts +20 -0
  34. package/build/types/index.d.ts.map +1 -0
  35. package/build/types/matchesAnyGlob.d.ts +17 -0
  36. package/build/types/matchesAnyGlob.d.ts.map +1 -0
  37. package/{lib → build/types}/mutateJson.d.ts +2 -1
  38. package/build/types/mutateJson.d.ts.map +1 -0
  39. package/build/types/nanosecondsToSanity.d.ts +8 -0
  40. package/build/types/nanosecondsToSanity.d.ts.map +1 -0
  41. package/{jest.config.js → jest.config.cjs} +0 -0
  42. package/package.json +37 -14
  43. package/src/AggregateTiming.ts +70 -0
  44. package/src/CachingHost.ts +490 -0
  45. package/src/Host.ts +34 -0
  46. package/src/SimpleHost.ts +56 -0
  47. package/src/Table.ts +318 -0
  48. package/src/Timing.ts +54 -0
  49. package/src/__tests__/CachingHost.spec.ts +244 -0
  50. package/src/findWorkspaceDir.ts +25 -7
  51. package/src/getPackageNameToDir.ts +11 -7
  52. package/src/getWorkspacePackageDirs.ts +39 -11
  53. package/src/index.ts +13 -7
  54. package/src/matchesAnyGlob.ts +146 -0
  55. package/src/mutateJson.ts +4 -6
  56. package/src/nanosecondsToSanity.ts +10 -0
  57. package/tsconfig.json +7 -2
  58. package/tsup.config.cjs +11 -0
  59. package/lib/PackageJson.d.ts.map +0 -1
  60. package/lib/PackageJson.js +0 -9
  61. package/lib/PackageJson.js.map +0 -1
  62. package/lib/findWorkspaceDir.d.ts +0 -8
  63. package/lib/findWorkspaceDir.d.ts.map +0 -1
  64. package/lib/findWorkspaceDir.js +0 -28
  65. package/lib/findWorkspaceDir.js.map +0 -1
  66. package/lib/getPackageNameToDir.d.ts.map +0 -1
  67. package/lib/getPackageNameToDir.js +0 -30
  68. package/lib/getPackageNameToDir.js.map +0 -1
  69. package/lib/getWorkspacePackageDirs.d.ts +0 -8
  70. package/lib/getWorkspacePackageDirs.d.ts.map +0 -1
  71. package/lib/getWorkspacePackageDirs.js +0 -39
  72. package/lib/getWorkspacePackageDirs.js.map +0 -1
  73. package/lib/index.d.ts +0 -14
  74. package/lib/index.d.ts.map +0 -1
  75. package/lib/index.js +0 -21
  76. package/lib/index.js.map +0 -1
  77. package/lib/mutateJson.d.ts.map +0 -1
  78. package/lib/mutateJson.js +0 -17
  79. package/lib/mutateJson.js.map +0 -1
  80. package/lib/readJson.d.ts +0 -8
  81. package/lib/readJson.d.ts.map +0 -1
  82. package/lib/readJson.js +0 -15
  83. package/lib/readJson.js.map +0 -1
  84. package/lib/writeJson.d.ts +0 -8
  85. package/lib/writeJson.d.ts.map +0 -1
  86. package/lib/writeJson.js +0 -14
  87. package/lib/writeJson.js.map +0 -1
  88. package/src/readJson.ts +0 -13
  89. package/src/writeJson.ts +0 -12
  90. package/tsconfig.tsbuildinfo +0 -1586
@@ -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
+ }
package/src/Table.ts ADDED
@@ -0,0 +1,318 @@
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
+ // tslint:disable:no-console
8
+ import { nanosecondsToSanity } from "./nanosecondsToSanity.js";
9
+ type HeaderFooterHelper<HB, FB, H, F> = (HB extends true ? { header: H } : { header?: H }) &
10
+ (FB extends true ? { footer: F } : { footer?: F });
11
+
12
+ type WithAlignemnt = { alignment?: "right" | "left" };
13
+ type BaseCellConfig = WithAlignemnt & { type: "bigint" | "string" };
14
+
15
+ type BaseBigIntCellConfig = {
16
+ type: "bigint";
17
+ renderAs?: "nanoseconds";
18
+ precision?: number;
19
+ } & WithAlignemnt;
20
+ type BaseStringCellConfig = { type: "string" } & WithAlignemnt;
21
+
22
+ type BigIntColumnConfig<H, F> = WithAlignemnt &
23
+ BaseBigIntCellConfig &
24
+ HeaderFooterHelper<H, F, string, AggregateFooterConfig | StaticFooterConfig>;
25
+
26
+ type StringColumnConfig<H, F> = WithAlignemnt &
27
+ BaseStringCellConfig &
28
+ HeaderFooterHelper<H, F, string, StaticFooterConfig>;
29
+
30
+ type AggregateFooterConfig = {
31
+ aggregate: "sum" | "average";
32
+ renderAs?: "nanoseconds";
33
+ precision?: number;
34
+ } & WithAlignemnt;
35
+
36
+ type StaticFooterConfig = StrictStaticFooterConfig | string;
37
+ type StrictStaticFooterConfig = {
38
+ aggregate: "static";
39
+ value: string;
40
+ } & WithAlignemnt;
41
+
42
+ type AnyStrictFooterConfig = AggregateFooterConfig | StrictStaticFooterConfig;
43
+
44
+ type TableConfig<T extends any[], H extends boolean, F extends boolean> = {
45
+ sortColumn?: number;
46
+ padding?: number;
47
+ showHeader: H;
48
+ showFooter: F;
49
+ columns: {
50
+ [K in keyof T]: T[K] extends bigint ? BigIntColumnConfig<H, F> : StringColumnConfig<H, F>;
51
+ };
52
+ title: string;
53
+ };
54
+
55
+ type InternalTableConfig = {
56
+ padding: number;
57
+ showHeader: boolean;
58
+ showFooter: boolean;
59
+ sortColumn?: number;
60
+ columns: Array<BigIntColumnConfig<any, any> | StringColumnConfig<any, any>>;
61
+ title: string;
62
+ };
63
+
64
+ export class Table<T extends any[]> {
65
+ #rows: T[] = [];
66
+ #config: InternalTableConfig;
67
+ #columnWidths: number[] = [];
68
+ #footer: Array<bigint | string> = [];
69
+ #footerRowConfig?: Array<Required<BaseCellConfig> & AnyStrictFooterConfig>;
70
+ #totalWidth = 0;
71
+
72
+ constructor(
73
+ config:
74
+ | TableConfig<T, true, true>
75
+ | TableConfig<T, true, false>
76
+ | TableConfig<T, false, true>
77
+ | TableConfig<T, false, false>
78
+ ) {
79
+ this.#config = {
80
+ padding: 2,
81
+ ...config,
82
+ };
83
+ this.#columnWidths.fill(0, 0, config.columns.length);
84
+
85
+ if (config.showFooter) {
86
+ this.#footerRowConfig = [];
87
+ for (const columnConfig of config.columns) {
88
+ if (columnConfig.footer === undefined) {
89
+ throw new Error("Must specify footer fields when showFooter is true");
90
+ } else if (typeof columnConfig.footer === "string") {
91
+ this.#footerRowConfig.push({
92
+ type: "string",
93
+ alignment: "left",
94
+ aggregate: "static",
95
+ value: columnConfig.footer,
96
+ });
97
+ } else if ("value" in columnConfig.footer) {
98
+ this.#footerRowConfig.push({
99
+ type: "string",
100
+ alignment: "left",
101
+ ...columnConfig.footer,
102
+ });
103
+ } else if ("aggregate" in columnConfig.footer) {
104
+ if (columnConfig.type !== "bigint") throw new Error("expecting bigint for aggregate");
105
+ this.#footerRowConfig.push({
106
+ type: columnConfig.type,
107
+ renderAs: columnConfig.renderAs,
108
+ precision: columnConfig.precision,
109
+ alignment: "right",
110
+ ...columnConfig.footer,
111
+ });
112
+ }
113
+ }
114
+ }
115
+ }
116
+
117
+ addRow(...data: T) {
118
+ // TODO: maybe clone the data
119
+ this.#rows.push(data);
120
+ }
121
+
122
+ #sumColumn(c: number) {
123
+ let total = BigInt(0);
124
+ for (const row of this.#rows) {
125
+ total += row[c];
126
+ }
127
+ return total;
128
+ }
129
+
130
+ #updateFooterRow() {
131
+ const footerRowConfig = this.#footerRowConfig;
132
+ if (footerRowConfig) {
133
+ for (let c = 0; c < footerRowConfig.length; c++) {
134
+ const footerColConfig = footerRowConfig[c];
135
+
136
+ switch (footerColConfig.aggregate) {
137
+ case "sum":
138
+ this.#footer[c] = this.#sumColumn(c);
139
+ break;
140
+ case "average":
141
+ this.#footer[c] = this.#sumColumn(c) / BigInt(this.#rows.length);
142
+ break;
143
+ case "static":
144
+ this.#footer[c] = footerColConfig.value;
145
+ break;
146
+ }
147
+ }
148
+ }
149
+ }
150
+
151
+ #calculateColumnWidths() {
152
+ this.#columnWidths.fill(0, 0, this.#config.columns.length);
153
+
154
+ for (let c = 0; c < this.#config.columns.length; c++) {
155
+ const colConfig = this.#config.columns[c];
156
+ this.#columnWidths[c] = Math.max(
157
+ (this.#config.columns[c].header ?? "").length,
158
+ ...this.#rows.map((a) => this.#getCellValueAsString(a[c], colConfig).length),
159
+ this.#footer && this.#footerRowConfig
160
+ ? this.#getCellValueAsString(this.#footer?.[c] ?? "", this.#footerRowConfig[c]).length
161
+ : 0
162
+ );
163
+ }
164
+
165
+ this.#totalWidth = 0;
166
+ for (const colWidth of this.#columnWidths) {
167
+ this.#totalWidth += colWidth;
168
+ }
169
+ this.#totalWidth += (this.#columnWidths.length - 1) * this.#config.padding;
170
+ }
171
+
172
+ #printSeparator(fillString: string) {
173
+ const paddingString = "".padStart(this.#config.padding, " ");
174
+
175
+ let hr2 = "";
176
+
177
+ // tslint:disable-next-line: prefer-for-of
178
+ for (let c = 0; c < this.#columnWidths.length; c++) {
179
+ hr2 += "".padStart(this.#columnWidths[c], fillString) + paddingString;
180
+ }
181
+ hr2 = hr2.trimRight();
182
+ console.log(hr2);
183
+ }
184
+
185
+ #printHeaderRow() {
186
+ if (this.#config.showHeader) {
187
+ const colConfigs = this.#config.columns;
188
+ const paddingString = "".padStart(this.#config.padding, " ");
189
+
190
+ let hr = "";
191
+ for (let c = 0; c < colConfigs.length; c++) {
192
+ const heading = colConfigs[c].header ?? "";
193
+ hr += heading.padEnd(this.#columnWidths[c], " ") + paddingString;
194
+ }
195
+ hr = hr.trimRight();
196
+ console.log(hr);
197
+
198
+ this.#printSeparator("-");
199
+ }
200
+ }
201
+
202
+ #printFooterRow() {
203
+ const footerRow = this.#footer;
204
+ if (footerRow) {
205
+ this.#printSeparator("=");
206
+
207
+ const paddingString = "".padStart(this.#config.padding, " ");
208
+
209
+ let hr = "";
210
+ for (let c = 0; c < footerRow.length; c++) {
211
+ hr += this.#getCellValueAligned(footerRow[c], this.#footerRowConfig![c], c) + paddingString; // .padEnd(this.#columnWidths[c], " ") + paddingString;
212
+ }
213
+ hr = hr.trimRight();
214
+ console.log(hr);
215
+ }
216
+ }
217
+
218
+ print() {
219
+ // let data = [...this.#rows];
220
+ if (this.#config.sortColumn !== undefined) {
221
+ // todo
222
+ }
223
+
224
+ this.#updateFooterRow();
225
+ this.#calculateColumnWidths();
226
+
227
+ console.log();
228
+ console.log(`${this.#config.title}`);
229
+ console.log("".padStart(this.#totalWidth, "="));
230
+
231
+ const paddingString = "".padStart(this.#config.padding, " ");
232
+ if (this.#config.showHeader) {
233
+ this.#printHeaderRow();
234
+ }
235
+
236
+ for (let r = 0; r < this.#rows.length; r++) {
237
+ let rowText = "";
238
+ for (let c = 0; c < this.#config.columns.length; c++) {
239
+ rowText += this.getEntryAsStringAligned(c, r) + paddingString;
240
+ }
241
+ rowText.trim();
242
+ console.log(rowText);
243
+ }
244
+
245
+ if (this.#config.showFooter) this.#printFooterRow();
246
+ console.log();
247
+ }
248
+
249
+ #getCellValueAsString(value: bigint | string, config: BaseBigIntCellConfig | BaseStringCellConfig) {
250
+ if (config.type === "bigint" && config.renderAs === "nanoseconds") {
251
+ return nanosecondsToSanity(value as bigint, config.precision ?? 9);
252
+ } else {
253
+ return "" + value;
254
+ }
255
+ }
256
+
257
+ #getCellValueAligned(value: bigint | string, config: BaseBigIntCellConfig | BaseStringCellConfig, column: number) {
258
+ let result: string;
259
+ if (config.type === "bigint" && config.renderAs === "nanoseconds") {
260
+ result = nanosecondsToSanity(value as bigint, config.precision ?? 9);
261
+ } else {
262
+ result = "" + value;
263
+ }
264
+
265
+ if (config.alignment === "left") {
266
+ return result.padEnd(this.#columnWidths[column]);
267
+ } else {
268
+ return result.padStart(this.#columnWidths[column]);
269
+ }
270
+ }
271
+
272
+ getEntryAsString(colNum: number, rowNum: number) {
273
+ const config = this.#config.columns[colNum];
274
+
275
+ if (config.type === "bigint" && config.renderAs === "nanoseconds") {
276
+ return nanosecondsToSanity(this.#rows[rowNum][colNum], config.precision ?? 9);
277
+ } else {
278
+ return "" + this.#rows[rowNum][colNum];
279
+ }
280
+ }
281
+
282
+ getEntryAsStringAligned(colNum: number, rowNum: number) {
283
+ const config = this.#config.columns[colNum];
284
+
285
+ let result: string;
286
+ if (config.type === "bigint" && config.renderAs === "nanoseconds") {
287
+ result = nanosecondsToSanity(this.#rows[rowNum][colNum], config.precision ?? 9);
288
+ } else {
289
+ result = "" + this.#rows[rowNum][colNum];
290
+ }
291
+
292
+ if (config.alignment === "left") {
293
+ return result.padEnd(this.#columnWidths[colNum]);
294
+ } else {
295
+ return result.padStart(this.#columnWidths[colNum]);
296
+ }
297
+ }
298
+
299
+ getColumnWidth(colNum: number, config: BigIntColumnConfig<boolean, boolean> | StringColumnConfig<boolean, boolean>) {
300
+ let maxWidth = Math.max(
301
+ (config.header ?? "").length,
302
+ this.#footer && this.#footerRowConfig
303
+ ? this.#getCellValueAsString(this.#footer[colNum], this.#footerRowConfig[colNum]).length
304
+ : 0
305
+ );
306
+
307
+ for (let r = 0; r < this.#rows.length; r++) {
308
+ maxWidth = Math.max(maxWidth, this.getEntryAsString(colNum, r).length);
309
+ // if (config.type == "bigint" && config.renderAs === "nanoseconds") {
310
+ // maxWidth = Math.max(maxWidth, Number(row[colNum] / BigInt(1000000000)) + 10); // 1 for period, 9 for digits
311
+ // } else if (config.type == "bigint" || config.type == "string") {
312
+ // maxWidth = Math.max(maxWidth, ("" + row[colNum]).length);
313
+ // }
314
+ }
315
+
316
+ return maxWidth;
317
+ }
318
+ }
package/src/Timing.ts ADDED
@@ -0,0 +1,54 @@
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
+ // tslint:disable:no-console
8
+ import { Table } from "./Table.js";
9
+ export class Timing {
10
+ #starts: Array<{ name?: string; start: bigint }> = [];
11
+ constructor(private title: string) {
12
+ this.stop(); // make sure we have starting point
13
+ }
14
+
15
+ public start(name: string) {
16
+ this.#starts.push({ name, start: process.hrtime.bigint() });
17
+ }
18
+
19
+ public stop() {
20
+ this.#starts.push({ start: process.hrtime.bigint() });
21
+ }
22
+
23
+ public printResults() {
24
+ const table = new Table<[bigint, string]>({
25
+ sortColumn: -1,
26
+ showFooter: true,
27
+ showHeader: true,
28
+ title: this.title,
29
+ columns: [
30
+ {
31
+ header: "Duration",
32
+ type: "bigint",
33
+ renderAs: "nanoseconds",
34
+ precision: 4,
35
+ footer: { aggregate: "sum" },
36
+ },
37
+ { header: "Task", type: "string", footer: "TOTAL" },
38
+ ],
39
+ });
40
+
41
+ this.stop(); // be sure we stopped the last one
42
+
43
+ let cur: { name?: string; start: bigint } = this.#starts[0];
44
+ for (const entry of this.#starts) {
45
+ if (cur.name) {
46
+ const span = entry.start - cur.start;
47
+ table.addRow(span, cur.name);
48
+ }
49
+ cur = entry;
50
+ }
51
+
52
+ table.print();
53
+ }
54
+ }
@@ -0,0 +1,244 @@
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
+ import { describe, expect, it, beforeEach } from "@jest/globals";
8
+ import { CachingHost } from "../CachingHost.js";
9
+ import * as realfs from "node:fs";
10
+ import * as path from "node:path";
11
+ import * as os from "node:os";
12
+
13
+ interface TestCase<T> {
14
+ getFs: () => T;
15
+ createTmpDir: () => string;
16
+ }
17
+
18
+ class RealFsTestCase implements TestCase<typeof realfs> {
19
+ getFs = () => {
20
+ return realfs;
21
+ };
22
+ createTmpDir = () => {
23
+ return realfs.mkdtempSync(path.join(os.tmpdir(), "mrl-test"));
24
+ };
25
+ }
26
+
27
+ describe(CachingHost, () => {
28
+ describe.each([["fs", new RealFsTestCase()]])("%s", (_testCaseName, testCase) => {
29
+ let baseDir: string;
30
+ let fs: ReturnType<typeof testCase.getFs>;
31
+
32
+ let SYMLINK_JSON_PATH: string;
33
+ let SYMLINK_TXT_PATH: string;
34
+ let FILE_JSON_PATH: string;
35
+ let FILE_TXT_PATH: string;
36
+
37
+ beforeEach(() => {
38
+ fs = testCase.getFs();
39
+ baseDir = testCase.createTmpDir();
40
+
41
+ SYMLINK_JSON_PATH = path.resolve(baseDir, "symlink.json");
42
+ SYMLINK_TXT_PATH = path.resolve(baseDir, "symlink.txt");
43
+ FILE_TXT_PATH = path.resolve(baseDir, "file.txt");
44
+ FILE_JSON_PATH = path.resolve(baseDir, "file.json");
45
+
46
+ fs.writeFileSync(FILE_JSON_PATH, JSON.stringify({ hi: "mom" }), { encoding: "utf-8" });
47
+ fs.symlinkSync(FILE_JSON_PATH, SYMLINK_JSON_PATH);
48
+
49
+ fs.writeFileSync(FILE_TXT_PATH, "hi dad", { encoding: "utf-8" });
50
+ fs.symlinkSync(FILE_TXT_PATH, SYMLINK_TXT_PATH);
51
+ });
52
+
53
+ function expectFileToExist(file: string) {
54
+ return expect(fs.existsSync(file));
55
+ }
56
+
57
+ function expectFileContents(file: string) {
58
+ return expect(fs.readFileSync(file, { encoding: "utf-8" }));
59
+ }
60
+
61
+ function expectSymlinkTarget(src: string, target: string) {
62
+ const stat = fs.lstatSync(src);
63
+ expect(stat.isSymbolicLink() && fs.readlinkSync(src)).toEqual(target);
64
+ }
65
+
66
+ it("Answers exists() properly", async () => {
67
+ expect.assertions(2);
68
+ await realfs.promises.writeFile(path.join(baseDir, "b.txt"), "hi", { encoding: "utf-8" });
69
+ const host = new CachingHost(fs as any);
70
+ expect(host.exists(path.join(baseDir, "b.txt"))).toBe(true);
71
+ expect(host.exists(path.join(baseDir, "nosuchfile.txt"))).toBe(false);
72
+ });
73
+
74
+ it("properly handles deletes", async () => {
75
+ expect.assertions(2);
76
+ const host = new CachingHost(fs as any);
77
+
78
+ host.writeFile(path.join(baseDir, "b.txt"), "hi", { encoding: "utf-8" });
79
+ host.deleteFile(path.join(baseDir, "b.txt"));
80
+ host.deleteFile(path.join(baseDir, "a.json"));
81
+
82
+ await host.flush();
83
+
84
+ expectFileToExist(path.join(baseDir, "b.txt")).toBeFalsy();
85
+ expectFileToExist(path.join(baseDir, "a.txt")).toBeFalsy();
86
+ });
87
+
88
+ it("handles simple read/write workflow", async () => {
89
+ expect.assertions(1);
90
+
91
+ const host = new CachingHost(fs as any);
92
+ host.writeFile(FILE_JSON_PATH, "cow", { encoding: "utf-8" });
93
+
94
+ expect(host.readFile(FILE_JSON_PATH, { encoding: "utf-8" })).toEqual("cow");
95
+ });
96
+
97
+ it("handles target symlink changing", async () => {
98
+ expect.assertions(1);
99
+
100
+ const host = new CachingHost(fs as any);
101
+ host.writeFile(FILE_JSON_PATH, "cow", { encoding: "utf-8" });
102
+
103
+ expect(host.readFile(FILE_JSON_PATH, { encoding: "utf-8" })).toEqual("cow");
104
+ });
105
+
106
+ it("handles writing symlinks properly", async () => {
107
+ expect.assertions(8);
108
+
109
+ const host = new CachingHost(fs as any);
110
+
111
+ // file.json should now hold "hmm"
112
+ host.writeFile(SYMLINK_JSON_PATH, "hmm", { encoding: "utf-8" });
113
+
114
+ expect(host.readFile(SYMLINK_JSON_PATH, { encoding: "utf-8" })).toEqual("hmm");
115
+ expect(host.readFile(FILE_JSON_PATH, { encoding: "utf-8" })).toEqual("hmm");
116
+
117
+ // Write it out so we can verify disk is right
118
+ await host.flush();
119
+
120
+ expectFileToExist(SYMLINK_JSON_PATH).toBeTruthy();
121
+ expectFileToExist(FILE_TXT_PATH).toBeTruthy();
122
+
123
+ expectFileContents(FILE_JSON_PATH).toBe("hmm");
124
+ expectFileContents(SYMLINK_JSON_PATH).toBe("hmm");
125
+
126
+ expectSymlinkTarget(SYMLINK_JSON_PATH, FILE_JSON_PATH);
127
+
128
+ expect(host.readFile(SYMLINK_JSON_PATH, { encoding: "utf-8" })).toEqual("hmm");
129
+ });
130
+
131
+ it("handles writing symlinks properly if you read it first", async () => {
132
+ expect.assertions(8);
133
+
134
+ const host = new CachingHost(fs as any);
135
+ host.readFile(SYMLINK_JSON_PATH);
136
+
137
+ // file.json should now hold "hmm"
138
+ host.writeFile(path.join(baseDir, "symlink.json"), "hmm", { encoding: "utf-8" });
139
+
140
+ expect(host.readFile(SYMLINK_JSON_PATH, { encoding: "utf-8" })).toEqual("hmm");
141
+ expect(host.readFile(FILE_JSON_PATH, { encoding: "utf-8" })).toEqual("hmm");
142
+
143
+ // Write it out so we can verify disk is right
144
+ await host.flush();
145
+
146
+ expectFileToExist(SYMLINK_JSON_PATH).toBeTruthy();
147
+ expectFileToExist(FILE_TXT_PATH).toBeTruthy();
148
+
149
+ expectFileContents(FILE_JSON_PATH).toBe("hmm");
150
+ expectFileContents(SYMLINK_JSON_PATH).toBe("hmm");
151
+
152
+ expectSymlinkTarget(SYMLINK_JSON_PATH, FILE_JSON_PATH);
153
+
154
+ expect(host.readFile(SYMLINK_JSON_PATH, { encoding: "utf-8" })).toEqual("hmm");
155
+ });
156
+
157
+ it("handles creating new symlinks", async () => {
158
+ expect.assertions(8);
159
+
160
+ const host = new CachingHost(fs as any);
161
+
162
+ host.readFile(SYMLINK_JSON_PATH);
163
+
164
+ // file.json should now hold "hmm"
165
+ host.writeFile(path.join(baseDir, "symlink.json"), "hmm", { encoding: "utf-8" });
166
+
167
+ expect(host.readFile(SYMLINK_JSON_PATH, { encoding: "utf-8" })).toEqual("hmm");
168
+ expect(host.readFile(FILE_JSON_PATH, { encoding: "utf-8" })).toEqual("hmm");
169
+
170
+ // Write it out so we can verify disk is right
171
+ await host.flush();
172
+
173
+ expectFileToExist(SYMLINK_JSON_PATH).toBeTruthy();
174
+ expectFileToExist(FILE_TXT_PATH).toBeTruthy();
175
+
176
+ expectFileContents(FILE_JSON_PATH).toBe("hmm");
177
+ expectFileContents(SYMLINK_JSON_PATH).toBe("hmm");
178
+
179
+ expectSymlinkTarget(SYMLINK_JSON_PATH, FILE_JSON_PATH);
180
+
181
+ expect(host.readFile(SYMLINK_JSON_PATH, { encoding: "utf-8" })).toEqual("hmm");
182
+ });
183
+
184
+ it("makes directories", async () => {
185
+ expect.assertions(3);
186
+
187
+ const host = new CachingHost(fs as any);
188
+
189
+ host.mkdir(path.join(baseDir, "foo", "bar", "baz"), { recursive: true });
190
+
191
+ // Write it out so we can verify disk is right
192
+ await host.flush();
193
+
194
+ expectFileToExist(path.join(baseDir, "foo")).toBeTruthy();
195
+ expectFileToExist(path.join(baseDir, "foo", "bar")).toBeTruthy();
196
+ expectFileToExist(path.join(baseDir, "foo", "bar", "baz")).toBeTruthy();
197
+ });
198
+
199
+ it("can unlink empty dirs", async () => {
200
+ expect.assertions(1);
201
+
202
+ // base setup
203
+ const fooDirPath = path.join(baseDir, "foo");
204
+ fs.mkdirSync(fooDirPath, { recursive: true });
205
+
206
+ // prep obj
207
+ const host = new CachingHost(fs as any);
208
+ host.rmdir(fooDirPath);
209
+
210
+ // Write it out so we can verify disk is right
211
+ await host.flush();
212
+
213
+ expectFileToExist(fooDirPath).toBeFalsy();
214
+ });
215
+
216
+ it("doesnt let you delete a directory with files", async () => {
217
+ expect.assertions(2);
218
+
219
+ const fooDirPath = path.join(baseDir, "foo");
220
+ const barFilePath = path.join(fooDirPath, "bar.txt");
221
+
222
+ const host = new CachingHost(fs as any);
223
+ host.mkdir(fooDirPath, { recursive: true });
224
+ host.writeJson(barFilePath, { hi: 5 });
225
+
226
+ expect(() => {
227
+ host.rmdir(fooDirPath);
228
+ }).toThrow();
229
+
230
+ // Write it out so we can verify disk is right
231
+ await host.flush();
232
+
233
+ expectFileToExist(fooDirPath).toBeTruthy();
234
+ });
235
+
236
+ it("doesn't let you rmdir() a file", () => {
237
+ expect.assertions(1);
238
+ const host = new CachingHost(fs as any);
239
+ expect(() => {
240
+ host.rmdir(FILE_JSON_PATH);
241
+ }).toThrow();
242
+ });
243
+ });
244
+ });