@monorepolint/utils 0.6.0-alpha.2 → 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 +14 -10
- package/.turbo/turbo-transpile-typescript.log +3 -3
- package/CHANGELOG.md +2 -0
- package/build/js/index.js +493 -443
- package/build/js/index.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/types/CachingHost.d.ts.map +1 -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.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 +2 -2
- package/coverage/CachingHost.ts.html +258 -42
- package/coverage/Host.ts.html +15 -3
- package/coverage/PackageJson.ts.html +4 -4
- package/coverage/SimpleHost.ts.html +40 -7
- package/coverage/Table.ts.html +148 -31
- package/coverage/Timing.ts.html +1 -1
- package/coverage/clover.xml +533 -389
- package/coverage/coverage-final.json +13 -13
- package/coverage/findWorkspaceDir.ts.html +4 -4
- package/coverage/getPackageNameToDir.ts.html +18 -6
- package/coverage/getWorkspacePackageDirs.ts.html +174 -138
- package/coverage/index.html +40 -40
- package/coverage/index.ts.html +30 -30
- package/coverage/matchesAnyGlob.ts.html +37 -10
- package/coverage/mutateJson.ts.html +17 -5
- package/coverage/nanosecondsToSanity.ts.html +11 -5
- package/package.json +1 -2
- package/src/AggregateTiming.ts +1 -1
- package/src/CachingHost.ts +106 -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 +10 -7
- package/vitest.config.mjs.timestamp-1736878329730-aa478e2241542.mjs +2 -2
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,7 +274,8 @@ 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
|
}
|
|
246
281
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -253,14 +288,19 @@ export class CachingHost implements Host {
|
|
|
253
288
|
|
|
254
289
|
#getNode(filePath: string) {
|
|
255
290
|
const canonicalPath = path.resolve(filePath);
|
|
256
|
-
const { pathWithSymlinks, node } = this.#getNearestAncestorNode(
|
|
291
|
+
const { pathWithSymlinks, node } = this.#getNearestAncestorNode(
|
|
292
|
+
canonicalPath,
|
|
293
|
+
);
|
|
257
294
|
if (pathWithSymlinks === canonicalPath) {
|
|
258
295
|
return node;
|
|
259
296
|
}
|
|
260
297
|
return undefined;
|
|
261
298
|
}
|
|
262
299
|
|
|
263
|
-
#getNodeResolvingSymlinks(
|
|
300
|
+
#getNodeResolvingSymlinks(
|
|
301
|
+
filePath: string,
|
|
302
|
+
follows: number = 100,
|
|
303
|
+
): Exclude<Node, SymlinkNode> | undefined {
|
|
264
304
|
const node = this.#getNode(filePath); // canonicalizes for us
|
|
265
305
|
if (!node || node.type !== "symlink") return node;
|
|
266
306
|
// this is a really poor mans way of doing this. but who has 100's of symlinks hanging around?
|
|
@@ -269,9 +309,14 @@ export class CachingHost implements Host {
|
|
|
269
309
|
return this.#getNodeResolvingSymlinks(node.symlink, follows--);
|
|
270
310
|
}
|
|
271
311
|
|
|
272
|
-
mkdir(
|
|
312
|
+
mkdir(
|
|
313
|
+
filePath: string,
|
|
314
|
+
opts: { recursive: boolean } = { recursive: false },
|
|
315
|
+
): void {
|
|
273
316
|
const canonicalPath = path.resolve(filePath);
|
|
274
|
-
const { node, pathWithSymlinks } = this.#getNearestAncestorNode(
|
|
317
|
+
const { node, pathWithSymlinks } = this.#getNearestAncestorNode(
|
|
318
|
+
canonicalPath,
|
|
319
|
+
);
|
|
275
320
|
if (filePath === pathWithSymlinks) {
|
|
276
321
|
assertType(node, "dir");
|
|
277
322
|
assertHasParent(node);
|
|
@@ -297,7 +342,9 @@ export class CachingHost implements Host {
|
|
|
297
342
|
let maybePath = canonicalPath;
|
|
298
343
|
const toMake: string[] = [];
|
|
299
344
|
while (maybePath !== rootPath) {
|
|
300
|
-
toMake.unshift(
|
|
345
|
+
toMake.unshift(
|
|
346
|
+
path.resolve(node.fullPath, path.relative(rootPath, maybePath)),
|
|
347
|
+
);
|
|
301
348
|
maybePath = path.dirname(maybePath);
|
|
302
349
|
}
|
|
303
350
|
|
|
@@ -337,7 +384,10 @@ export class CachingHost implements Host {
|
|
|
337
384
|
readFile(filePath: string, opts: { asJson: true }): object;
|
|
338
385
|
readFile(
|
|
339
386
|
filePath: string,
|
|
340
|
-
opts: undefined | { encoding: BufferEncoding; asJson?: false } | {
|
|
387
|
+
opts: undefined | { encoding: BufferEncoding; asJson?: false } | {
|
|
388
|
+
encoding?: never;
|
|
389
|
+
asJson: true;
|
|
390
|
+
},
|
|
341
391
|
) {
|
|
342
392
|
let node = this.#getNodeResolvingSymlinks(filePath); // canonicalizes for us
|
|
343
393
|
|
|
@@ -365,10 +415,24 @@ export class CachingHost implements Host {
|
|
|
365
415
|
}
|
|
366
416
|
|
|
367
417
|
writeFile(filePath: string, buffer: Buffer): void;
|
|
368
|
-
writeFile(
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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);
|
|
372
436
|
|
|
373
437
|
const canonicalPath = path.resolve(filePath);
|
|
374
438
|
const existingNode = this.#getNodeResolvingSymlinks(canonicalPath);
|
|
@@ -384,7 +448,9 @@ export class CachingHost implements Host {
|
|
|
384
448
|
return;
|
|
385
449
|
}
|
|
386
450
|
|
|
387
|
-
const maybeDirNode = this.#getNodeResolvingSymlinks(
|
|
451
|
+
const maybeDirNode = this.#getNodeResolvingSymlinks(
|
|
452
|
+
path.dirname(canonicalPath),
|
|
453
|
+
);
|
|
388
454
|
assertExists(maybeDirNode);
|
|
389
455
|
assertType(maybeDirNode, "dir");
|
|
390
456
|
assertNoTombstone(maybeDirNode);
|
|
@@ -420,7 +486,9 @@ export class CachingHost implements Host {
|
|
|
420
486
|
});
|
|
421
487
|
}
|
|
422
488
|
|
|
423
|
-
async #flushFileNode(
|
|
489
|
+
async #flushFileNode(
|
|
490
|
+
node: FileNode | FileStubNode | FileTombstoneNode,
|
|
491
|
+
): Promise<unknown> {
|
|
424
492
|
// FIXME all tombstones need a flush, so we can get rid of needsFlush for them
|
|
425
493
|
if (node.tombstone) {
|
|
426
494
|
try {
|
|
@@ -453,7 +521,9 @@ export class CachingHost implements Host {
|
|
|
453
521
|
return this.fs.promises.symlink(node.symlink, node.fullPath);
|
|
454
522
|
}
|
|
455
523
|
|
|
456
|
-
async #flushDirNode(
|
|
524
|
+
async #flushDirNode(
|
|
525
|
+
node: DirNode | DirStubNode | DirTombstoneNode,
|
|
526
|
+
): Promise<unknown> {
|
|
457
527
|
if (!node.tombstone && node.needsFlush) {
|
|
458
528
|
try {
|
|
459
529
|
await this.fs.promises.access(node.fullPath); // throws if the file doesnt exist
|
|
@@ -466,7 +536,9 @@ export class CachingHost implements Host {
|
|
|
466
536
|
const promises: Promise<unknown>[] = [];
|
|
467
537
|
for (const child of node.dir.values()) {
|
|
468
538
|
if (node.tombstone && !child.tombstone) {
|
|
469
|
-
throw new Error(
|
|
539
|
+
throw new Error(
|
|
540
|
+
"Unexpected failure during sanity check. A non-deleted child is on a deleted dir",
|
|
541
|
+
);
|
|
470
542
|
}
|
|
471
543
|
if (child.type === "dir") {
|
|
472
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++) {
|