@monorepolint/utils 0.6.0-alpha.1 → 0.6.0-alpha.3
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/.turbo/turbo-clean.log +1 -1
- package/.turbo/turbo-compile-typescript.log +1 -1
- package/.turbo/turbo-lint.log +7 -7
- package/.turbo/turbo-test.log +36 -23
- package/.turbo/turbo-transpile-typescript.log +5 -5
- package/CHANGELOG.md +28 -0
- package/build/js/index.js +502 -461
- package/build/js/index.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/types/CachingHost.d.ts +0 -2
- package/build/types/CachingHost.d.ts.map +1 -1
- package/build/types/Host.d.ts +0 -1
- package/build/types/Host.d.ts.map +1 -1
- package/build/types/PackageJson.d.ts.map +1 -1
- package/build/types/SimpleHost.d.ts +0 -2
- package/build/types/SimpleHost.d.ts.map +1 -1
- package/build/types/Table.d.ts.map +1 -1
- package/build/types/findWorkspaceDir.d.ts.map +1 -1
- package/build/types/getPackageNameToDir.d.ts.map +1 -1
- package/build/types/getWorkspacePackageDirs.d.ts.map +1 -1
- package/build/types/index.d.ts +7 -7
- package/build/types/index.d.ts.map +1 -1
- package/build/types/matchesAnyGlob.d.ts.map +1 -1
- package/build/types/mutateJson.d.ts.map +1 -1
- package/build/types/nanosecondsToSanity.d.ts.map +1 -1
- package/coverage/AggregateTiming.ts.html +295 -0
- package/coverage/CachingHost.ts.html +1786 -0
- package/coverage/Host.ts.html +199 -0
- package/coverage/PackageJson.ts.html +151 -0
- package/coverage/SimpleHost.ts.html +286 -0
- package/coverage/Table.ts.html +1156 -0
- package/coverage/Timing.ts.html +247 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/clover.xml +1272 -0
- package/coverage/coverage-final.json +15 -0
- package/coverage/favicon.png +0 -0
- package/coverage/findWorkspaceDir.ts.html +223 -0
- package/coverage/getPackageNameToDir.ts.html +199 -0
- package/coverage/getWorkspacePackageDirs.ts.html +331 -0
- package/coverage/index.html +311 -0
- package/coverage/index.ts.html +145 -0
- package/coverage/matchesAnyGlob.ts.html +550 -0
- package/coverage/mutateJson.ts.html +136 -0
- package/coverage/nanosecondsToSanity.ts.html +121 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +196 -0
- package/package.json +12 -17
- package/src/AggregateTiming.ts +1 -1
- package/src/CachingHost.ts +110 -34
- package/src/Host.ts +5 -1
- package/src/PackageJson.ts +3 -3
- package/src/SimpleHost.ts +14 -3
- package/src/Table.ts +62 -23
- package/src/__tests__/CachingHost.spec.ts +203 -166
- package/src/findWorkspaceDir.ts +3 -3
- package/src/getPackageNameToDir.ts +6 -2
- package/src/getWorkspacePackageDirs.ts +22 -10
- package/src/index.ts +7 -7
- package/src/matchesAnyGlob.ts +12 -3
- package/src/mutateJson.ts +5 -1
- package/src/nanosecondsToSanity.ts +3 -1
- package/vitest.config.mjs +17 -0
- package/vitest.config.mjs.timestamp-1736878329730-aa478e2241542.mjs +18 -0
- package/jest.config.cjs +0 -4
package/src/CachingHost.ts
CHANGED
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import * as realFs from "node:fs";
|
|
9
|
-
import { Host } from "./Host.js";
|
|
10
9
|
import * as path from "node:path";
|
|
10
|
+
import { Host } from "./Host.js";
|
|
11
11
|
|
|
12
|
-
function assertNoTombstone(
|
|
12
|
+
function assertNoTombstone(
|
|
13
|
+
node: Node,
|
|
14
|
+
): asserts node is Node & { tombstone?: false } {
|
|
13
15
|
if (node.tombstone) {
|
|
14
16
|
throw new Error(`Unexpected tombstone ${JSON.stringify(node)}`);
|
|
15
17
|
}
|
|
@@ -17,14 +19,17 @@ function assertNoTombstone(node: Node): asserts node is Node & { tombstone?: fal
|
|
|
17
19
|
|
|
18
20
|
function assertNotType<N extends Node, T extends Node["type"]>(
|
|
19
21
|
node: N,
|
|
20
|
-
type: T
|
|
22
|
+
type: T,
|
|
21
23
|
): asserts node is N & { type: Exclude<N["type"], T> } {
|
|
22
24
|
if (node.type === type) {
|
|
23
25
|
throw new Error(`Unexpected node type ${JSON.stringify(node)}`);
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
function assertType<N extends Node, T extends Node["type"]>(
|
|
29
|
+
function assertType<N extends Node, T extends Node["type"]>(
|
|
30
|
+
node: N,
|
|
31
|
+
type: T,
|
|
32
|
+
): asserts node is N & { type: T } {
|
|
28
33
|
if (node.type !== type) {
|
|
29
34
|
throw new Error(`Unexpected node type ${JSON.stringify(node)}`);
|
|
30
35
|
}
|
|
@@ -90,7 +95,14 @@ interface SymlinkNode extends BaseNode<"symlink"> {
|
|
|
90
95
|
symlink: string;
|
|
91
96
|
}
|
|
92
97
|
|
|
93
|
-
type Node =
|
|
98
|
+
type Node =
|
|
99
|
+
| DirNode
|
|
100
|
+
| FileNode
|
|
101
|
+
| SymlinkNode
|
|
102
|
+
| DirTombstoneNode
|
|
103
|
+
| FileTombstoneNode
|
|
104
|
+
| DirStubNode
|
|
105
|
+
| FileStubNode;
|
|
94
106
|
|
|
95
107
|
export class CachingHost implements Host {
|
|
96
108
|
// We need many trees because of windows, key is the `root`
|
|
@@ -111,23 +123,29 @@ export class CachingHost implements Host {
|
|
|
111
123
|
| "statSync"
|
|
112
124
|
| "unlinkSync"
|
|
113
125
|
| "writeFileSync"
|
|
114
|
-
> = realFs
|
|
126
|
+
> = realFs,
|
|
115
127
|
) {}
|
|
116
128
|
|
|
117
129
|
#replaceNode(
|
|
118
130
|
node: FileNode | FileStubNode | SymlinkNode,
|
|
119
|
-
newNode: Omit<FileTombstoneNode, "fullPath" | "parent"
|
|
131
|
+
newNode: Omit<FileTombstoneNode, "fullPath" | "parent">,
|
|
120
132
|
): FileTombstoneNode;
|
|
121
133
|
#replaceNode(
|
|
122
134
|
node: FileNode | FileStubNode | FileTombstoneNode,
|
|
123
|
-
newNode: Omit<FileNode, "fullPath" | "parent"
|
|
135
|
+
newNode: Omit<FileNode, "fullPath" | "parent">,
|
|
124
136
|
): FileNode;
|
|
125
137
|
#replaceNode(
|
|
126
138
|
node: DirTombstoneNode | DirStubNode,
|
|
127
|
-
newNode: Omit<DirNode, "fullPath" | "parent" | "dir"
|
|
139
|
+
newNode: Omit<DirNode, "fullPath" | "parent" | "dir">,
|
|
128
140
|
): DirStubNode;
|
|
129
|
-
#replaceNode(
|
|
130
|
-
|
|
141
|
+
#replaceNode(
|
|
142
|
+
node: DirNode,
|
|
143
|
+
newNode: Omit<DirTombstoneNode, "fullPath" | "parent" | "dir">,
|
|
144
|
+
): DirTombstoneNode;
|
|
145
|
+
#replaceNode(
|
|
146
|
+
node: Node,
|
|
147
|
+
partialNewNode: Omit<Node, "fullPath" | "parent">,
|
|
148
|
+
): Node {
|
|
131
149
|
if (!node.parent) throw new Error("Cannot replace root node");
|
|
132
150
|
const newNode: Node = {
|
|
133
151
|
...partialNewNode,
|
|
@@ -156,15 +174,22 @@ export class CachingHost implements Host {
|
|
|
156
174
|
* Throws if the path doesnt exist!
|
|
157
175
|
*/
|
|
158
176
|
#stubify(filePath: string, parent: undefined): DirStubNode;
|
|
159
|
-
#stubify(filePath: string, parent: DirNode | DirStubNode | undefined): DirStubNode | SymlinkNode | FileStubNode;
|
|
160
177
|
#stubify(
|
|
161
178
|
filePath: string,
|
|
162
|
-
parent: DirNode | DirStubNode | undefined
|
|
163
|
-
):
|
|
179
|
+
parent: DirNode | DirStubNode | undefined,
|
|
180
|
+
): DirStubNode | SymlinkNode | FileStubNode;
|
|
181
|
+
#stubify(
|
|
182
|
+
filePath: string,
|
|
183
|
+
parent: DirNode | DirStubNode | undefined,
|
|
184
|
+
): typeof parent extends undefined ? DirNode | DirStubNode
|
|
185
|
+
: DirNode | DirStubNode | SymlinkNode | FileStubNode
|
|
186
|
+
{
|
|
164
187
|
const canonicalPath = path.resolve(filePath);
|
|
165
188
|
|
|
166
189
|
if (!parent && canonicalPath !== path.parse(canonicalPath).root) {
|
|
167
|
-
throw new Error(
|
|
190
|
+
throw new Error(
|
|
191
|
+
`parent can only be null if path is root. Instead got: ${canonicalPath}`,
|
|
192
|
+
);
|
|
168
193
|
}
|
|
169
194
|
const stat = this.fs.lstatSync(canonicalPath); // may throw
|
|
170
195
|
|
|
@@ -196,7 +221,9 @@ export class CachingHost implements Host {
|
|
|
196
221
|
needsFlush: false,
|
|
197
222
|
};
|
|
198
223
|
} else {
|
|
199
|
-
throw new Error(
|
|
224
|
+
throw new Error(
|
|
225
|
+
`what is not a file nor symlink nor directory? nothing we care about: ${canonicalPath}`,
|
|
226
|
+
);
|
|
200
227
|
}
|
|
201
228
|
|
|
202
229
|
if (!parent && node.type === "dir") {
|
|
@@ -205,7 +232,11 @@ export class CachingHost implements Host {
|
|
|
205
232
|
} else if (parent) {
|
|
206
233
|
parent.dir.set(path.basename(canonicalPath), node);
|
|
207
234
|
} else {
|
|
208
|
-
throw new Error(
|
|
235
|
+
throw new Error(
|
|
236
|
+
`root can only be a dir, got ${
|
|
237
|
+
JSON.stringify(node)
|
|
238
|
+
} for path: ${canonicalPath}`,
|
|
239
|
+
);
|
|
209
240
|
}
|
|
210
241
|
return node;
|
|
211
242
|
}
|
|
@@ -226,13 +257,16 @@ export class CachingHost implements Host {
|
|
|
226
257
|
}
|
|
227
258
|
|
|
228
259
|
let curPath = root;
|
|
229
|
-
let curNode: Node = this.#trees.get(root)
|
|
260
|
+
let curNode: Node = this.#trees.get(root)
|
|
261
|
+
?? this.#stubify(curPath, undefined); // its okay to throw if there is no root
|
|
230
262
|
try {
|
|
231
263
|
for (const part of parts) {
|
|
232
264
|
assertNoTombstone(curNode);
|
|
233
265
|
assertNotType(curNode, "file");
|
|
234
266
|
if (curNode.type === "symlink") {
|
|
235
|
-
const linkedNode = this.#getNodeResolvingSymlinks(
|
|
267
|
+
const linkedNode = this.#getNodeResolvingSymlinks(
|
|
268
|
+
path.resolve(path.dirname(curPath), curNode.symlink),
|
|
269
|
+
);
|
|
236
270
|
assertExists(linkedNode);
|
|
237
271
|
assertNoTombstone(linkedNode);
|
|
238
272
|
assertType(linkedNode, "dir");
|
|
@@ -240,9 +274,11 @@ export class CachingHost implements Host {
|
|
|
240
274
|
}
|
|
241
275
|
assertType(curNode, "dir");
|
|
242
276
|
assertNoTombstone(curNode);
|
|
243
|
-
curNode = curNode.dir.get(part)
|
|
277
|
+
curNode = curNode.dir.get(part)
|
|
278
|
+
?? this.#stubify(path.join(curNode.fullPath, part), curNode);
|
|
244
279
|
curPath = path.join(curPath, part);
|
|
245
280
|
}
|
|
281
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
246
282
|
} catch (e) {
|
|
247
283
|
// This error is expected when things done exist.
|
|
248
284
|
// console.log(`Got EXPECTED error when trying to getNearestAncestorNode(${canonicalPath}): `, (e as any).message);
|
|
@@ -252,14 +288,19 @@ export class CachingHost implements Host {
|
|
|
252
288
|
|
|
253
289
|
#getNode(filePath: string) {
|
|
254
290
|
const canonicalPath = path.resolve(filePath);
|
|
255
|
-
const { pathWithSymlinks, node } = this.#getNearestAncestorNode(
|
|
291
|
+
const { pathWithSymlinks, node } = this.#getNearestAncestorNode(
|
|
292
|
+
canonicalPath,
|
|
293
|
+
);
|
|
256
294
|
if (pathWithSymlinks === canonicalPath) {
|
|
257
295
|
return node;
|
|
258
296
|
}
|
|
259
297
|
return undefined;
|
|
260
298
|
}
|
|
261
299
|
|
|
262
|
-
#getNodeResolvingSymlinks(
|
|
300
|
+
#getNodeResolvingSymlinks(
|
|
301
|
+
filePath: string,
|
|
302
|
+
follows: number = 100,
|
|
303
|
+
): Exclude<Node, SymlinkNode> | undefined {
|
|
263
304
|
const node = this.#getNode(filePath); // canonicalizes for us
|
|
264
305
|
if (!node || node.type !== "symlink") return node;
|
|
265
306
|
// this is a really poor mans way of doing this. but who has 100's of symlinks hanging around?
|
|
@@ -268,9 +309,14 @@ export class CachingHost implements Host {
|
|
|
268
309
|
return this.#getNodeResolvingSymlinks(node.symlink, follows--);
|
|
269
310
|
}
|
|
270
311
|
|
|
271
|
-
mkdir(
|
|
312
|
+
mkdir(
|
|
313
|
+
filePath: string,
|
|
314
|
+
opts: { recursive: boolean } = { recursive: false },
|
|
315
|
+
): void {
|
|
272
316
|
const canonicalPath = path.resolve(filePath);
|
|
273
|
-
const { node, pathWithSymlinks } = this.#getNearestAncestorNode(
|
|
317
|
+
const { node, pathWithSymlinks } = this.#getNearestAncestorNode(
|
|
318
|
+
canonicalPath,
|
|
319
|
+
);
|
|
274
320
|
if (filePath === pathWithSymlinks) {
|
|
275
321
|
assertType(node, "dir");
|
|
276
322
|
assertHasParent(node);
|
|
@@ -296,7 +342,9 @@ export class CachingHost implements Host {
|
|
|
296
342
|
let maybePath = canonicalPath;
|
|
297
343
|
const toMake: string[] = [];
|
|
298
344
|
while (maybePath !== rootPath) {
|
|
299
|
-
toMake.unshift(
|
|
345
|
+
toMake.unshift(
|
|
346
|
+
path.resolve(node.fullPath, path.relative(rootPath, maybePath)),
|
|
347
|
+
);
|
|
300
348
|
maybePath = path.dirname(maybePath);
|
|
301
349
|
}
|
|
302
350
|
|
|
@@ -336,7 +384,10 @@ export class CachingHost implements Host {
|
|
|
336
384
|
readFile(filePath: string, opts: { asJson: true }): object;
|
|
337
385
|
readFile(
|
|
338
386
|
filePath: string,
|
|
339
|
-
opts: undefined | { encoding: BufferEncoding; asJson?: false } | {
|
|
387
|
+
opts: undefined | { encoding: BufferEncoding; asJson?: false } | {
|
|
388
|
+
encoding?: never;
|
|
389
|
+
asJson: true;
|
|
390
|
+
},
|
|
340
391
|
) {
|
|
341
392
|
let node = this.#getNodeResolvingSymlinks(filePath); // canonicalizes for us
|
|
342
393
|
|
|
@@ -364,10 +415,24 @@ export class CachingHost implements Host {
|
|
|
364
415
|
}
|
|
365
416
|
|
|
366
417
|
writeFile(filePath: string, buffer: Buffer): void;
|
|
367
|
-
writeFile(
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
418
|
+
writeFile(
|
|
419
|
+
filePath: string,
|
|
420
|
+
body: string,
|
|
421
|
+
opts: { encoding: BufferEncoding },
|
|
422
|
+
): void;
|
|
423
|
+
writeFile(
|
|
424
|
+
filePath: string,
|
|
425
|
+
body: string,
|
|
426
|
+
opts: { encoding: BufferEncoding },
|
|
427
|
+
): void;
|
|
428
|
+
writeFile(
|
|
429
|
+
filePath: string,
|
|
430
|
+
body: string | Buffer,
|
|
431
|
+
opts?: { encoding: BufferEncoding },
|
|
432
|
+
) {
|
|
433
|
+
const fileContentsAsBuffer = typeof body === "string"
|
|
434
|
+
? Buffer.from(body, opts?.encoding)
|
|
435
|
+
: Buffer.from(body);
|
|
371
436
|
|
|
372
437
|
const canonicalPath = path.resolve(filePath);
|
|
373
438
|
const existingNode = this.#getNodeResolvingSymlinks(canonicalPath);
|
|
@@ -383,7 +448,9 @@ export class CachingHost implements Host {
|
|
|
383
448
|
return;
|
|
384
449
|
}
|
|
385
450
|
|
|
386
|
-
const maybeDirNode = this.#getNodeResolvingSymlinks(
|
|
451
|
+
const maybeDirNode = this.#getNodeResolvingSymlinks(
|
|
452
|
+
path.dirname(canonicalPath),
|
|
453
|
+
);
|
|
387
454
|
assertExists(maybeDirNode);
|
|
388
455
|
assertType(maybeDirNode, "dir");
|
|
389
456
|
assertNoTombstone(maybeDirNode);
|
|
@@ -419,12 +486,15 @@ export class CachingHost implements Host {
|
|
|
419
486
|
});
|
|
420
487
|
}
|
|
421
488
|
|
|
422
|
-
async #flushFileNode(
|
|
489
|
+
async #flushFileNode(
|
|
490
|
+
node: FileNode | FileStubNode | FileTombstoneNode,
|
|
491
|
+
): Promise<unknown> {
|
|
423
492
|
// FIXME all tombstones need a flush, so we can get rid of needsFlush for them
|
|
424
493
|
if (node.tombstone) {
|
|
425
494
|
try {
|
|
426
495
|
await this.fs.promises.access(node.fullPath);
|
|
427
496
|
return this.fs.promises.unlink(node.fullPath);
|
|
497
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
428
498
|
} catch (e) {
|
|
429
499
|
// should only throw if file doesnt exist which is no op
|
|
430
500
|
return;
|
|
@@ -444,16 +514,20 @@ export class CachingHost implements Host {
|
|
|
444
514
|
if (linkValue === node.symlink) {
|
|
445
515
|
return;
|
|
446
516
|
}
|
|
517
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
447
518
|
} catch (e) {
|
|
448
519
|
// expected when the link doesnt exist
|
|
449
520
|
}
|
|
450
521
|
return this.fs.promises.symlink(node.symlink, node.fullPath);
|
|
451
522
|
}
|
|
452
523
|
|
|
453
|
-
async #flushDirNode(
|
|
524
|
+
async #flushDirNode(
|
|
525
|
+
node: DirNode | DirStubNode | DirTombstoneNode,
|
|
526
|
+
): Promise<unknown> {
|
|
454
527
|
if (!node.tombstone && node.needsFlush) {
|
|
455
528
|
try {
|
|
456
529
|
await this.fs.promises.access(node.fullPath); // throws if the file doesnt exist
|
|
530
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
457
531
|
} catch (e) {
|
|
458
532
|
await this.fs.promises.mkdir(node.fullPath); // throws if it does :(
|
|
459
533
|
}
|
|
@@ -462,7 +536,9 @@ export class CachingHost implements Host {
|
|
|
462
536
|
const promises: Promise<unknown>[] = [];
|
|
463
537
|
for (const child of node.dir.values()) {
|
|
464
538
|
if (node.tombstone && !child.tombstone) {
|
|
465
|
-
throw new Error(
|
|
539
|
+
throw new Error(
|
|
540
|
+
"Unexpected failure during sanity check. A non-deleted child is on a deleted dir",
|
|
541
|
+
);
|
|
466
542
|
}
|
|
467
543
|
if (child.type === "dir") {
|
|
468
544
|
promises.push(this.#flushDirNode(child));
|
package/src/Host.ts
CHANGED
|
@@ -19,7 +19,11 @@ export interface Host {
|
|
|
19
19
|
writeJson(path: string, o: object): void;
|
|
20
20
|
|
|
21
21
|
writeFile(path: string, buffer: Buffer): void;
|
|
22
|
-
writeFile(
|
|
22
|
+
writeFile(
|
|
23
|
+
path: string,
|
|
24
|
+
body: string,
|
|
25
|
+
opts: { encoding: BufferEncoding },
|
|
26
|
+
): void;
|
|
23
27
|
|
|
24
28
|
readFile(path: string, opts?: undefined): Buffer;
|
|
25
29
|
readFile(path: string, opts: { encoding: BufferEncoding }): string;
|
package/src/PackageJson.ts
CHANGED
|
@@ -14,9 +14,9 @@ export interface PackageJson {
|
|
|
14
14
|
optionalDependencies?: Record<string, string>;
|
|
15
15
|
workspaces?:
|
|
16
16
|
| {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
packages?: string[];
|
|
18
|
+
nohoist?: string[];
|
|
19
|
+
}
|
|
20
20
|
| string[];
|
|
21
21
|
[otherKey: string]: unknown;
|
|
22
22
|
}
|
package/src/SimpleHost.ts
CHANGED
|
@@ -22,8 +22,16 @@ export class SimpleHost implements Host {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
writeFile(path: string, buffer: Buffer): void;
|
|
25
|
-
writeFile(
|
|
26
|
-
|
|
25
|
+
writeFile(
|
|
26
|
+
path: string,
|
|
27
|
+
body: string,
|
|
28
|
+
opts: { encoding: BufferEncoding },
|
|
29
|
+
): void;
|
|
30
|
+
writeFile(
|
|
31
|
+
path: string,
|
|
32
|
+
body: string | Buffer,
|
|
33
|
+
opts?: { encoding: BufferEncoding },
|
|
34
|
+
): void {
|
|
27
35
|
if (opts) {
|
|
28
36
|
this.fs.writeFileSync(path, body, { encoding: opts.encoding });
|
|
29
37
|
} else {
|
|
@@ -33,7 +41,10 @@ export class SimpleHost implements Host {
|
|
|
33
41
|
readFile(path: string, opts?: undefined): Buffer;
|
|
34
42
|
readFile(path: string, opts: { encoding: BufferEncoding }): string;
|
|
35
43
|
readFile(path: string, opts: { asJson: true }): object;
|
|
36
|
-
readFile(
|
|
44
|
+
readFile(
|
|
45
|
+
path: string,
|
|
46
|
+
opts?: { encoding?: BufferEncoding; asJson?: boolean },
|
|
47
|
+
): string | object | Buffer {
|
|
37
48
|
if (opts?.asJson) {
|
|
38
49
|
return JSON.parse(this.fs.readFileSync(path, "utf-8"));
|
|
39
50
|
}
|
package/src/Table.ts
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
*/
|
|
7
7
|
// tslint:disable:no-console
|
|
8
8
|
import { nanosecondsToSanity } from "./nanosecondsToSanity.js";
|
|
9
|
-
type HeaderFooterHelper<HB, FB, H, F> =
|
|
10
|
-
(
|
|
9
|
+
type HeaderFooterHelper<HB, FB, H, F> =
|
|
10
|
+
& (HB extends true ? { header: H } : { header?: H })
|
|
11
|
+
& (FB extends true ? { footer: F } : { footer?: F });
|
|
11
12
|
|
|
12
13
|
type WithAlignemnt = { alignment?: "right" | "left" };
|
|
13
14
|
type BaseCellConfig = WithAlignemnt & { type: "bigint" | "string" };
|
|
@@ -19,13 +20,20 @@ type BaseBigIntCellConfig = {
|
|
|
19
20
|
} & WithAlignemnt;
|
|
20
21
|
type BaseStringCellConfig = { type: "string" } & WithAlignemnt;
|
|
21
22
|
|
|
22
|
-
type BigIntColumnConfig<H, F> =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
type BigIntColumnConfig<H, F> =
|
|
24
|
+
& WithAlignemnt
|
|
25
|
+
& BaseBigIntCellConfig
|
|
26
|
+
& HeaderFooterHelper<
|
|
27
|
+
H,
|
|
28
|
+
F,
|
|
29
|
+
string,
|
|
30
|
+
AggregateFooterConfig | StaticFooterConfig
|
|
31
|
+
>;
|
|
32
|
+
|
|
33
|
+
type StringColumnConfig<H, F> =
|
|
34
|
+
& WithAlignemnt
|
|
35
|
+
& BaseStringCellConfig
|
|
36
|
+
& HeaderFooterHelper<H, F, string, StaticFooterConfig>;
|
|
29
37
|
|
|
30
38
|
type AggregateFooterConfig = {
|
|
31
39
|
aggregate: "sum" | "average";
|
|
@@ -47,7 +55,8 @@ type TableConfig<T extends any[], H extends boolean, F extends boolean> = {
|
|
|
47
55
|
showHeader: H;
|
|
48
56
|
showFooter: F;
|
|
49
57
|
columns: {
|
|
50
|
-
[K in keyof T]: T[K] extends bigint ? BigIntColumnConfig<H, F>
|
|
58
|
+
[K in keyof T]: T[K] extends bigint ? BigIntColumnConfig<H, F>
|
|
59
|
+
: StringColumnConfig<H, F>;
|
|
51
60
|
};
|
|
52
61
|
title: string;
|
|
53
62
|
};
|
|
@@ -74,7 +83,7 @@ export class Table<T extends any[]> {
|
|
|
74
83
|
| TableConfig<T, true, true>
|
|
75
84
|
| TableConfig<T, true, false>
|
|
76
85
|
| TableConfig<T, false, true>
|
|
77
|
-
| TableConfig<T, false, false
|
|
86
|
+
| TableConfig<T, false, false>,
|
|
78
87
|
) {
|
|
79
88
|
this.#config = {
|
|
80
89
|
padding: 2,
|
|
@@ -101,7 +110,9 @@ export class Table<T extends any[]> {
|
|
|
101
110
|
...columnConfig.footer,
|
|
102
111
|
});
|
|
103
112
|
} else if ("aggregate" in columnConfig.footer) {
|
|
104
|
-
if (columnConfig.type !== "bigint")
|
|
113
|
+
if (columnConfig.type !== "bigint") {
|
|
114
|
+
throw new Error("expecting bigint for aggregate");
|
|
115
|
+
}
|
|
105
116
|
this.#footerRowConfig.push({
|
|
106
117
|
type: columnConfig.type,
|
|
107
118
|
renderAs: columnConfig.renderAs,
|
|
@@ -155,10 +166,15 @@ export class Table<T extends any[]> {
|
|
|
155
166
|
const colConfig = this.#config.columns[c];
|
|
156
167
|
this.#columnWidths[c] = Math.max(
|
|
157
168
|
(this.#config.columns[c].header ?? "").length,
|
|
158
|
-
...this.#rows.map((a) =>
|
|
169
|
+
...this.#rows.map((a) =>
|
|
170
|
+
this.#getCellValueAsString(a[c], colConfig).length
|
|
171
|
+
),
|
|
159
172
|
this.#footer && this.#footerRowConfig
|
|
160
|
-
? this.#getCellValueAsString(
|
|
161
|
-
|
|
173
|
+
? this.#getCellValueAsString(
|
|
174
|
+
this.#footer?.[c] ?? "",
|
|
175
|
+
this.#footerRowConfig[c],
|
|
176
|
+
).length
|
|
177
|
+
: 0,
|
|
162
178
|
);
|
|
163
179
|
}
|
|
164
180
|
|
|
@@ -208,7 +224,9 @@ export class Table<T extends any[]> {
|
|
|
208
224
|
|
|
209
225
|
let hr = "";
|
|
210
226
|
for (let c = 0; c < footerRow.length; c++) {
|
|
211
|
-
hr +=
|
|
227
|
+
hr +=
|
|
228
|
+
this.#getCellValueAligned(footerRow[c], this.#footerRowConfig![c], c)
|
|
229
|
+
+ paddingString; // .padEnd(this.#columnWidths[c], " ") + paddingString;
|
|
212
230
|
}
|
|
213
231
|
hr = hr.trimRight();
|
|
214
232
|
console.log(hr);
|
|
@@ -246,7 +264,10 @@ export class Table<T extends any[]> {
|
|
|
246
264
|
console.log();
|
|
247
265
|
}
|
|
248
266
|
|
|
249
|
-
#getCellValueAsString(
|
|
267
|
+
#getCellValueAsString(
|
|
268
|
+
value: bigint | string,
|
|
269
|
+
config: BaseBigIntCellConfig | BaseStringCellConfig,
|
|
270
|
+
) {
|
|
250
271
|
if (config.type === "bigint" && config.renderAs === "nanoseconds") {
|
|
251
272
|
return nanosecondsToSanity(value as bigint, config.precision ?? 9);
|
|
252
273
|
} else {
|
|
@@ -254,7 +275,11 @@ export class Table<T extends any[]> {
|
|
|
254
275
|
}
|
|
255
276
|
}
|
|
256
277
|
|
|
257
|
-
#getCellValueAligned(
|
|
278
|
+
#getCellValueAligned(
|
|
279
|
+
value: bigint | string,
|
|
280
|
+
config: BaseBigIntCellConfig | BaseStringCellConfig,
|
|
281
|
+
column: number,
|
|
282
|
+
) {
|
|
258
283
|
let result: string;
|
|
259
284
|
if (config.type === "bigint" && config.renderAs === "nanoseconds") {
|
|
260
285
|
result = nanosecondsToSanity(value as bigint, config.precision ?? 9);
|
|
@@ -273,7 +298,10 @@ export class Table<T extends any[]> {
|
|
|
273
298
|
const config = this.#config.columns[colNum];
|
|
274
299
|
|
|
275
300
|
if (config.type === "bigint" && config.renderAs === "nanoseconds") {
|
|
276
|
-
return nanosecondsToSanity(
|
|
301
|
+
return nanosecondsToSanity(
|
|
302
|
+
this.#rows[rowNum][colNum],
|
|
303
|
+
config.precision ?? 9,
|
|
304
|
+
);
|
|
277
305
|
} else {
|
|
278
306
|
return "" + this.#rows[rowNum][colNum];
|
|
279
307
|
}
|
|
@@ -284,7 +312,10 @@ export class Table<T extends any[]> {
|
|
|
284
312
|
|
|
285
313
|
let result: string;
|
|
286
314
|
if (config.type === "bigint" && config.renderAs === "nanoseconds") {
|
|
287
|
-
result = nanosecondsToSanity(
|
|
315
|
+
result = nanosecondsToSanity(
|
|
316
|
+
this.#rows[rowNum][colNum],
|
|
317
|
+
config.precision ?? 9,
|
|
318
|
+
);
|
|
288
319
|
} else {
|
|
289
320
|
result = "" + this.#rows[rowNum][colNum];
|
|
290
321
|
}
|
|
@@ -296,12 +327,20 @@ export class Table<T extends any[]> {
|
|
|
296
327
|
}
|
|
297
328
|
}
|
|
298
329
|
|
|
299
|
-
getColumnWidth(
|
|
330
|
+
getColumnWidth(
|
|
331
|
+
colNum: number,
|
|
332
|
+
config:
|
|
333
|
+
| BigIntColumnConfig<boolean, boolean>
|
|
334
|
+
| StringColumnConfig<boolean, boolean>,
|
|
335
|
+
) {
|
|
300
336
|
let maxWidth = Math.max(
|
|
301
337
|
(config.header ?? "").length,
|
|
302
338
|
this.#footer && this.#footerRowConfig
|
|
303
|
-
? this.#getCellValueAsString(
|
|
304
|
-
|
|
339
|
+
? this.#getCellValueAsString(
|
|
340
|
+
this.#footer[colNum],
|
|
341
|
+
this.#footerRowConfig[colNum],
|
|
342
|
+
).length
|
|
343
|
+
: 0,
|
|
305
344
|
);
|
|
306
345
|
|
|
307
346
|
for (let r = 0; r < this.#rows.length; r++) {
|