@monorepolint/utils 0.5.0-alpha.84 → 0.5.0-alpha.85
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/lib/AggregateTiming.d.ts +15 -0
- package/lib/AggregateTiming.d.ts.map +1 -0
- package/lib/AggregateTiming.js +70 -0
- package/lib/AggregateTiming.js.map +1 -0
- package/lib/CachingHost.d.ts +39 -0
- package/lib/CachingHost.d.ts.map +1 -0
- package/lib/CachingHost.js +366 -0
- package/lib/CachingHost.js.map +1 -0
- package/lib/Host.d.ts +38 -0
- package/lib/Host.d.ts.map +1 -0
- package/lib/Host.js +9 -0
- package/lib/Host.js.map +1 -0
- package/lib/SimpleHost.d.ts +35 -0
- package/lib/SimpleHost.d.ts.map +1 -0
- package/lib/SimpleHost.js +56 -0
- package/lib/SimpleHost.js.map +1 -0
- package/lib/Table.d.ts +53 -0
- package/lib/Table.d.ts.map +1 -0
- package/lib/Table.js +234 -0
- package/lib/Table.js.map +1 -0
- package/lib/Timing.d.ts +9 -0
- package/lib/Timing.d.ts.map +1 -0
- package/lib/Timing.js +57 -0
- package/lib/Timing.js.map +1 -0
- package/lib/__tests__/CachingHost.spec.d.ts +8 -0
- package/lib/__tests__/CachingHost.spec.d.ts.map +1 -0
- package/lib/__tests__/CachingHost.spec.js +178 -0
- package/lib/__tests__/CachingHost.spec.js.map +1 -0
- package/lib/findWorkspaceDir.d.ts +2 -1
- package/lib/findWorkspaceDir.d.ts.map +1 -1
- package/lib/findWorkspaceDir.js +4 -6
- package/lib/findWorkspaceDir.js.map +1 -1
- package/lib/getPackageNameToDir.d.ts +2 -1
- package/lib/getPackageNameToDir.d.ts.map +1 -1
- package/lib/getPackageNameToDir.js +4 -5
- package/lib/getPackageNameToDir.js.map +1 -1
- package/lib/getWorkspacePackageDirs.d.ts +2 -1
- package/lib/getWorkspacePackageDirs.d.ts.map +1 -1
- package/lib/getWorkspacePackageDirs.js +5 -6
- package/lib/getWorkspacePackageDirs.js.map +1 -1
- package/lib/index.d.ts +8 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +15 -4
- package/lib/index.js.map +1 -1
- package/lib/matchesAnyGlob.d.ts +17 -0
- package/lib/matchesAnyGlob.d.ts.map +1 -0
- package/lib/matchesAnyGlob.js +131 -0
- package/lib/matchesAnyGlob.js.map +1 -0
- package/lib/mutateJson.d.ts +2 -1
- package/lib/mutateJson.d.ts.map +1 -1
- package/lib/mutateJson.js +3 -5
- package/lib/mutateJson.js.map +1 -1
- package/lib/nanosecondsToSanity.d.ts +8 -0
- package/lib/nanosecondsToSanity.d.ts.map +1 -0
- package/lib/nanosecondsToSanity.js +14 -0
- package/lib/nanosecondsToSanity.js.map +1 -0
- package/package.json +8 -6
- package/src/AggregateTiming.ts +71 -0
- package/src/CachingHost.ts +489 -0
- package/src/Host.ts +34 -0
- package/src/SimpleHost.ts +57 -0
- package/src/Table.ts +319 -0
- package/src/Timing.ts +55 -0
- package/src/__tests__/CachingHost.spec.ts +244 -0
- package/src/findWorkspaceDir.ts +5 -6
- package/src/getPackageNameToDir.ts +4 -4
- package/src/getWorkspacePackageDirs.ts +7 -3
- package/src/index.ts +8 -2
- package/src/matchesAnyGlob.ts +145 -0
- package/src/mutateJson.ts +4 -5
- package/src/nanosecondsToSanity.ts +10 -0
- package/tsconfig.tsbuildinfo +1 -4046
- package/lib/readJson.d.ts +0 -8
- package/lib/readJson.d.ts.map +0 -1
- package/lib/readJson.js +0 -16
- package/lib/readJson.js.map +0 -1
- package/lib/writeJson.d.ts +0 -8
- package/lib/writeJson.d.ts.map +0 -1
- package/lib/writeJson.js +0 -15
- package/lib/writeJson.js.map +0 -1
- package/src/readJson.ts +0 -13
- package/src/writeJson.ts +0 -12
package/src/Table.ts
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
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";
|
|
9
|
+
|
|
10
|
+
type HeaderFooterHelper<HB, FB, H, F> = (HB extends true ? { header: H } : { header?: H }) &
|
|
11
|
+
(FB extends true ? { footer: F } : { footer?: F });
|
|
12
|
+
|
|
13
|
+
type WithAlignemnt = { alignment?: "right" | "left" };
|
|
14
|
+
type BaseCellConfig = WithAlignemnt & { type: "bigint" | "string" };
|
|
15
|
+
|
|
16
|
+
type BaseBigIntCellConfig = {
|
|
17
|
+
type: "bigint";
|
|
18
|
+
renderAs?: "nanoseconds";
|
|
19
|
+
precision?: number;
|
|
20
|
+
} & WithAlignemnt;
|
|
21
|
+
type BaseStringCellConfig = { type: "string" } & WithAlignemnt;
|
|
22
|
+
|
|
23
|
+
type BigIntColumnConfig<H, F> = WithAlignemnt &
|
|
24
|
+
BaseBigIntCellConfig &
|
|
25
|
+
HeaderFooterHelper<H, F, string, AggregateFooterConfig | StaticFooterConfig>;
|
|
26
|
+
|
|
27
|
+
type StringColumnConfig<H, F> = WithAlignemnt &
|
|
28
|
+
BaseStringCellConfig &
|
|
29
|
+
HeaderFooterHelper<H, F, string, StaticFooterConfig>;
|
|
30
|
+
|
|
31
|
+
type AggregateFooterConfig = {
|
|
32
|
+
aggregate: "sum" | "average";
|
|
33
|
+
renderAs?: "nanoseconds";
|
|
34
|
+
precision?: number;
|
|
35
|
+
} & WithAlignemnt;
|
|
36
|
+
|
|
37
|
+
type StaticFooterConfig = StrictStaticFooterConfig | string;
|
|
38
|
+
type StrictStaticFooterConfig = {
|
|
39
|
+
aggregate: "static";
|
|
40
|
+
value: string;
|
|
41
|
+
} & WithAlignemnt;
|
|
42
|
+
|
|
43
|
+
type AnyStrictFooterConfig = AggregateFooterConfig | StrictStaticFooterConfig;
|
|
44
|
+
|
|
45
|
+
type TableConfig<T extends any[], H extends boolean, F extends boolean> = {
|
|
46
|
+
sortColumn?: number;
|
|
47
|
+
padding?: number;
|
|
48
|
+
showHeader: H;
|
|
49
|
+
showFooter: F;
|
|
50
|
+
columns: {
|
|
51
|
+
[K in keyof T]: T[K] extends bigint ? BigIntColumnConfig<H, F> : StringColumnConfig<H, F>;
|
|
52
|
+
};
|
|
53
|
+
title: string;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
type InternalTableConfig = {
|
|
57
|
+
padding: number;
|
|
58
|
+
showHeader: boolean;
|
|
59
|
+
showFooter: boolean;
|
|
60
|
+
sortColumn?: number;
|
|
61
|
+
columns: Array<BigIntColumnConfig<any, any> | StringColumnConfig<any, any>>;
|
|
62
|
+
title: string;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export class Table<T extends any[]> {
|
|
66
|
+
#rows: T[] = [];
|
|
67
|
+
#config: InternalTableConfig;
|
|
68
|
+
#columnWidths: number[] = [];
|
|
69
|
+
#footer: Array<bigint | string> = [];
|
|
70
|
+
#footerRowConfig?: Array<Required<BaseCellConfig> & AnyStrictFooterConfig>;
|
|
71
|
+
#totalWidth = 0;
|
|
72
|
+
|
|
73
|
+
constructor(
|
|
74
|
+
config:
|
|
75
|
+
| TableConfig<T, true, true>
|
|
76
|
+
| TableConfig<T, true, false>
|
|
77
|
+
| TableConfig<T, false, true>
|
|
78
|
+
| TableConfig<T, false, false>
|
|
79
|
+
) {
|
|
80
|
+
this.#config = {
|
|
81
|
+
padding: 2,
|
|
82
|
+
...config,
|
|
83
|
+
};
|
|
84
|
+
this.#columnWidths.fill(0, 0, config.columns.length);
|
|
85
|
+
|
|
86
|
+
if (config.showFooter) {
|
|
87
|
+
this.#footerRowConfig = [];
|
|
88
|
+
for (const columnConfig of config.columns) {
|
|
89
|
+
if (columnConfig.footer === undefined) {
|
|
90
|
+
throw new Error("Must specify footer fields when showFooter is true");
|
|
91
|
+
} else if (typeof columnConfig.footer === "string") {
|
|
92
|
+
this.#footerRowConfig.push({
|
|
93
|
+
type: "string",
|
|
94
|
+
alignment: "left",
|
|
95
|
+
aggregate: "static",
|
|
96
|
+
value: columnConfig.footer,
|
|
97
|
+
});
|
|
98
|
+
} else if ("value" in columnConfig.footer) {
|
|
99
|
+
this.#footerRowConfig.push({
|
|
100
|
+
type: "string",
|
|
101
|
+
alignment: "left",
|
|
102
|
+
...columnConfig.footer,
|
|
103
|
+
});
|
|
104
|
+
} else if ("aggregate" in columnConfig.footer) {
|
|
105
|
+
if (columnConfig.type !== "bigint") throw new Error("expecting bigint for aggregate");
|
|
106
|
+
this.#footerRowConfig.push({
|
|
107
|
+
type: columnConfig.type,
|
|
108
|
+
renderAs: columnConfig.renderAs,
|
|
109
|
+
precision: columnConfig.precision,
|
|
110
|
+
alignment: "right",
|
|
111
|
+
...columnConfig.footer,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
addRow(...data: T) {
|
|
119
|
+
// TODO: maybe clone the data
|
|
120
|
+
this.#rows.push(data);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
#sumColumn(c: number) {
|
|
124
|
+
let total = BigInt(0);
|
|
125
|
+
for (const row of this.#rows) {
|
|
126
|
+
total += row[c];
|
|
127
|
+
}
|
|
128
|
+
return total;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
#updateFooterRow() {
|
|
132
|
+
const footerRowConfig = this.#footerRowConfig;
|
|
133
|
+
if (footerRowConfig) {
|
|
134
|
+
for (let c = 0; c < footerRowConfig.length; c++) {
|
|
135
|
+
const footerColConfig = footerRowConfig[c];
|
|
136
|
+
|
|
137
|
+
switch (footerColConfig.aggregate) {
|
|
138
|
+
case "sum":
|
|
139
|
+
this.#footer[c] = this.#sumColumn(c);
|
|
140
|
+
break;
|
|
141
|
+
case "average":
|
|
142
|
+
this.#footer[c] = this.#sumColumn(c) / BigInt(this.#rows.length);
|
|
143
|
+
break;
|
|
144
|
+
case "static":
|
|
145
|
+
this.#footer[c] = footerColConfig.value;
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
#calculateColumnWidths() {
|
|
153
|
+
this.#columnWidths.fill(0, 0, this.#config.columns.length);
|
|
154
|
+
|
|
155
|
+
for (let c = 0; c < this.#config.columns.length; c++) {
|
|
156
|
+
const colConfig = this.#config.columns[c];
|
|
157
|
+
this.#columnWidths[c] = Math.max(
|
|
158
|
+
(this.#config.columns[c].header ?? "").length,
|
|
159
|
+
...this.#rows.map((a) => this.#getCellValueAsString(a[c], colConfig).length),
|
|
160
|
+
this.#footer && this.#footerRowConfig
|
|
161
|
+
? this.#getCellValueAsString(this.#footer?.[c] ?? "", this.#footerRowConfig[c]).length
|
|
162
|
+
: 0
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
this.#totalWidth = 0;
|
|
167
|
+
for (const colWidth of this.#columnWidths) {
|
|
168
|
+
this.#totalWidth += colWidth;
|
|
169
|
+
}
|
|
170
|
+
this.#totalWidth += (this.#columnWidths.length - 1) * this.#config.padding;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
#printSeparator(fillString: string) {
|
|
174
|
+
const paddingString = "".padStart(this.#config.padding, " ");
|
|
175
|
+
|
|
176
|
+
let hr2 = "";
|
|
177
|
+
|
|
178
|
+
// tslint:disable-next-line: prefer-for-of
|
|
179
|
+
for (let c = 0; c < this.#columnWidths.length; c++) {
|
|
180
|
+
hr2 += "".padStart(this.#columnWidths[c], fillString) + paddingString;
|
|
181
|
+
}
|
|
182
|
+
hr2 = hr2.trimRight();
|
|
183
|
+
console.log(hr2);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#printHeaderRow() {
|
|
187
|
+
if (this.#config.showHeader) {
|
|
188
|
+
const colConfigs = this.#config.columns;
|
|
189
|
+
const paddingString = "".padStart(this.#config.padding, " ");
|
|
190
|
+
|
|
191
|
+
let hr = "";
|
|
192
|
+
for (let c = 0; c < colConfigs.length; c++) {
|
|
193
|
+
const heading = colConfigs[c].header ?? "";
|
|
194
|
+
hr += heading.padEnd(this.#columnWidths[c], " ") + paddingString;
|
|
195
|
+
}
|
|
196
|
+
hr = hr.trimRight();
|
|
197
|
+
console.log(hr);
|
|
198
|
+
|
|
199
|
+
this.#printSeparator("-");
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
#printFooterRow() {
|
|
204
|
+
const footerRow = this.#footer;
|
|
205
|
+
if (footerRow) {
|
|
206
|
+
this.#printSeparator("=");
|
|
207
|
+
|
|
208
|
+
const paddingString = "".padStart(this.#config.padding, " ");
|
|
209
|
+
|
|
210
|
+
let hr = "";
|
|
211
|
+
for (let c = 0; c < footerRow.length; c++) {
|
|
212
|
+
hr += this.#getCellValueAligned(footerRow[c], this.#footerRowConfig![c], c) + paddingString; // .padEnd(this.#columnWidths[c], " ") + paddingString;
|
|
213
|
+
}
|
|
214
|
+
hr = hr.trimRight();
|
|
215
|
+
console.log(hr);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
print() {
|
|
220
|
+
// let data = [...this.#rows];
|
|
221
|
+
if (this.#config.sortColumn !== undefined) {
|
|
222
|
+
// todo
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
this.#updateFooterRow();
|
|
226
|
+
this.#calculateColumnWidths();
|
|
227
|
+
|
|
228
|
+
console.log();
|
|
229
|
+
console.log(`${this.#config.title}`);
|
|
230
|
+
console.log("".padStart(this.#totalWidth, "="));
|
|
231
|
+
|
|
232
|
+
const paddingString = "".padStart(this.#config.padding, " ");
|
|
233
|
+
if (this.#config.showHeader) {
|
|
234
|
+
this.#printHeaderRow();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
for (let r = 0; r < this.#rows.length; r++) {
|
|
238
|
+
let rowText = "";
|
|
239
|
+
for (let c = 0; c < this.#config.columns.length; c++) {
|
|
240
|
+
rowText += this.getEntryAsStringAligned(c, r) + paddingString;
|
|
241
|
+
}
|
|
242
|
+
rowText.trim();
|
|
243
|
+
console.log(rowText);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (this.#config.showFooter) this.#printFooterRow();
|
|
247
|
+
console.log();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
#getCellValueAsString(value: bigint | string, config: BaseBigIntCellConfig | BaseStringCellConfig) {
|
|
251
|
+
if (config.type === "bigint" && config.renderAs === "nanoseconds") {
|
|
252
|
+
return nanosecondsToSanity(value as bigint, config.precision ?? 9);
|
|
253
|
+
} else {
|
|
254
|
+
return "" + value;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
#getCellValueAligned(value: bigint | string, config: BaseBigIntCellConfig | BaseStringCellConfig, column: number) {
|
|
259
|
+
let result: string;
|
|
260
|
+
if (config.type === "bigint" && config.renderAs === "nanoseconds") {
|
|
261
|
+
result = nanosecondsToSanity(value as bigint, config.precision ?? 9);
|
|
262
|
+
} else {
|
|
263
|
+
result = "" + value;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (config.alignment === "left") {
|
|
267
|
+
return result.padEnd(this.#columnWidths[column]);
|
|
268
|
+
} else {
|
|
269
|
+
return result.padStart(this.#columnWidths[column]);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
getEntryAsString(colNum: number, rowNum: number) {
|
|
274
|
+
const config = this.#config.columns[colNum];
|
|
275
|
+
|
|
276
|
+
if (config.type === "bigint" && config.renderAs === "nanoseconds") {
|
|
277
|
+
return nanosecondsToSanity(this.#rows[rowNum][colNum], config.precision ?? 9);
|
|
278
|
+
} else {
|
|
279
|
+
return "" + this.#rows[rowNum][colNum];
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
getEntryAsStringAligned(colNum: number, rowNum: number) {
|
|
284
|
+
const config = this.#config.columns[colNum];
|
|
285
|
+
|
|
286
|
+
let result: string;
|
|
287
|
+
if (config.type === "bigint" && config.renderAs === "nanoseconds") {
|
|
288
|
+
result = nanosecondsToSanity(this.#rows[rowNum][colNum], config.precision ?? 9);
|
|
289
|
+
} else {
|
|
290
|
+
result = "" + this.#rows[rowNum][colNum];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (config.alignment === "left") {
|
|
294
|
+
return result.padEnd(this.#columnWidths[colNum]);
|
|
295
|
+
} else {
|
|
296
|
+
return result.padStart(this.#columnWidths[colNum]);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
getColumnWidth(colNum: number, config: BigIntColumnConfig<boolean, boolean> | StringColumnConfig<boolean, boolean>) {
|
|
301
|
+
let maxWidth = Math.max(
|
|
302
|
+
(config.header ?? "").length,
|
|
303
|
+
this.#footer && this.#footerRowConfig
|
|
304
|
+
? this.#getCellValueAsString(this.#footer[colNum], this.#footerRowConfig[colNum]).length
|
|
305
|
+
: 0
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
for (let r = 0; r < this.#rows.length; r++) {
|
|
309
|
+
maxWidth = Math.max(maxWidth, this.getEntryAsString(colNum, r).length);
|
|
310
|
+
// if (config.type == "bigint" && config.renderAs === "nanoseconds") {
|
|
311
|
+
// maxWidth = Math.max(maxWidth, Number(row[colNum] / BigInt(1000000000)) + 10); // 1 for period, 9 for digits
|
|
312
|
+
// } else if (config.type == "bigint" || config.type == "string") {
|
|
313
|
+
// maxWidth = Math.max(maxWidth, ("" + row[colNum]).length);
|
|
314
|
+
// }
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return maxWidth;
|
|
318
|
+
}
|
|
319
|
+
}
|
package/src/Timing.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
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";
|
|
9
|
+
|
|
10
|
+
export class Timing {
|
|
11
|
+
#starts: Array<{ name?: string; start: bigint }> = [];
|
|
12
|
+
constructor(private title: string) {
|
|
13
|
+
this.stop(); // make sure we have starting point
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public start(name: string) {
|
|
17
|
+
this.#starts.push({ name, start: process.hrtime.bigint() });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public stop() {
|
|
21
|
+
this.#starts.push({ start: process.hrtime.bigint() });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public printResults() {
|
|
25
|
+
const table = new Table<[bigint, string]>({
|
|
26
|
+
sortColumn: -1,
|
|
27
|
+
showFooter: true,
|
|
28
|
+
showHeader: true,
|
|
29
|
+
title: this.title,
|
|
30
|
+
columns: [
|
|
31
|
+
{
|
|
32
|
+
header: "Duration",
|
|
33
|
+
type: "bigint",
|
|
34
|
+
renderAs: "nanoseconds",
|
|
35
|
+
precision: 4,
|
|
36
|
+
footer: { aggregate: "sum" },
|
|
37
|
+
},
|
|
38
|
+
{ header: "Task", type: "string", footer: "TOTAL" },
|
|
39
|
+
],
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
this.stop(); // be sure we stopped the last one
|
|
43
|
+
|
|
44
|
+
let cur: { name?: string; start: bigint } = this.#starts[0];
|
|
45
|
+
for (const entry of this.#starts) {
|
|
46
|
+
if (cur.name) {
|
|
47
|
+
const span = entry.start - cur.start;
|
|
48
|
+
table.addRow(span, cur.name);
|
|
49
|
+
}
|
|
50
|
+
cur = entry;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
table.print();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -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
|
+
|
|
8
|
+
import { CachingHost } from "../CachingHost";
|
|
9
|
+
import * as realfs from "fs";
|
|
10
|
+
import * as path from "path";
|
|
11
|
+
import * as os from "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
|
+
});
|
package/src/findWorkspaceDir.ts
CHANGED
|
@@ -5,15 +5,14 @@
|
|
|
5
5
|
*
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { existsSync } from "fs";
|
|
9
8
|
import * as path from "path";
|
|
9
|
+
import { Host } from "./Host";
|
|
10
10
|
import { PackageJson } from "./PackageJson";
|
|
11
|
-
import { readJson } from "./readJson";
|
|
12
11
|
|
|
13
|
-
export function findWorkspaceDir(dir: string): string | undefined {
|
|
12
|
+
export function findWorkspaceDir(host: Pick<Host, "readJson" | "exists">, dir: string): string | undefined {
|
|
14
13
|
const packagePath = path.join(dir, "package.json");
|
|
15
|
-
if (
|
|
16
|
-
const packageJson = readJson(packagePath) as PackageJson;
|
|
14
|
+
if (host.exists(packagePath)) {
|
|
15
|
+
const packageJson = host.readJson(packagePath) as PackageJson;
|
|
17
16
|
if (packageJson.workspaces !== undefined) {
|
|
18
17
|
return dir;
|
|
19
18
|
}
|
|
@@ -24,5 +23,5 @@ export function findWorkspaceDir(dir: string): string | undefined {
|
|
|
24
23
|
return undefined;
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
return findWorkspaceDir(nextDir);
|
|
26
|
+
return findWorkspaceDir(host, nextDir);
|
|
28
27
|
}
|
|
@@ -7,20 +7,20 @@
|
|
|
7
7
|
|
|
8
8
|
import { join as pathJoin } from "path";
|
|
9
9
|
import { getWorkspacePackageDirs } from "./getWorkspacePackageDirs";
|
|
10
|
+
import { Host } from "./Host";
|
|
10
11
|
import { PackageJson } from "./PackageJson";
|
|
11
|
-
import { readJson } from "./readJson";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* returns a map of package names to their directories in the workspace.
|
|
15
15
|
* if `resolvePaths` is true, the returned directory names are absolute paths
|
|
16
16
|
* resolved against the `workspaceDir`.
|
|
17
17
|
*/
|
|
18
|
-
export function getPackageNameToDir(workspaceDir: string, resolvePaths: boolean = false) {
|
|
18
|
+
export function getPackageNameToDir(host: Pick<Host, "readJson">, workspaceDir: string, resolvePaths: boolean = false) {
|
|
19
19
|
const ret = new Map<string, string>();
|
|
20
20
|
|
|
21
|
-
for (const packageDir of getWorkspacePackageDirs(workspaceDir, resolvePaths)) {
|
|
21
|
+
for (const packageDir of getWorkspacePackageDirs(host, workspaceDir, resolvePaths)) {
|
|
22
22
|
const packagePath = pathJoin(packageDir, "package.json");
|
|
23
|
-
const { name } = readJson(packagePath) as PackageJson;
|
|
23
|
+
const { name } = host.readJson(packagePath) as PackageJson;
|
|
24
24
|
if (name === undefined) {
|
|
25
25
|
throw new Error(`Package needs a name: ${packagePath}`);
|
|
26
26
|
}
|
|
@@ -8,13 +8,17 @@
|
|
|
8
8
|
import { existsSync } from "fs";
|
|
9
9
|
import glob from "glob";
|
|
10
10
|
import { join as pathJoin, resolve as pathResolve } from "path";
|
|
11
|
+
import { Host } from "./Host";
|
|
11
12
|
import { PackageJson } from "./PackageJson";
|
|
12
|
-
import { readJson } from "./readJson";
|
|
13
13
|
|
|
14
|
-
export function getWorkspacePackageDirs(
|
|
14
|
+
export function getWorkspacePackageDirs(
|
|
15
|
+
host: Pick<Host, "readJson">,
|
|
16
|
+
workspaceDir: string,
|
|
17
|
+
resolvePaths: boolean = false
|
|
18
|
+
) {
|
|
15
19
|
const ret: string[] = [];
|
|
16
20
|
|
|
17
|
-
const packageJson: PackageJson = readJson(pathJoin(workspaceDir, "package.json"));
|
|
21
|
+
const packageJson: PackageJson = host.readJson(pathJoin(workspaceDir, "package.json"));
|
|
18
22
|
|
|
19
23
|
if (packageJson.workspaces === undefined) {
|
|
20
24
|
throw new Error("Invalid workspaceDir: " + workspaceDir);
|