tstyche 1.1.0 → 2.0.0-beta.0
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/README.md +5 -5
- package/build/index.d.cts +98 -39
- package/build/index.d.ts +98 -39
- package/build/tstyche.d.ts +87 -74
- package/build/tstyche.js +598 -267
- package/package.json +15 -16
package/build/tstyche.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
2
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
3
|
+
import path from 'node:path';
|
|
3
4
|
import { createRequire } from 'node:module';
|
|
4
5
|
import os from 'node:os';
|
|
5
|
-
import path from 'node:path';
|
|
6
6
|
import { existsSync, writeFileSync, rmSync } from 'node:fs';
|
|
7
7
|
import fs from 'node:fs/promises';
|
|
8
8
|
import vm from 'node:vm';
|
|
@@ -24,103 +24,6 @@ class EventEmitter {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
class Path {
|
|
28
|
-
static dirname(filePath) {
|
|
29
|
-
return Path.normalizeSlashes(path.dirname(filePath));
|
|
30
|
-
}
|
|
31
|
-
static join(...filePaths) {
|
|
32
|
-
return Path.normalizeSlashes(path.join(...filePaths));
|
|
33
|
-
}
|
|
34
|
-
static normalizeSlashes(filePath) {
|
|
35
|
-
if (path.sep === "/") {
|
|
36
|
-
return filePath;
|
|
37
|
-
}
|
|
38
|
-
return filePath.replace(/\\/g, "/");
|
|
39
|
-
}
|
|
40
|
-
static relative(from, to) {
|
|
41
|
-
let relativePath = path.relative(from, to);
|
|
42
|
-
if (!relativePath.startsWith(".")) {
|
|
43
|
-
relativePath = `./${relativePath}`;
|
|
44
|
-
}
|
|
45
|
-
return Path.normalizeSlashes(relativePath);
|
|
46
|
-
}
|
|
47
|
-
static resolve(...filePaths) {
|
|
48
|
-
return Path.normalizeSlashes(path.resolve(...filePaths));
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
class Environment {
|
|
53
|
-
static #noColor = Environment.#resolveNoColor();
|
|
54
|
-
static #noInteractive = Environment.#resolveNoInteractive();
|
|
55
|
-
static #storePath = Environment.#resolveStorePath();
|
|
56
|
-
static #timeout = Environment.#resolveTimeout();
|
|
57
|
-
static #typescriptPath = Environment.#resolveTypeScriptPath();
|
|
58
|
-
static get noColor() {
|
|
59
|
-
return Environment.#noColor;
|
|
60
|
-
}
|
|
61
|
-
static get noInteractive() {
|
|
62
|
-
return Environment.#noInteractive;
|
|
63
|
-
}
|
|
64
|
-
static get storePath() {
|
|
65
|
-
return Environment.#storePath;
|
|
66
|
-
}
|
|
67
|
-
static get timeout() {
|
|
68
|
-
return Environment.#timeout;
|
|
69
|
-
}
|
|
70
|
-
static get typescriptPath() {
|
|
71
|
-
return Environment.#typescriptPath;
|
|
72
|
-
}
|
|
73
|
-
static #resolveNoColor() {
|
|
74
|
-
if (process.env["TSTYCHE_NO_COLOR"] != null) {
|
|
75
|
-
return process.env["TSTYCHE_NO_COLOR"] !== "";
|
|
76
|
-
}
|
|
77
|
-
if (process.env["NO_COLOR"] != null) {
|
|
78
|
-
return process.env["NO_COLOR"] !== "";
|
|
79
|
-
}
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
static #resolveNoInteractive() {
|
|
83
|
-
if (process.env["TSTYCHE_NO_INTERACTIVE"] != null) {
|
|
84
|
-
return process.env["TSTYCHE_NO_INTERACTIVE"] !== "";
|
|
85
|
-
}
|
|
86
|
-
return !process.stdout.isTTY;
|
|
87
|
-
}
|
|
88
|
-
static #resolveStorePath() {
|
|
89
|
-
if (process.env["TSTYCHE_STORE_PATH"] != null) {
|
|
90
|
-
return Path.resolve(process.env["TSTYCHE_STORE_PATH"]);
|
|
91
|
-
}
|
|
92
|
-
if (process.platform === "darwin") {
|
|
93
|
-
return Path.resolve(os.homedir(), "Library", "TSTyche");
|
|
94
|
-
}
|
|
95
|
-
if (process.env["LocalAppData"] != null) {
|
|
96
|
-
return Path.resolve(process.env["LocalAppData"], "TSTyche");
|
|
97
|
-
}
|
|
98
|
-
if (process.env["XDG_DATA_HOME"] != null) {
|
|
99
|
-
return Path.resolve(process.env["XDG_DATA_HOME"], "TSTyche");
|
|
100
|
-
}
|
|
101
|
-
return Path.resolve(os.homedir(), ".local", "share", "TSTyche");
|
|
102
|
-
}
|
|
103
|
-
static #resolveTimeout() {
|
|
104
|
-
if (process.env["TSTYCHE_TIMEOUT"] != null) {
|
|
105
|
-
return Number(process.env["TSTYCHE_TIMEOUT"]);
|
|
106
|
-
}
|
|
107
|
-
return 30;
|
|
108
|
-
}
|
|
109
|
-
static #resolveTypeScriptPath() {
|
|
110
|
-
let moduleId = "typescript";
|
|
111
|
-
if (process.env["TSTYCHE_TYPESCRIPT_PATH"] != null) {
|
|
112
|
-
moduleId = process.env["TSTYCHE_TYPESCRIPT_PATH"];
|
|
113
|
-
}
|
|
114
|
-
let resolvedPath;
|
|
115
|
-
try {
|
|
116
|
-
resolvedPath = Path.normalizeSlashes(createRequire(import.meta.url).resolve(moduleId));
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
}
|
|
120
|
-
return resolvedPath;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
27
|
var Color;
|
|
125
28
|
(function (Color) {
|
|
126
29
|
Color["Reset"] = "0";
|
|
@@ -134,8 +37,10 @@ var Color;
|
|
|
134
37
|
})(Color || (Color = {}));
|
|
135
38
|
|
|
136
39
|
class Scribbler {
|
|
40
|
+
#indentStep = " ";
|
|
137
41
|
#newLine;
|
|
138
42
|
#noColor;
|
|
43
|
+
#notEmptyLineRegex = /^(?!$)/gm;
|
|
139
44
|
constructor(options) {
|
|
140
45
|
this.#newLine = options?.newLine ?? "\n";
|
|
141
46
|
this.#noColor = options?.noColor ?? false;
|
|
@@ -152,9 +57,7 @@ class Scribbler {
|
|
|
152
57
|
return ["\u001B[", Array.isArray(attributes) ? attributes.join(";") : attributes, "m"].join("");
|
|
153
58
|
}
|
|
154
59
|
#indentEachLine(lines, level) {
|
|
155
|
-
|
|
156
|
-
const notEmptyLineRegExp = /^(?!$)/gm;
|
|
157
|
-
return lines.replace(notEmptyLineRegExp, indentStep.repeat(level));
|
|
60
|
+
return lines.replace(this.#notEmptyLineRegex, this.#indentStep.repeat(level));
|
|
158
61
|
}
|
|
159
62
|
render(element) {
|
|
160
63
|
if (element != null) {
|
|
@@ -245,49 +148,6 @@ class Line {
|
|
|
245
148
|
}
|
|
246
149
|
}
|
|
247
150
|
|
|
248
|
-
class Logger {
|
|
249
|
-
#noColor;
|
|
250
|
-
#scribbler;
|
|
251
|
-
#stderr;
|
|
252
|
-
#stdout;
|
|
253
|
-
constructor(options) {
|
|
254
|
-
this.#noColor = options?.noColor ?? Environment.noColor;
|
|
255
|
-
this.#stderr = options?.stderr ?? process.stderr;
|
|
256
|
-
this.#stdout = options?.stdout ?? process.stdout;
|
|
257
|
-
this.#scribbler = new Scribbler({ noColor: this.#noColor });
|
|
258
|
-
}
|
|
259
|
-
eraseLastLine() {
|
|
260
|
-
this.#stdout.write("\u001B[1A\u001B[0K");
|
|
261
|
-
}
|
|
262
|
-
#write(stream, body) {
|
|
263
|
-
const elements = Array.isArray(body) ? body : [body];
|
|
264
|
-
for (const element of elements) {
|
|
265
|
-
if (element.$$typeof !== Symbol.for("tstyche:scribbler")) {
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
stream.write(this.#scribbler.render(element));
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
writeError(body) {
|
|
272
|
-
this.#write(this.#stderr, body);
|
|
273
|
-
}
|
|
274
|
-
writeMessage(body) {
|
|
275
|
-
this.#write(this.#stdout, body);
|
|
276
|
-
}
|
|
277
|
-
writeWarning(body) {
|
|
278
|
-
this.#write(this.#stderr, body);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
class Reporter {
|
|
283
|
-
resolvedConfig;
|
|
284
|
-
logger;
|
|
285
|
-
constructor(resolvedConfig) {
|
|
286
|
-
this.resolvedConfig = resolvedConfig;
|
|
287
|
-
this.logger = new Logger();
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
151
|
function addsPackageStepText(compilerVersion, installationPath) {
|
|
292
152
|
return (Scribbler.createElement(Line, null,
|
|
293
153
|
Scribbler.createElement(Text, { color: "90" }, "adds"),
|
|
@@ -302,6 +162,31 @@ function describeNameText(name, indent = 0) {
|
|
|
302
162
|
return Scribbler.createElement(Line, { indent: indent + 1 }, name);
|
|
303
163
|
}
|
|
304
164
|
|
|
165
|
+
class Path {
|
|
166
|
+
static dirname(filePath) {
|
|
167
|
+
return Path.normalizeSlashes(path.dirname(filePath));
|
|
168
|
+
}
|
|
169
|
+
static join(...filePaths) {
|
|
170
|
+
return Path.normalizeSlashes(path.join(...filePaths));
|
|
171
|
+
}
|
|
172
|
+
static normalizeSlashes(filePath) {
|
|
173
|
+
if (path.sep === "/") {
|
|
174
|
+
return filePath;
|
|
175
|
+
}
|
|
176
|
+
return filePath.replace(/\\/g, "/");
|
|
177
|
+
}
|
|
178
|
+
static relative(from, to) {
|
|
179
|
+
let relativePath = path.relative(from, to);
|
|
180
|
+
if (!relativePath.startsWith("./")) {
|
|
181
|
+
relativePath = `./${relativePath}`;
|
|
182
|
+
}
|
|
183
|
+
return Path.normalizeSlashes(relativePath);
|
|
184
|
+
}
|
|
185
|
+
static resolve(...filePaths) {
|
|
186
|
+
return Path.normalizeSlashes(path.resolve(...filePaths));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
305
190
|
class CodeSpanText {
|
|
306
191
|
props;
|
|
307
192
|
constructor(props) {
|
|
@@ -312,7 +197,7 @@ class CodeSpanText {
|
|
|
312
197
|
const { character: markedCharacter, line: markedLine } = this.props.file.getLineAndCharacterOfPosition(this.props.start);
|
|
313
198
|
const firstLine = Math.max(markedLine - 2, 0);
|
|
314
199
|
const lastLine = Math.min(firstLine + 5, lastLineInFile);
|
|
315
|
-
const lineNumberMaxWidth =
|
|
200
|
+
const lineNumberMaxWidth = String(lastLine + 1).length;
|
|
316
201
|
const codeSpan = [];
|
|
317
202
|
for (let index = firstLine; index <= lastLine; index++) {
|
|
318
203
|
const lineStart = this.props.file.getPositionOfLineAndCharacter(index, 0);
|
|
@@ -609,6 +494,125 @@ function helpText(optionDefinitions, tstycheVersion) {
|
|
|
609
494
|
Scribbler.createElement(Line, null)));
|
|
610
495
|
}
|
|
611
496
|
|
|
497
|
+
class Environment {
|
|
498
|
+
static #isCi = Environment.#resolveIsCi();
|
|
499
|
+
static #noColor = Environment.#resolveNoColor();
|
|
500
|
+
static #noInteractive = Environment.#resolveNoInteractive();
|
|
501
|
+
static #storePath = Environment.#resolveStorePath();
|
|
502
|
+
static #timeout = Environment.#resolveTimeout();
|
|
503
|
+
static #typescriptPath = Environment.#resolveTypeScriptPath();
|
|
504
|
+
static get isCi() {
|
|
505
|
+
return Environment.#isCi;
|
|
506
|
+
}
|
|
507
|
+
static get noColor() {
|
|
508
|
+
return Environment.#noColor;
|
|
509
|
+
}
|
|
510
|
+
static get noInteractive() {
|
|
511
|
+
return Environment.#noInteractive;
|
|
512
|
+
}
|
|
513
|
+
static get storePath() {
|
|
514
|
+
return Environment.#storePath;
|
|
515
|
+
}
|
|
516
|
+
static get timeout() {
|
|
517
|
+
return Environment.#timeout;
|
|
518
|
+
}
|
|
519
|
+
static get typescriptPath() {
|
|
520
|
+
return Environment.#typescriptPath;
|
|
521
|
+
}
|
|
522
|
+
static #resolveIsCi() {
|
|
523
|
+
if (process.env["CI"] != null) {
|
|
524
|
+
return process.env["CI"] !== "";
|
|
525
|
+
}
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
static #resolveNoColor() {
|
|
529
|
+
if (process.env["TSTYCHE_NO_COLOR"] != null) {
|
|
530
|
+
return process.env["TSTYCHE_NO_COLOR"] !== "";
|
|
531
|
+
}
|
|
532
|
+
if (process.env["NO_COLOR"] != null) {
|
|
533
|
+
return process.env["NO_COLOR"] !== "";
|
|
534
|
+
}
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
static #resolveNoInteractive() {
|
|
538
|
+
if (process.env["TSTYCHE_NO_INTERACTIVE"] != null) {
|
|
539
|
+
return process.env["TSTYCHE_NO_INTERACTIVE"] !== "";
|
|
540
|
+
}
|
|
541
|
+
return !process.stdout.isTTY;
|
|
542
|
+
}
|
|
543
|
+
static #resolveStorePath() {
|
|
544
|
+
if (process.env["TSTYCHE_STORE_PATH"] != null) {
|
|
545
|
+
return Path.resolve(process.env["TSTYCHE_STORE_PATH"]);
|
|
546
|
+
}
|
|
547
|
+
if (process.platform === "darwin") {
|
|
548
|
+
return Path.resolve(os.homedir(), "Library", "TSTyche");
|
|
549
|
+
}
|
|
550
|
+
if (process.env["LocalAppData"] != null) {
|
|
551
|
+
return Path.resolve(process.env["LocalAppData"], "TSTyche");
|
|
552
|
+
}
|
|
553
|
+
if (process.env["XDG_DATA_HOME"] != null) {
|
|
554
|
+
return Path.resolve(process.env["XDG_DATA_HOME"], "TSTyche");
|
|
555
|
+
}
|
|
556
|
+
return Path.resolve(os.homedir(), ".local", "share", "TSTyche");
|
|
557
|
+
}
|
|
558
|
+
static #resolveTimeout() {
|
|
559
|
+
if (process.env["TSTYCHE_TIMEOUT"] != null) {
|
|
560
|
+
return Number(process.env["TSTYCHE_TIMEOUT"]);
|
|
561
|
+
}
|
|
562
|
+
return 30;
|
|
563
|
+
}
|
|
564
|
+
static #resolveTypeScriptPath() {
|
|
565
|
+
let moduleId = "typescript";
|
|
566
|
+
if (process.env["TSTYCHE_TYPESCRIPT_PATH"] != null) {
|
|
567
|
+
moduleId = process.env["TSTYCHE_TYPESCRIPT_PATH"];
|
|
568
|
+
}
|
|
569
|
+
let resolvedPath;
|
|
570
|
+
try {
|
|
571
|
+
resolvedPath = Path.normalizeSlashes(createRequire(import.meta.url).resolve(moduleId));
|
|
572
|
+
}
|
|
573
|
+
catch {
|
|
574
|
+
}
|
|
575
|
+
return resolvedPath;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
class OutputService {
|
|
580
|
+
#noColor;
|
|
581
|
+
#scribbler;
|
|
582
|
+
#stderr;
|
|
583
|
+
#stdout;
|
|
584
|
+
constructor(options) {
|
|
585
|
+
this.#noColor = options?.noColor ?? Environment.noColor;
|
|
586
|
+
this.#stderr = options?.stderr ?? process.stderr;
|
|
587
|
+
this.#stdout = options?.stdout ?? process.stdout;
|
|
588
|
+
this.#scribbler = new Scribbler({ noColor: this.#noColor });
|
|
589
|
+
}
|
|
590
|
+
clearTerminal() {
|
|
591
|
+
this.#stdout.write("\u001B[2J\u001B[3J\u001B[H");
|
|
592
|
+
}
|
|
593
|
+
eraseLastLine() {
|
|
594
|
+
this.#stdout.write("\u001B[1A\u001B[0K");
|
|
595
|
+
}
|
|
596
|
+
#write(stream, body) {
|
|
597
|
+
const elements = Array.isArray(body) ? body : [body];
|
|
598
|
+
for (const element of elements) {
|
|
599
|
+
if (element.$$typeof !== Symbol.for("tstyche:scribbler")) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
stream.write(this.#scribbler.render(element));
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
writeError(body) {
|
|
606
|
+
this.#write(this.#stderr, body);
|
|
607
|
+
}
|
|
608
|
+
writeMessage(body) {
|
|
609
|
+
this.#write(this.#stdout, body);
|
|
610
|
+
}
|
|
611
|
+
writeWarning(body) {
|
|
612
|
+
this.#write(this.#stderr, body);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
612
616
|
class RowText {
|
|
613
617
|
props;
|
|
614
618
|
constructor(props) {
|
|
@@ -670,8 +674,8 @@ class DurationText {
|
|
|
670
674
|
const minutes = Math.floor(duration / 60);
|
|
671
675
|
const seconds = duration % 60;
|
|
672
676
|
return (Scribbler.createElement(Text, null,
|
|
673
|
-
minutes > 0 ? `${minutes}m ` : undefined,
|
|
674
|
-
`${Math.round(seconds * 10) / 10}s`));
|
|
677
|
+
minutes > 0 ? `${String(minutes)}m ` : undefined,
|
|
678
|
+
`${String(Math.round(seconds * 10) / 10)}s`));
|
|
675
679
|
}
|
|
676
680
|
}
|
|
677
681
|
class MatchText {
|
|
@@ -807,11 +811,30 @@ function usesCompilerStepText(compilerVersion, tsconfigFilePath, options) {
|
|
|
807
811
|
Scribbler.createElement(Line, null)));
|
|
808
812
|
}
|
|
809
813
|
|
|
814
|
+
function watchModeUsageText() {
|
|
815
|
+
const usageText = Object.entries({ a: "run all tests", x: "exit" }).map(([key, action]) => {
|
|
816
|
+
return (Scribbler.createElement(Line, null,
|
|
817
|
+
Scribbler.createElement(Text, { color: "90" }, "Press"),
|
|
818
|
+
Scribbler.createElement(Text, null, ` ${key} `),
|
|
819
|
+
Scribbler.createElement(Text, { color: "90" }, `to ${action}.`)));
|
|
820
|
+
});
|
|
821
|
+
return (Scribbler.createElement(Text, null, usageText));
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
class Reporter {
|
|
825
|
+
resolvedConfig;
|
|
826
|
+
outputService;
|
|
827
|
+
constructor(resolvedConfig) {
|
|
828
|
+
this.resolvedConfig = resolvedConfig;
|
|
829
|
+
this.outputService = new OutputService();
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
810
833
|
class SummaryReporter extends Reporter {
|
|
811
834
|
handleEvent([eventName, payload]) {
|
|
812
835
|
switch (eventName) {
|
|
813
|
-
case "end":
|
|
814
|
-
this.
|
|
836
|
+
case "run:end":
|
|
837
|
+
this.outputService.writeMessage(summaryText({
|
|
815
838
|
duration: payload.result.timing.duration,
|
|
816
839
|
expectCount: payload.result.expectCount,
|
|
817
840
|
fileCount: payload.result.fileCount,
|
|
@@ -867,21 +890,30 @@ class ThoroughReporter extends Reporter {
|
|
|
867
890
|
#hasReportedAdds = false;
|
|
868
891
|
#hasReportedError = false;
|
|
869
892
|
#isFileViewExpanded = false;
|
|
893
|
+
#seenDeprecations = new Set();
|
|
870
894
|
get #isLastFile() {
|
|
871
895
|
return this.#fileCount === 0;
|
|
872
896
|
}
|
|
873
897
|
handleEvent([eventName, payload]) {
|
|
874
898
|
switch (eventName) {
|
|
875
|
-
case "
|
|
876
|
-
|
|
899
|
+
case "deprecation:info":
|
|
900
|
+
for (const diagnostic of payload.diagnostics) {
|
|
901
|
+
if (!this.#seenDeprecations.has(diagnostic.text.toString())) {
|
|
902
|
+
this.#fileView.addMessage(diagnosticText(diagnostic));
|
|
903
|
+
this.#seenDeprecations.add(diagnostic.text.toString());
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
break;
|
|
907
|
+
case "run:start":
|
|
908
|
+
this.#isFileViewExpanded = payload.result.testFiles.length === 1 && this.resolvedConfig.watch !== true;
|
|
877
909
|
break;
|
|
878
910
|
case "store:info":
|
|
879
|
-
this.
|
|
911
|
+
this.outputService.writeMessage(addsPackageStepText(payload.compilerVersion, payload.installationPath));
|
|
880
912
|
this.#hasReportedAdds = true;
|
|
881
913
|
break;
|
|
882
914
|
case "store:error":
|
|
883
915
|
for (const diagnostic of payload.diagnostics) {
|
|
884
|
-
this.
|
|
916
|
+
this.outputService.writeError(diagnosticText(diagnostic));
|
|
885
917
|
}
|
|
886
918
|
break;
|
|
887
919
|
case "target:start":
|
|
@@ -894,7 +926,7 @@ class ThoroughReporter extends Reporter {
|
|
|
894
926
|
case "project:info":
|
|
895
927
|
if (this.#currentCompilerVersion !== payload.compilerVersion
|
|
896
928
|
|| this.#currentProjectConfigFilePath !== payload.projectConfigFilePath) {
|
|
897
|
-
this.
|
|
929
|
+
this.outputService.writeMessage(usesCompilerStepText(payload.compilerVersion, payload.projectConfigFilePath, {
|
|
898
930
|
prependEmptyLine: this.#currentCompilerVersion != null && !this.#hasReportedAdds
|
|
899
931
|
&& !this.#hasReportedError,
|
|
900
932
|
}));
|
|
@@ -905,12 +937,12 @@ class ThoroughReporter extends Reporter {
|
|
|
905
937
|
break;
|
|
906
938
|
case "project:error":
|
|
907
939
|
for (const diagnostic of payload.diagnostics) {
|
|
908
|
-
this.
|
|
940
|
+
this.outputService.writeError(diagnosticText(diagnostic));
|
|
909
941
|
}
|
|
910
942
|
break;
|
|
911
943
|
case "file:start":
|
|
912
944
|
if (!Environment.noInteractive) {
|
|
913
|
-
this.
|
|
945
|
+
this.outputService.writeMessage(fileStatusText(payload.result.status, payload.result.testFile));
|
|
914
946
|
}
|
|
915
947
|
this.#fileCount--;
|
|
916
948
|
this.#hasReportedError = false;
|
|
@@ -922,12 +954,12 @@ class ThoroughReporter extends Reporter {
|
|
|
922
954
|
break;
|
|
923
955
|
case "file:end":
|
|
924
956
|
if (!Environment.noInteractive) {
|
|
925
|
-
this.
|
|
957
|
+
this.outputService.eraseLastLine();
|
|
926
958
|
}
|
|
927
|
-
this.
|
|
928
|
-
this.
|
|
959
|
+
this.outputService.writeMessage(fileStatusText(payload.result.status, payload.result.testFile));
|
|
960
|
+
this.outputService.writeMessage(this.#fileView.getViewText({ appendEmptyLine: this.#isLastFile }));
|
|
929
961
|
if (this.#fileView.hasErrors) {
|
|
930
|
-
this.
|
|
962
|
+
this.outputService.writeError(this.#fileView.getMessages());
|
|
931
963
|
this.#hasReportedError = true;
|
|
932
964
|
}
|
|
933
965
|
this.#fileView.reset();
|
|
@@ -980,6 +1012,19 @@ class ThoroughReporter extends Reporter {
|
|
|
980
1012
|
}
|
|
981
1013
|
}
|
|
982
1014
|
|
|
1015
|
+
class WatchModeReporter extends Reporter {
|
|
1016
|
+
handleEvent([eventName]) {
|
|
1017
|
+
switch (eventName) {
|
|
1018
|
+
case "run:start":
|
|
1019
|
+
this.outputService.clearTerminal();
|
|
1020
|
+
break;
|
|
1021
|
+
case "run:end":
|
|
1022
|
+
this.outputService.writeMessage(watchModeUsageText());
|
|
1023
|
+
break;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
983
1028
|
class ResultTiming {
|
|
984
1029
|
end = Date.now();
|
|
985
1030
|
start = Date.now();
|
|
@@ -1079,11 +1124,11 @@ class ResultManager {
|
|
|
1079
1124
|
#testResult;
|
|
1080
1125
|
handleEvent([eventName, payload]) {
|
|
1081
1126
|
switch (eventName) {
|
|
1082
|
-
case "start":
|
|
1127
|
+
case "run:start":
|
|
1083
1128
|
this.#result = payload.result;
|
|
1084
1129
|
this.#result.timing.start = Date.now();
|
|
1085
1130
|
break;
|
|
1086
|
-
case "end":
|
|
1131
|
+
case "run:end":
|
|
1087
1132
|
this.#result.timing.end = Date.now();
|
|
1088
1133
|
this.#result = undefined;
|
|
1089
1134
|
break;
|
|
@@ -1318,7 +1363,7 @@ class Diagnostic {
|
|
|
1318
1363
|
static fromDiagnostics(diagnostics, compiler) {
|
|
1319
1364
|
return diagnostics.map((diagnostic) => {
|
|
1320
1365
|
const category = "error";
|
|
1321
|
-
const code = `ts(${diagnostic.code})`;
|
|
1366
|
+
const code = `ts(${String(diagnostic.code)})`;
|
|
1322
1367
|
let origin;
|
|
1323
1368
|
const text = compiler.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
|
1324
1369
|
if (Diagnostic.isTsDiagnosticWithLocation(diagnostic)) {
|
|
@@ -1400,10 +1445,8 @@ class TestMember {
|
|
|
1400
1445
|
}
|
|
1401
1446
|
validate() {
|
|
1402
1447
|
const diagnostics = [];
|
|
1403
|
-
const getText = (node) => `'${node.expression.getText()}()' cannot be nested within '${this.node.expression.getText()}()'
|
|
1448
|
+
const getText = (node) => `'${node.expression.getText()}()' cannot be nested within '${this.node.expression.getText()}()'.`;
|
|
1404
1449
|
switch (this.brand) {
|
|
1405
|
-
case "expect":
|
|
1406
|
-
break;
|
|
1407
1450
|
case "describe":
|
|
1408
1451
|
for (const member of this.members) {
|
|
1409
1452
|
if (member.brand === "expect") {
|
|
@@ -1416,6 +1459,7 @@ class TestMember {
|
|
|
1416
1459
|
}
|
|
1417
1460
|
break;
|
|
1418
1461
|
case "test":
|
|
1462
|
+
case "expect":
|
|
1419
1463
|
for (const member of this.members) {
|
|
1420
1464
|
if (member.brand !== "expect") {
|
|
1421
1465
|
diagnostics.push(Diagnostic.error(getText(member.node), {
|
|
@@ -1599,8 +1643,11 @@ class TestTree {
|
|
|
1599
1643
|
class CollectService {
|
|
1600
1644
|
compiler;
|
|
1601
1645
|
matcherIdentifiers = [
|
|
1646
|
+
"toBe",
|
|
1602
1647
|
"toBeAny",
|
|
1603
1648
|
"toBeAssignable",
|
|
1649
|
+
"toBeAssignableTo",
|
|
1650
|
+
"toBeAssignableWith",
|
|
1604
1651
|
"toBeBigInt",
|
|
1605
1652
|
"toBeBoolean",
|
|
1606
1653
|
"toBeNever",
|
|
@@ -1649,7 +1696,11 @@ class CollectService {
|
|
|
1649
1696
|
if (matcherNode == null || !this.#isMatcherNode(matcherNode)) {
|
|
1650
1697
|
return;
|
|
1651
1698
|
}
|
|
1652
|
-
|
|
1699
|
+
const assertion = new Assertion(meta.brand, node, parent, meta.flags, matcherNode, modifierNode, notNode);
|
|
1700
|
+
parent.members.push(assertion);
|
|
1701
|
+
this.compiler.forEachChild(node, (node) => {
|
|
1702
|
+
this.#collectTestMembers(node, identifiers, assertion);
|
|
1703
|
+
});
|
|
1653
1704
|
return;
|
|
1654
1705
|
}
|
|
1655
1706
|
}
|
|
@@ -1716,6 +1767,7 @@ class PrimitiveTypeMatcher {
|
|
|
1716
1767
|
|
|
1717
1768
|
class RelationMatcherBase {
|
|
1718
1769
|
typeChecker;
|
|
1770
|
+
relationExplanationVerb = "is";
|
|
1719
1771
|
constructor(typeChecker) {
|
|
1720
1772
|
this.typeChecker = typeChecker;
|
|
1721
1773
|
}
|
|
@@ -1723,8 +1775,12 @@ class RelationMatcherBase {
|
|
|
1723
1775
|
const sourceTypeText = this.typeChecker.typeToString(sourceType);
|
|
1724
1776
|
const targetTypeText = this.typeChecker.typeToString(targetType);
|
|
1725
1777
|
return isNot
|
|
1726
|
-
? [
|
|
1727
|
-
|
|
1778
|
+
? [
|
|
1779
|
+
Diagnostic.error(`Type '${sourceTypeText}' ${this.relationExplanationVerb} ${this.relationExplanationText} type '${targetTypeText}'.`),
|
|
1780
|
+
]
|
|
1781
|
+
: [
|
|
1782
|
+
Diagnostic.error(`Type '${sourceTypeText}' ${this.relationExplanationVerb} not ${this.relationExplanationText} type '${targetTypeText}'.`),
|
|
1783
|
+
];
|
|
1728
1784
|
}
|
|
1729
1785
|
match(sourceType, targetType, isNot) {
|
|
1730
1786
|
const isMatch = this.typeChecker.isTypeRelatedTo(sourceType, targetType, this.relation);
|
|
@@ -1735,9 +1791,19 @@ class RelationMatcherBase {
|
|
|
1735
1791
|
}
|
|
1736
1792
|
}
|
|
1737
1793
|
|
|
1738
|
-
class
|
|
1794
|
+
class ToBe extends RelationMatcherBase {
|
|
1795
|
+
relation = this.typeChecker.relation.identity;
|
|
1796
|
+
relationExplanationText = "identical to";
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
class ToBeAssignableTo extends RelationMatcherBase {
|
|
1739
1800
|
relation = this.typeChecker.relation.assignable;
|
|
1740
1801
|
relationExplanationText = "assignable to";
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
class ToBeAssignableWith extends RelationMatcherBase {
|
|
1805
|
+
relation = this.typeChecker.relation.assignable;
|
|
1806
|
+
relationExplanationText = "assignable with";
|
|
1741
1807
|
match(sourceType, targetType, isNot) {
|
|
1742
1808
|
const isMatch = this.typeChecker.isTypeRelatedTo(targetType, sourceType, this.relation);
|
|
1743
1809
|
return {
|
|
@@ -1747,11 +1813,6 @@ class ToBeAssignable extends RelationMatcherBase {
|
|
|
1747
1813
|
}
|
|
1748
1814
|
}
|
|
1749
1815
|
|
|
1750
|
-
class ToEqual extends RelationMatcherBase {
|
|
1751
|
-
relation = this.typeChecker.relation.identity;
|
|
1752
|
-
relationExplanationText = "identical to";
|
|
1753
|
-
}
|
|
1754
|
-
|
|
1755
1816
|
class ToHaveProperty {
|
|
1756
1817
|
compiler;
|
|
1757
1818
|
typeChecker;
|
|
@@ -1795,7 +1856,8 @@ class ToHaveProperty {
|
|
|
1795
1856
|
|
|
1796
1857
|
class ToMatch extends RelationMatcherBase {
|
|
1797
1858
|
relation = this.typeChecker.relation.subtype;
|
|
1798
|
-
relationExplanationText = "
|
|
1859
|
+
relationExplanationText = "match";
|
|
1860
|
+
relationExplanationVerb = "does";
|
|
1799
1861
|
}
|
|
1800
1862
|
|
|
1801
1863
|
class ToRaiseError {
|
|
@@ -1815,16 +1877,16 @@ class ToRaiseError {
|
|
|
1815
1877
|
Diagnostic.error(`The raised type error${source.diagnostics.length === 1 ? "" : "s"}:`),
|
|
1816
1878
|
...Diagnostic.fromDiagnostics(source.diagnostics, this.compiler),
|
|
1817
1879
|
];
|
|
1818
|
-
const text = `${sourceText} raised ${source.diagnostics.length === 1 ? "a" : source.diagnostics.length} type error${source.diagnostics.length === 1 ? "" : "s"}.`;
|
|
1880
|
+
const text = `${sourceText} raised ${source.diagnostics.length === 1 ? "a" : String(source.diagnostics.length)} type error${source.diagnostics.length === 1 ? "" : "s"}.`;
|
|
1819
1881
|
return [Diagnostic.error(text).add({ related })];
|
|
1820
1882
|
}
|
|
1821
1883
|
if (source.diagnostics.length !== targetTypes.length) {
|
|
1822
1884
|
const expectedText = source.diagnostics.length > targetTypes.length
|
|
1823
|
-
? `only ${targetTypes.length} type error${targetTypes.length === 1 ? "" : "s"}`
|
|
1824
|
-
: `${targetTypes.length} type error${targetTypes.length === 1 ? "" : "s"}`;
|
|
1885
|
+
? `only ${String(targetTypes.length)} type error${targetTypes.length === 1 ? "" : "s"}`
|
|
1886
|
+
: `${String(targetTypes.length)} type error${targetTypes.length === 1 ? "" : "s"}`;
|
|
1825
1887
|
const foundText = source.diagnostics.length > targetTypes.length
|
|
1826
|
-
?
|
|
1827
|
-
: `only ${source.diagnostics.length}`;
|
|
1888
|
+
? String(source.diagnostics.length)
|
|
1889
|
+
: `only ${String(source.diagnostics.length)}`;
|
|
1828
1890
|
const related = [
|
|
1829
1891
|
Diagnostic.error(`The raised type error${source.diagnostics.length === 1 ? "" : "s"}:`),
|
|
1830
1892
|
...Diagnostic.fromDiagnostics(source.diagnostics, this.compiler),
|
|
@@ -1842,7 +1904,7 @@ class ToRaiseError {
|
|
|
1842
1904
|
if (!isNot && !isMatch) {
|
|
1843
1905
|
const expectedText = this.#isStringLiteralType(argument)
|
|
1844
1906
|
? `matching substring '${argument.value}'`
|
|
1845
|
-
: `with code ${argument.value}`;
|
|
1907
|
+
: `with code ${String(argument.value)}`;
|
|
1846
1908
|
const related = [
|
|
1847
1909
|
Diagnostic.error("The raised type error:"),
|
|
1848
1910
|
...Diagnostic.fromDiagnostics([diagnostic], this.compiler),
|
|
@@ -1853,7 +1915,7 @@ class ToRaiseError {
|
|
|
1853
1915
|
if (isNot && isMatch) {
|
|
1854
1916
|
const expectedText = this.#isStringLiteralType(argument)
|
|
1855
1917
|
? `matching substring '${argument.value}'`
|
|
1856
|
-
: `with code ${argument.value}`;
|
|
1918
|
+
: `with code ${String(argument.value)}`;
|
|
1857
1919
|
const related = [
|
|
1858
1920
|
Diagnostic.error("The raised type error:"),
|
|
1859
1921
|
...Diagnostic.fromDiagnostics([diagnostic], this.compiler),
|
|
@@ -1897,8 +1959,11 @@ class ToRaiseError {
|
|
|
1897
1959
|
class Expect {
|
|
1898
1960
|
compiler;
|
|
1899
1961
|
typeChecker;
|
|
1962
|
+
toBe;
|
|
1900
1963
|
toBeAny;
|
|
1901
1964
|
toBeAssignable;
|
|
1965
|
+
toBeAssignableTo;
|
|
1966
|
+
toBeAssignableWith;
|
|
1902
1967
|
toBeBigInt;
|
|
1903
1968
|
toBeBoolean;
|
|
1904
1969
|
toBeNever;
|
|
@@ -1917,8 +1982,11 @@ class Expect {
|
|
|
1917
1982
|
constructor(compiler, typeChecker) {
|
|
1918
1983
|
this.compiler = compiler;
|
|
1919
1984
|
this.typeChecker = typeChecker;
|
|
1985
|
+
this.toBe = new ToBe(this.typeChecker);
|
|
1920
1986
|
this.toBeAny = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Any);
|
|
1921
|
-
this.toBeAssignable = new
|
|
1987
|
+
this.toBeAssignable = new ToBeAssignableWith(this.typeChecker);
|
|
1988
|
+
this.toBeAssignableTo = new ToBeAssignableTo(this.typeChecker);
|
|
1989
|
+
this.toBeAssignableWith = new ToBeAssignableWith(this.typeChecker);
|
|
1922
1990
|
this.toBeBigInt = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.BigInt);
|
|
1923
1991
|
this.toBeBoolean = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Boolean);
|
|
1924
1992
|
this.toBeNever = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Never);
|
|
@@ -1930,7 +1998,7 @@ class Expect {
|
|
|
1930
1998
|
this.toBeUniqueSymbol = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.UniqueESSymbol);
|
|
1931
1999
|
this.toBeUnknown = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Unknown);
|
|
1932
2000
|
this.toBeVoid = new PrimitiveTypeMatcher(this.typeChecker, this.compiler.TypeFlags.Void);
|
|
1933
|
-
this.toEqual = new
|
|
2001
|
+
this.toEqual = new ToBe(this.typeChecker);
|
|
1934
2002
|
this.toHaveProperty = new ToHaveProperty(this.compiler, this.typeChecker);
|
|
1935
2003
|
this.toMatch = new ToMatch(this.typeChecker);
|
|
1936
2004
|
this.toRaiseError = new ToRaiseError(this.compiler, this.typeChecker);
|
|
@@ -1960,6 +2028,10 @@ class Expect {
|
|
|
1960
2028
|
switch (matcherNameText) {
|
|
1961
2029
|
case "toBeAssignable":
|
|
1962
2030
|
case "toEqual":
|
|
2031
|
+
this.#onDeprecatedMatcher(assertion);
|
|
2032
|
+
case "toBe":
|
|
2033
|
+
case "toBeAssignableTo":
|
|
2034
|
+
case "toBeAssignableWith":
|
|
1963
2035
|
case "toMatch":
|
|
1964
2036
|
if (assertion.source[0] == null) {
|
|
1965
2037
|
this.#onSourceArgumentMustBeProvided(assertion, expectResult);
|
|
@@ -2027,6 +2099,22 @@ class Expect {
|
|
|
2027
2099
|
return;
|
|
2028
2100
|
}
|
|
2029
2101
|
}
|
|
2102
|
+
#onDeprecatedMatcher(assertion) {
|
|
2103
|
+
const matcherNameText = assertion.matcherName.getText();
|
|
2104
|
+
const text = [
|
|
2105
|
+
`'.${matcherNameText}()' is deprecated and will be removed in TSTyche 3.`,
|
|
2106
|
+
"To learn more, visit https://tstyche.org/release-notes/tstyche-2",
|
|
2107
|
+
];
|
|
2108
|
+
const origin = {
|
|
2109
|
+
end: assertion.matcherName.getEnd(),
|
|
2110
|
+
file: assertion.matcherName.getSourceFile(),
|
|
2111
|
+
start: assertion.matcherName.getStart(),
|
|
2112
|
+
};
|
|
2113
|
+
EventEmitter.dispatch([
|
|
2114
|
+
"deprecation:info",
|
|
2115
|
+
{ diagnostics: [Diagnostic.warning(text, origin)] },
|
|
2116
|
+
]);
|
|
2117
|
+
}
|
|
2030
2118
|
#onKeyArgumentMustBeOfType(node, expectResult) {
|
|
2031
2119
|
const receivedTypeText = this.typeChecker.typeToString(this.#getType(node));
|
|
2032
2120
|
const text = `An argument for 'key' must be of type 'string | number | symbol', received: '${receivedTypeText}'.`;
|
|
@@ -2320,6 +2408,7 @@ class TestTreeWorker {
|
|
|
2320
2408
|
}
|
|
2321
2409
|
}
|
|
2322
2410
|
#visitAssertion(assertion, runMode, parentResult) {
|
|
2411
|
+
this.visit(assertion.members, runMode, parentResult);
|
|
2323
2412
|
const expectResult = new ExpectResult(assertion, parentResult);
|
|
2324
2413
|
EventEmitter.dispatch(["expect:start", { result: expectResult }]);
|
|
2325
2414
|
runMode = this.#resolveRunMode(runMode, assertion);
|
|
@@ -2522,7 +2611,7 @@ class TaskRunner {
|
|
|
2522
2611
|
}
|
|
2523
2612
|
async run(testFiles, target, cancellationToken) {
|
|
2524
2613
|
const result = new Result(this.resolvedConfig, testFiles);
|
|
2525
|
-
EventEmitter.dispatch(["start", { result }]);
|
|
2614
|
+
EventEmitter.dispatch(["run:start", { result }]);
|
|
2526
2615
|
for (const versionTag of target) {
|
|
2527
2616
|
const targetResult = new TargetResult(versionTag, testFiles);
|
|
2528
2617
|
EventEmitter.dispatch(["target:start", { result: targetResult }]);
|
|
@@ -2535,7 +2624,7 @@ class TaskRunner {
|
|
|
2535
2624
|
}
|
|
2536
2625
|
EventEmitter.dispatch(["target:end", { result: targetResult }]);
|
|
2537
2626
|
}
|
|
2538
|
-
EventEmitter.dispatch(["end", { result }]);
|
|
2627
|
+
EventEmitter.dispatch(["run:end", { result }]);
|
|
2539
2628
|
return result;
|
|
2540
2629
|
}
|
|
2541
2630
|
}
|
|
@@ -2552,12 +2641,128 @@ class CancellationToken {
|
|
|
2552
2641
|
}
|
|
2553
2642
|
}
|
|
2554
2643
|
|
|
2644
|
+
class InputService {
|
|
2645
|
+
#stdin;
|
|
2646
|
+
constructor(options) {
|
|
2647
|
+
this.#stdin = options?.stdin ?? process.stdin;
|
|
2648
|
+
this.#stdin.setRawMode?.(true);
|
|
2649
|
+
this.#stdin.setEncoding("utf8");
|
|
2650
|
+
this.#stdin.on("data", (key) => {
|
|
2651
|
+
EventEmitter.dispatch(["input:info", { key }]);
|
|
2652
|
+
});
|
|
2653
|
+
this.#stdin.unref();
|
|
2654
|
+
}
|
|
2655
|
+
dispose() {
|
|
2656
|
+
this.#stdin.setRawMode?.(false);
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
class Watcher {
|
|
2661
|
+
resolvedConfig;
|
|
2662
|
+
#abortController = new AbortController();
|
|
2663
|
+
#changedTestFiles = new Set();
|
|
2664
|
+
#inputService;
|
|
2665
|
+
#runCallback;
|
|
2666
|
+
#runChangedDebounced;
|
|
2667
|
+
#selectService;
|
|
2668
|
+
#testFiles;
|
|
2669
|
+
#watcher;
|
|
2670
|
+
constructor(resolvedConfig, runCallback, selectService, testFiles) {
|
|
2671
|
+
this.resolvedConfig = resolvedConfig;
|
|
2672
|
+
this.#inputService = new InputService();
|
|
2673
|
+
this.#runCallback = runCallback;
|
|
2674
|
+
this.#runChangedDebounced = this.#debounce(this.#runChanged, 100);
|
|
2675
|
+
this.#selectService = selectService;
|
|
2676
|
+
this.#testFiles = new Set(testFiles);
|
|
2677
|
+
EventEmitter.addHandler(([eventName, payload]) => {
|
|
2678
|
+
if (eventName === "input:info") {
|
|
2679
|
+
switch (payload.key) {
|
|
2680
|
+
case "\u000D":
|
|
2681
|
+
case "\u0041":
|
|
2682
|
+
case "\u0061":
|
|
2683
|
+
void this.#runAll();
|
|
2684
|
+
break;
|
|
2685
|
+
case "\u0003":
|
|
2686
|
+
case "\u0004":
|
|
2687
|
+
case "\u001B":
|
|
2688
|
+
case "\u0058":
|
|
2689
|
+
case "\u0078":
|
|
2690
|
+
this.#abortController.abort();
|
|
2691
|
+
break;
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
});
|
|
2695
|
+
}
|
|
2696
|
+
#debounce(target, delay) {
|
|
2697
|
+
let timeout;
|
|
2698
|
+
return function (...args) {
|
|
2699
|
+
clearTimeout(timeout);
|
|
2700
|
+
timeout = setTimeout(() => {
|
|
2701
|
+
target.apply(this, args);
|
|
2702
|
+
}, delay);
|
|
2703
|
+
};
|
|
2704
|
+
}
|
|
2705
|
+
#onChanged(fileName) {
|
|
2706
|
+
if (this.#testFiles.has(fileName)) {
|
|
2707
|
+
this.#changedTestFiles.add(fileName);
|
|
2708
|
+
return;
|
|
2709
|
+
}
|
|
2710
|
+
if (this.#selectService.isTestFile(fileName)) {
|
|
2711
|
+
this.#changedTestFiles.add(fileName);
|
|
2712
|
+
this.#testFiles.add(fileName);
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
#onExit() {
|
|
2716
|
+
this.#inputService.dispose();
|
|
2717
|
+
this.#watcher = undefined;
|
|
2718
|
+
}
|
|
2719
|
+
#onRemoved(fileName) {
|
|
2720
|
+
this.#changedTestFiles.delete(fileName);
|
|
2721
|
+
this.#testFiles.delete(fileName);
|
|
2722
|
+
}
|
|
2723
|
+
async #runAll() {
|
|
2724
|
+
await this.#runCallback([...this.#testFiles].sort());
|
|
2725
|
+
}
|
|
2726
|
+
async #runChanged() {
|
|
2727
|
+
await this.#runCallback([...this.#changedTestFiles].sort());
|
|
2728
|
+
this.#changedTestFiles.clear();
|
|
2729
|
+
}
|
|
2730
|
+
async watch() {
|
|
2731
|
+
this.#watcher = fs.watch(this.resolvedConfig.rootPath, { recursive: true, signal: this.#abortController.signal });
|
|
2732
|
+
await this.#runAll();
|
|
2733
|
+
try {
|
|
2734
|
+
for await (const event of this.#watcher) {
|
|
2735
|
+
if (event.filename != null) {
|
|
2736
|
+
const filePath = Path.resolve(this.resolvedConfig.rootPath, event.filename);
|
|
2737
|
+
if (!existsSync(filePath)) {
|
|
2738
|
+
this.#onRemoved(filePath);
|
|
2739
|
+
continue;
|
|
2740
|
+
}
|
|
2741
|
+
this.#onChanged(filePath);
|
|
2742
|
+
}
|
|
2743
|
+
if (this.#changedTestFiles.size !== 0) {
|
|
2744
|
+
await this.#runChangedDebounced();
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
catch (error) {
|
|
2749
|
+
if (error != null && typeof error === "object" && "name" in error && error.name === "AbortError") ;
|
|
2750
|
+
else {
|
|
2751
|
+
throw error;
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
finally {
|
|
2755
|
+
this.#onExit();
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2555
2760
|
class TSTyche {
|
|
2556
2761
|
resolvedConfig;
|
|
2557
2762
|
#cancellationToken = new CancellationToken();
|
|
2558
2763
|
#storeService;
|
|
2559
2764
|
#taskRunner;
|
|
2560
|
-
static version = "
|
|
2765
|
+
static version = "2.0.0-beta.0";
|
|
2561
2766
|
constructor(resolvedConfig, storeService) {
|
|
2562
2767
|
this.resolvedConfig = resolvedConfig;
|
|
2563
2768
|
this.#storeService = storeService;
|
|
@@ -2569,17 +2774,27 @@ class TSTyche {
|
|
|
2569
2774
|
if (eventName.includes("error") || eventName.includes("fail")) {
|
|
2570
2775
|
if ("diagnostics" in payload
|
|
2571
2776
|
&& payload.diagnostics.some((diagnostic) => diagnostic.category === "error")) {
|
|
2572
|
-
|
|
2777
|
+
if (this.resolvedConfig.watch !== true) {
|
|
2778
|
+
process.exitCode = 1;
|
|
2779
|
+
}
|
|
2573
2780
|
if (this.resolvedConfig.failFast) {
|
|
2574
2781
|
this.#cancellationToken.cancel();
|
|
2575
2782
|
}
|
|
2576
2783
|
}
|
|
2577
2784
|
}
|
|
2578
2785
|
});
|
|
2579
|
-
const
|
|
2580
|
-
|
|
2786
|
+
const reporters = [
|
|
2787
|
+
new ThoroughReporter(this.resolvedConfig),
|
|
2788
|
+
];
|
|
2789
|
+
if (this.resolvedConfig.watch === true) {
|
|
2790
|
+
reporters.push(new WatchModeReporter(this.resolvedConfig));
|
|
2791
|
+
}
|
|
2792
|
+
else {
|
|
2793
|
+
reporters.push(new SummaryReporter(this.resolvedConfig));
|
|
2794
|
+
}
|
|
2795
|
+
for (const reporter of reporters) {
|
|
2581
2796
|
EventEmitter.addHandler((event) => {
|
|
2582
|
-
|
|
2797
|
+
reporter.handleEvent(event);
|
|
2583
2798
|
});
|
|
2584
2799
|
}
|
|
2585
2800
|
}
|
|
@@ -2597,6 +2812,11 @@ class TSTyche {
|
|
|
2597
2812
|
async run(testFiles) {
|
|
2598
2813
|
await this.#taskRunner.run(this.#normalizePaths(testFiles), this.resolvedConfig.target, this.#cancellationToken);
|
|
2599
2814
|
}
|
|
2815
|
+
async watch(testFiles, selectService) {
|
|
2816
|
+
const runCallback = async (testFiles) => this.run(testFiles);
|
|
2817
|
+
const watcher = new Watcher(this.resolvedConfig, runCallback, selectService, testFiles);
|
|
2818
|
+
await watcher.watch();
|
|
2819
|
+
}
|
|
2600
2820
|
}
|
|
2601
2821
|
|
|
2602
2822
|
class OptionDefinitionsMap {
|
|
@@ -2694,6 +2914,12 @@ class OptionDefinitionsMap {
|
|
|
2694
2914
|
group: 2,
|
|
2695
2915
|
name: "version",
|
|
2696
2916
|
},
|
|
2917
|
+
{
|
|
2918
|
+
brand: "boolean",
|
|
2919
|
+
description: "Watch for changes and rerun related tests files.",
|
|
2920
|
+
group: 2,
|
|
2921
|
+
name: "watch",
|
|
2922
|
+
},
|
|
2697
2923
|
];
|
|
2698
2924
|
static for(optionGroup) {
|
|
2699
2925
|
const definitionMap = new Map();
|
|
@@ -2714,13 +2940,13 @@ class OptionDiagnosticText {
|
|
|
2714
2940
|
doubleQuotesExpected() {
|
|
2715
2941
|
return "String literal with double quotes expected.";
|
|
2716
2942
|
}
|
|
2717
|
-
expectsArgument(optionName) {
|
|
2718
|
-
optionName = this.#optionName(optionName);
|
|
2719
|
-
return `Option '${optionName}' expects an argument.`;
|
|
2720
|
-
}
|
|
2721
2943
|
expectsListItemType(optionName, optionBrand) {
|
|
2722
2944
|
return `Item of the '${optionName}' list must be of type ${optionBrand}.`;
|
|
2723
2945
|
}
|
|
2946
|
+
expectsValue(optionName) {
|
|
2947
|
+
optionName = this.#optionName(optionName);
|
|
2948
|
+
return `Option '${optionName}' expects a value.`;
|
|
2949
|
+
}
|
|
2724
2950
|
fileDoesNotExist(filePath) {
|
|
2725
2951
|
return `The specified path '${filePath}' does not exist.`;
|
|
2726
2952
|
}
|
|
@@ -2732,9 +2958,9 @@ class OptionDiagnosticText {
|
|
|
2732
2958
|
return optionName;
|
|
2733
2959
|
}
|
|
2734
2960
|
}
|
|
2735
|
-
|
|
2961
|
+
requiresValueType(optionName, optionBrand) {
|
|
2736
2962
|
optionName = this.#optionName(optionName);
|
|
2737
|
-
return `Option '${optionName}' requires
|
|
2963
|
+
return `Option '${optionName}' requires a value of type ${optionBrand}.`;
|
|
2738
2964
|
}
|
|
2739
2965
|
unknownOption(optionName) {
|
|
2740
2966
|
return `Unknown option '${optionName}'.`;
|
|
@@ -2745,6 +2971,9 @@ class OptionDiagnosticText {
|
|
|
2745
2971
|
}
|
|
2746
2972
|
return `TypeScript version '${value}' is not supported.`;
|
|
2747
2973
|
}
|
|
2974
|
+
watchCannotBeEnabledInCiEnvironment() {
|
|
2975
|
+
return "The watch mode cannot be enabled in a continuous integration environment.";
|
|
2976
|
+
}
|
|
2748
2977
|
}
|
|
2749
2978
|
|
|
2750
2979
|
class OptionUsageText {
|
|
@@ -2764,7 +2993,7 @@ class OptionUsageText {
|
|
|
2764
2993
|
const supportedTagsText = `Supported tags: ${["'", supportedTags.join("', '"), "'"].join("")}.`;
|
|
2765
2994
|
switch (this.#optionGroup) {
|
|
2766
2995
|
case 2:
|
|
2767
|
-
usageText.push("
|
|
2996
|
+
usageText.push("Value for the '--target' option must be a single tag or a comma separated list.", "Usage examples: '--target 4.9', '--target latest', '--target 4.9,5.3.2,current'.", supportedTagsText);
|
|
2768
2997
|
break;
|
|
2769
2998
|
case 4:
|
|
2770
2999
|
usageText.push("Item of the 'target' list must be a supported version tag.", supportedTagsText);
|
|
@@ -2773,7 +3002,7 @@ class OptionUsageText {
|
|
|
2773
3002
|
break;
|
|
2774
3003
|
}
|
|
2775
3004
|
default:
|
|
2776
|
-
usageText.push(this.#optionDiagnosticText.
|
|
3005
|
+
usageText.push(this.#optionDiagnosticText.requiresValueType(optionName, optionBrand));
|
|
2777
3006
|
}
|
|
2778
3007
|
return usageText;
|
|
2779
3008
|
}
|
|
@@ -2797,8 +3026,9 @@ class OptionValidator {
|
|
|
2797
3026
|
case "config":
|
|
2798
3027
|
case "rootPath":
|
|
2799
3028
|
if (!existsSync(optionValue)) {
|
|
2800
|
-
|
|
2801
|
-
|
|
3029
|
+
this.#onDiagnostic(Diagnostic.error([
|
|
3030
|
+
this.#optionDiagnosticText.fileDoesNotExist(optionValue),
|
|
3031
|
+
], origin));
|
|
2802
3032
|
}
|
|
2803
3033
|
break;
|
|
2804
3034
|
case "target":
|
|
@@ -2809,6 +3039,13 @@ class OptionValidator {
|
|
|
2809
3039
|
], origin));
|
|
2810
3040
|
}
|
|
2811
3041
|
break;
|
|
3042
|
+
case "watch":
|
|
3043
|
+
if (Environment.isCi) {
|
|
3044
|
+
this.#onDiagnostic(Diagnostic.error([
|
|
3045
|
+
this.#optionDiagnosticText.watchCannotBeEnabledInCiEnvironment(),
|
|
3046
|
+
], origin));
|
|
3047
|
+
}
|
|
3048
|
+
break;
|
|
2812
3049
|
}
|
|
2813
3050
|
}
|
|
2814
3051
|
}
|
|
@@ -2832,9 +3069,9 @@ class CommandLineOptionsWorker {
|
|
|
2832
3069
|
this.#optionUsageText = new OptionUsageText(2, this.#storeService);
|
|
2833
3070
|
this.#optionValidator = new OptionValidator(2, this.#storeService, this.#onDiagnostic);
|
|
2834
3071
|
}
|
|
2835
|
-
async #
|
|
3072
|
+
async #onExpectsValue(optionDefinition) {
|
|
2836
3073
|
const text = [
|
|
2837
|
-
this.#optionDiagnosticText.
|
|
3074
|
+
this.#optionDiagnosticText.expectsValue(optionDefinition.name),
|
|
2838
3075
|
...(await this.#optionUsageText.get(optionDefinition.name, optionDefinition.brand)),
|
|
2839
3076
|
];
|
|
2840
3077
|
this.#onDiagnostic(Diagnostic.error(text));
|
|
@@ -2870,13 +3107,14 @@ class CommandLineOptionsWorker {
|
|
|
2870
3107
|
this.#commandLineOptions[optionDefinition.name] = true;
|
|
2871
3108
|
break;
|
|
2872
3109
|
case "boolean":
|
|
3110
|
+
await this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand);
|
|
2873
3111
|
this.#commandLineOptions[optionDefinition.name] = optionValue !== "false";
|
|
2874
3112
|
if (optionValue === "false" || optionValue === "true") {
|
|
2875
3113
|
index++;
|
|
2876
3114
|
}
|
|
2877
3115
|
break;
|
|
2878
3116
|
case "list":
|
|
2879
|
-
if (optionValue
|
|
3117
|
+
if (optionValue !== "") {
|
|
2880
3118
|
const optionValues = optionValue
|
|
2881
3119
|
.split(",")
|
|
2882
3120
|
.map((value) => value.trim())
|
|
@@ -2888,10 +3126,10 @@ class CommandLineOptionsWorker {
|
|
|
2888
3126
|
index++;
|
|
2889
3127
|
break;
|
|
2890
3128
|
}
|
|
2891
|
-
await this.#
|
|
3129
|
+
await this.#onExpectsValue(optionDefinition);
|
|
2892
3130
|
break;
|
|
2893
3131
|
case "string":
|
|
2894
|
-
if (optionValue
|
|
3132
|
+
if (optionValue !== "") {
|
|
2895
3133
|
if (optionDefinition.name === "config") {
|
|
2896
3134
|
optionValue = Path.resolve(optionValue);
|
|
2897
3135
|
}
|
|
@@ -2900,16 +3138,13 @@ class CommandLineOptionsWorker {
|
|
|
2900
3138
|
index++;
|
|
2901
3139
|
break;
|
|
2902
3140
|
}
|
|
2903
|
-
await this.#
|
|
3141
|
+
await this.#onExpectsValue(optionDefinition);
|
|
2904
3142
|
break;
|
|
2905
3143
|
}
|
|
2906
3144
|
return index;
|
|
2907
3145
|
}
|
|
2908
|
-
#resolveOptionValue(
|
|
2909
|
-
|
|
2910
|
-
return;
|
|
2911
|
-
}
|
|
2912
|
-
return optionValue;
|
|
3146
|
+
#resolveOptionValue(target = "") {
|
|
3147
|
+
return target.startsWith("-") ? "" : target;
|
|
2913
3148
|
}
|
|
2914
3149
|
}
|
|
2915
3150
|
|
|
@@ -3034,7 +3269,7 @@ class ConfigFileOptionsWorker {
|
|
|
3034
3269
|
};
|
|
3035
3270
|
const text = isListItem
|
|
3036
3271
|
? this.#optionDiagnosticText.expectsListItemType(optionDefinition.name, optionDefinition.brand)
|
|
3037
|
-
: this.#optionDiagnosticText.
|
|
3272
|
+
: this.#optionDiagnosticText.requiresValueType(optionDefinition.name, optionDefinition.brand);
|
|
3038
3273
|
this.#onDiagnostic(Diagnostic.error(text, origin));
|
|
3039
3274
|
return;
|
|
3040
3275
|
}
|
|
@@ -3107,9 +3342,9 @@ class ConfigService {
|
|
|
3107
3342
|
static get defaultOptions() {
|
|
3108
3343
|
return ConfigService.#defaultOptions;
|
|
3109
3344
|
}
|
|
3110
|
-
#onDiagnostic
|
|
3345
|
+
#onDiagnostic(diagnostic) {
|
|
3111
3346
|
EventEmitter.dispatch(["config:error", { diagnostics: [diagnostic] }]);
|
|
3112
|
-
}
|
|
3347
|
+
}
|
|
3113
3348
|
async parseCommandLine(commandLineArgs) {
|
|
3114
3349
|
this.#commandLineOptions = {};
|
|
3115
3350
|
this.#pathMatch = [];
|
|
@@ -3139,28 +3374,6 @@ class ConfigService {
|
|
|
3139
3374
|
};
|
|
3140
3375
|
return mergedOptions;
|
|
3141
3376
|
}
|
|
3142
|
-
selectTestFiles() {
|
|
3143
|
-
const { pathMatch, rootPath, testFileMatch } = this.resolveConfig();
|
|
3144
|
-
let testFilePaths = this.compiler.sys.readDirectory(rootPath, ["ts", "tsx", "mts", "cts", "js", "jsx", "mjs", "cjs"], undefined, testFileMatch);
|
|
3145
|
-
if (pathMatch.length > 0) {
|
|
3146
|
-
testFilePaths = testFilePaths.filter((testFilePath) => pathMatch.some((match) => {
|
|
3147
|
-
const relativeTestFilePath = Path.relative("", testFilePath);
|
|
3148
|
-
return relativeTestFilePath.toLowerCase().includes(match.toLowerCase());
|
|
3149
|
-
}));
|
|
3150
|
-
}
|
|
3151
|
-
if (testFilePaths.length === 0) {
|
|
3152
|
-
const text = [
|
|
3153
|
-
"No test files were selected using current configuration.",
|
|
3154
|
-
`Root path: ${rootPath}`,
|
|
3155
|
-
`Test file match: ${testFileMatch.join(", ")}`,
|
|
3156
|
-
];
|
|
3157
|
-
if (pathMatch.length > 0) {
|
|
3158
|
-
text.push(`Path match: ${pathMatch.join(", ")}`);
|
|
3159
|
-
}
|
|
3160
|
-
this.#onDiagnostic(Diagnostic.error(text));
|
|
3161
|
-
}
|
|
3162
|
-
return testFilePaths;
|
|
3163
|
-
}
|
|
3164
3377
|
}
|
|
3165
3378
|
|
|
3166
3379
|
var OptionBrand;
|
|
@@ -3177,20 +3390,134 @@ var OptionGroup;
|
|
|
3177
3390
|
OptionGroup[OptionGroup["ConfigFile"] = 4] = "ConfigFile";
|
|
3178
3391
|
})(OptionGroup || (OptionGroup = {}));
|
|
3179
3392
|
|
|
3393
|
+
class GlobPattern {
|
|
3394
|
+
static #reservedCharacterRegex = /[^\w\s/]/g;
|
|
3395
|
+
static #parse(pattern, usageTarget) {
|
|
3396
|
+
const segments = pattern.split("/");
|
|
3397
|
+
let resultPattern = "\\.";
|
|
3398
|
+
let optionalSegmentCount = 0;
|
|
3399
|
+
for (const segment of segments) {
|
|
3400
|
+
if (segment === "**") {
|
|
3401
|
+
resultPattern += "(\\/(?!(node_modules)(\\/|$))[^./][^/]*)*?";
|
|
3402
|
+
continue;
|
|
3403
|
+
}
|
|
3404
|
+
if (usageTarget === "directories") {
|
|
3405
|
+
resultPattern += "(";
|
|
3406
|
+
optionalSegmentCount++;
|
|
3407
|
+
}
|
|
3408
|
+
resultPattern += `\\/`;
|
|
3409
|
+
const segmentPattern = segment.replace(GlobPattern.#reservedCharacterRegex, GlobPattern.#replaceReservedCharacter);
|
|
3410
|
+
if (segmentPattern !== segment) {
|
|
3411
|
+
resultPattern += "(?!(node_modules)(\\/|$))";
|
|
3412
|
+
}
|
|
3413
|
+
resultPattern += segmentPattern;
|
|
3414
|
+
}
|
|
3415
|
+
resultPattern += ")?".repeat(optionalSegmentCount);
|
|
3416
|
+
return resultPattern;
|
|
3417
|
+
}
|
|
3418
|
+
static #replaceReservedCharacter(match, offset) {
|
|
3419
|
+
switch (match) {
|
|
3420
|
+
case "*":
|
|
3421
|
+
return (offset === 0) ? "([^./][^/]*)?" : "([^/]*)?";
|
|
3422
|
+
case "?":
|
|
3423
|
+
return (offset === 0) ? "[^./]" : "[^/]";
|
|
3424
|
+
default:
|
|
3425
|
+
return `\\${match}`;
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
static toRegex(patterns, usageTarget) {
|
|
3429
|
+
const patternText = patterns.map((pattern) => `(${GlobPattern.#parse(pattern, usageTarget)})`).join("|");
|
|
3430
|
+
return new RegExp(`^(${patternText})$`);
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3433
|
+
|
|
3434
|
+
class SelectService {
|
|
3435
|
+
resolvedConfig;
|
|
3436
|
+
#includeDirectoryRegex;
|
|
3437
|
+
#includeFileRegex;
|
|
3438
|
+
constructor(resolvedConfig) {
|
|
3439
|
+
this.resolvedConfig = resolvedConfig;
|
|
3440
|
+
this.#includeDirectoryRegex = GlobPattern.toRegex(resolvedConfig.testFileMatch, "directories");
|
|
3441
|
+
this.#includeFileRegex = GlobPattern.toRegex(resolvedConfig.testFileMatch, "files");
|
|
3442
|
+
}
|
|
3443
|
+
#isDirectoryIncluded(directoryPath) {
|
|
3444
|
+
return this.#includeDirectoryRegex.test(directoryPath);
|
|
3445
|
+
}
|
|
3446
|
+
#isFileIncluded(filePath) {
|
|
3447
|
+
if (this.resolvedConfig.pathMatch.length > 0
|
|
3448
|
+
&& !this.resolvedConfig.pathMatch.some((match) => filePath.toLowerCase().includes(match.toLowerCase()))) {
|
|
3449
|
+
return false;
|
|
3450
|
+
}
|
|
3451
|
+
return this.#includeFileRegex.test(filePath);
|
|
3452
|
+
}
|
|
3453
|
+
isTestFile(filePath) {
|
|
3454
|
+
return this.#isFileIncluded(Path.relative(this.resolvedConfig.rootPath, filePath));
|
|
3455
|
+
}
|
|
3456
|
+
#onDiagnostic(diagnostic) {
|
|
3457
|
+
EventEmitter.dispatch(["select:error", { diagnostics: [diagnostic] }]);
|
|
3458
|
+
}
|
|
3459
|
+
async selectFiles() {
|
|
3460
|
+
const currentPath = ".";
|
|
3461
|
+
const testFilePaths = [];
|
|
3462
|
+
await this.#visitDirectory(currentPath, testFilePaths);
|
|
3463
|
+
if (testFilePaths.length === 0) {
|
|
3464
|
+
const text = [
|
|
3465
|
+
"No test files were selected using current configuration.",
|
|
3466
|
+
`Root path: ${this.resolvedConfig.rootPath}`,
|
|
3467
|
+
`Test file match: ${this.resolvedConfig.testFileMatch.join(", ")}`,
|
|
3468
|
+
];
|
|
3469
|
+
if (this.resolvedConfig.pathMatch.length > 0) {
|
|
3470
|
+
text.push(`Path match: ${this.resolvedConfig.pathMatch.join(", ")}`);
|
|
3471
|
+
}
|
|
3472
|
+
this.#onDiagnostic(Diagnostic.error(text));
|
|
3473
|
+
}
|
|
3474
|
+
return testFilePaths.sort();
|
|
3475
|
+
}
|
|
3476
|
+
async #visitDirectory(currentPath, testFilePaths) {
|
|
3477
|
+
const targetPath = Path.join(this.resolvedConfig.rootPath, currentPath);
|
|
3478
|
+
let entries = [];
|
|
3479
|
+
try {
|
|
3480
|
+
entries = await fs.readdir(targetPath, { withFileTypes: true });
|
|
3481
|
+
}
|
|
3482
|
+
catch {
|
|
3483
|
+
}
|
|
3484
|
+
for (const entry of entries) {
|
|
3485
|
+
let entryMeta;
|
|
3486
|
+
if (entry.isSymbolicLink()) {
|
|
3487
|
+
try {
|
|
3488
|
+
entryMeta = await fs.stat([targetPath, entry.name].join("/"));
|
|
3489
|
+
}
|
|
3490
|
+
catch {
|
|
3491
|
+
continue;
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
else {
|
|
3495
|
+
entryMeta = entry;
|
|
3496
|
+
}
|
|
3497
|
+
const entryPath = [currentPath, entry.name].join("/");
|
|
3498
|
+
if (entryMeta.isDirectory() && this.#isDirectoryIncluded(entryPath)) {
|
|
3499
|
+
await this.#visitDirectory(entryPath, testFilePaths);
|
|
3500
|
+
continue;
|
|
3501
|
+
}
|
|
3502
|
+
if (entryMeta.isFile() && this.#isFileIncluded(entryPath)) {
|
|
3503
|
+
testFilePaths.push([targetPath, entry.name].join("/"));
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
|
|
3180
3509
|
class ManifestWorker {
|
|
3181
3510
|
#manifestFileName = "store-manifest.json";
|
|
3182
3511
|
#manifestFilePath;
|
|
3183
3512
|
#onDiagnostic;
|
|
3184
|
-
#prune;
|
|
3185
3513
|
#registryUrl = new URL("https://registry.npmjs.org");
|
|
3186
3514
|
#storePath;
|
|
3187
3515
|
#timeout = Environment.timeout * 1000;
|
|
3188
3516
|
#version = "1";
|
|
3189
|
-
constructor(storePath, onDiagnostic
|
|
3517
|
+
constructor(storePath, onDiagnostic) {
|
|
3190
3518
|
this.#storePath = storePath;
|
|
3191
3519
|
this.#onDiagnostic = onDiagnostic;
|
|
3192
3520
|
this.#manifestFilePath = Path.join(storePath, this.#manifestFileName);
|
|
3193
|
-
this.#prune = prune;
|
|
3194
3521
|
}
|
|
3195
3522
|
async #create() {
|
|
3196
3523
|
const manifest = await this.#load();
|
|
@@ -3251,7 +3578,7 @@ class ManifestWorker {
|
|
|
3251
3578
|
}
|
|
3252
3579
|
const text = [`Failed to fetch metadata of the 'typescript' package from '${this.#registryUrl.toString()}'.`];
|
|
3253
3580
|
if (error instanceof Error && "code" in error && error.code === "ECONNRESET") {
|
|
3254
|
-
text.push(`Setup timeout of ${this.#timeout / 1000}s was exceeded.`);
|
|
3581
|
+
text.push(`Setup timeout of ${String(this.#timeout / 1000)}s was exceeded.`);
|
|
3255
3582
|
}
|
|
3256
3583
|
else {
|
|
3257
3584
|
text.push("Might be there is an issue with the registry or the network connection.");
|
|
@@ -3298,7 +3625,7 @@ class ManifestWorker {
|
|
|
3298
3625
|
catch {
|
|
3299
3626
|
}
|
|
3300
3627
|
if (manifest == null || manifest.$version !== this.#version) {
|
|
3301
|
-
await this.#
|
|
3628
|
+
await fs.rm(this.#storePath, { force: true, recursive: true });
|
|
3302
3629
|
return this.#create();
|
|
3303
3630
|
}
|
|
3304
3631
|
if (this.isOutdated(manifest) || options?.refresh === true) {
|
|
@@ -3346,7 +3673,7 @@ class Lock {
|
|
|
3346
3673
|
break;
|
|
3347
3674
|
}
|
|
3348
3675
|
if (Date.now() - waitStartTime > options.timeout) {
|
|
3349
|
-
options.onDiagnostic?.(`Lock wait timeout of ${options.timeout / 1000}s was exceeded.`);
|
|
3676
|
+
options.onDiagnostic?.(`Lock wait timeout of ${String(options.timeout / 1000)}s was exceeded.`);
|
|
3350
3677
|
break;
|
|
3351
3678
|
}
|
|
3352
3679
|
await Lock.#sleep(1000);
|
|
@@ -3431,7 +3758,7 @@ class PackageInstaller {
|
|
|
3431
3758
|
resolve();
|
|
3432
3759
|
}
|
|
3433
3760
|
if (signal != null) {
|
|
3434
|
-
reject(new Error(`setup timeout of ${this.#timeout / 1000}s was exceeded`));
|
|
3761
|
+
reject(new Error(`setup timeout of ${String(this.#timeout / 1000)}s was exceeded`));
|
|
3435
3762
|
}
|
|
3436
3763
|
reject(new Error(`process exited with code ${String(code)}`));
|
|
3437
3764
|
});
|
|
@@ -3448,7 +3775,7 @@ class StoreService {
|
|
|
3448
3775
|
constructor() {
|
|
3449
3776
|
this.#storePath = Environment.storePath;
|
|
3450
3777
|
this.#packageInstaller = new PackageInstaller(this.#storePath, this.#onDiagnostic);
|
|
3451
|
-
this.#manifestWorker = new ManifestWorker(this.#storePath, this.#onDiagnostic
|
|
3778
|
+
this.#manifestWorker = new ManifestWorker(this.#storePath, this.#onDiagnostic);
|
|
3452
3779
|
}
|
|
3453
3780
|
async getSupportedTags() {
|
|
3454
3781
|
await this.open();
|
|
@@ -3516,18 +3843,15 @@ class StoreService {
|
|
|
3516
3843
|
}
|
|
3517
3844
|
return module.exports;
|
|
3518
3845
|
}
|
|
3519
|
-
#onDiagnostic
|
|
3846
|
+
#onDiagnostic(diagnostic) {
|
|
3520
3847
|
EventEmitter.dispatch(["store:error", { diagnostics: [diagnostic] }]);
|
|
3521
|
-
}
|
|
3848
|
+
}
|
|
3522
3849
|
async open() {
|
|
3523
3850
|
if (this.#manifest) {
|
|
3524
3851
|
return;
|
|
3525
3852
|
}
|
|
3526
3853
|
this.#manifest = await this.#manifestWorker.open();
|
|
3527
3854
|
}
|
|
3528
|
-
prune = async () => {
|
|
3529
|
-
await fs.rm(this.#storePath, { force: true, recursive: true });
|
|
3530
|
-
};
|
|
3531
3855
|
async resolveTag(tag) {
|
|
3532
3856
|
if (tag === "current") {
|
|
3533
3857
|
return tag;
|
|
@@ -3567,28 +3891,29 @@ class StoreService {
|
|
|
3567
3891
|
|
|
3568
3892
|
class Cli {
|
|
3569
3893
|
#cancellationToken = new CancellationToken();
|
|
3570
|
-
#
|
|
3894
|
+
#outputService;
|
|
3571
3895
|
#storeService;
|
|
3572
3896
|
constructor() {
|
|
3573
|
-
this.#
|
|
3897
|
+
this.#outputService = new OutputService();
|
|
3574
3898
|
this.#storeService = new StoreService();
|
|
3575
3899
|
}
|
|
3576
3900
|
#onStartupEvent = ([eventName, payload]) => {
|
|
3577
3901
|
switch (eventName) {
|
|
3578
3902
|
case "store:info":
|
|
3579
|
-
this.#
|
|
3903
|
+
this.#outputService.writeMessage(addsPackageStepText(payload.compilerVersion, payload.installationPath));
|
|
3580
3904
|
break;
|
|
3581
3905
|
case "config:error":
|
|
3906
|
+
case "select:error":
|
|
3582
3907
|
case "store:error":
|
|
3583
3908
|
for (const diagnostic of payload.diagnostics) {
|
|
3584
3909
|
switch (diagnostic.category) {
|
|
3585
3910
|
case "error":
|
|
3586
3911
|
this.#cancellationToken.cancel();
|
|
3587
|
-
this.#
|
|
3912
|
+
this.#outputService.writeError(diagnosticText(diagnostic));
|
|
3588
3913
|
process.exitCode = 1;
|
|
3589
3914
|
break;
|
|
3590
3915
|
case "warning":
|
|
3591
|
-
this.#
|
|
3916
|
+
this.#outputService.writeWarning(diagnosticText(diagnostic));
|
|
3592
3917
|
break;
|
|
3593
3918
|
}
|
|
3594
3919
|
}
|
|
@@ -3599,15 +3924,15 @@ class Cli {
|
|
|
3599
3924
|
EventEmitter.addHandler(this.#onStartupEvent);
|
|
3600
3925
|
if (commandLineArguments.includes("--help")) {
|
|
3601
3926
|
const commandLineOptionDefinitions = OptionDefinitionsMap.for(2);
|
|
3602
|
-
this.#
|
|
3927
|
+
this.#outputService.writeMessage(helpText(commandLineOptionDefinitions, TSTyche.version));
|
|
3603
3928
|
return;
|
|
3604
3929
|
}
|
|
3605
3930
|
if (commandLineArguments.includes("--prune")) {
|
|
3606
|
-
await
|
|
3931
|
+
await fs.rm(Environment.storePath, { force: true, recursive: true });
|
|
3607
3932
|
return;
|
|
3608
3933
|
}
|
|
3609
3934
|
if (commandLineArguments.includes("--version")) {
|
|
3610
|
-
this.#
|
|
3935
|
+
this.#outputService.writeMessage(formattedText(TSTyche.version));
|
|
3611
3936
|
return;
|
|
3612
3937
|
}
|
|
3613
3938
|
if (commandLineArguments.includes("--update")) {
|
|
@@ -3628,8 +3953,8 @@ class Cli {
|
|
|
3628
3953
|
return;
|
|
3629
3954
|
}
|
|
3630
3955
|
const resolvedConfig = configService.resolveConfig();
|
|
3631
|
-
if (
|
|
3632
|
-
this.#
|
|
3956
|
+
if (commandLineArguments.includes("--showConfig")) {
|
|
3957
|
+
this.#outputService.writeMessage(formattedText({
|
|
3633
3958
|
noColor: Environment.noColor,
|
|
3634
3959
|
noInteractive: Environment.noInteractive,
|
|
3635
3960
|
storePath: Environment.storePath,
|
|
@@ -3639,27 +3964,33 @@ class Cli {
|
|
|
3639
3964
|
}));
|
|
3640
3965
|
return;
|
|
3641
3966
|
}
|
|
3642
|
-
if (
|
|
3967
|
+
if (commandLineArguments.includes("--install")) {
|
|
3643
3968
|
for (const tag of resolvedConfig.target) {
|
|
3644
3969
|
await this.#storeService.install(tag);
|
|
3645
3970
|
}
|
|
3646
3971
|
return;
|
|
3647
3972
|
}
|
|
3973
|
+
const selectService = new SelectService(resolvedConfig);
|
|
3648
3974
|
let testFiles = [];
|
|
3649
3975
|
if (resolvedConfig.testFileMatch.length !== 0) {
|
|
3650
|
-
testFiles =
|
|
3976
|
+
testFiles = await selectService.selectFiles();
|
|
3651
3977
|
if (testFiles.length === 0) {
|
|
3652
3978
|
return;
|
|
3653
3979
|
}
|
|
3654
|
-
if (
|
|
3655
|
-
this.#
|
|
3980
|
+
if (commandLineArguments.includes("--listFiles")) {
|
|
3981
|
+
this.#outputService.writeMessage(formattedText(testFiles));
|
|
3656
3982
|
return;
|
|
3657
3983
|
}
|
|
3658
3984
|
}
|
|
3659
3985
|
EventEmitter.removeHandler(this.#onStartupEvent);
|
|
3660
3986
|
const tstyche = new TSTyche(resolvedConfig, this.#storeService);
|
|
3661
|
-
|
|
3987
|
+
if (resolvedConfig.watch === true) {
|
|
3988
|
+
await tstyche.watch(testFiles, selectService);
|
|
3989
|
+
}
|
|
3990
|
+
else {
|
|
3991
|
+
await tstyche.run(testFiles);
|
|
3992
|
+
}
|
|
3662
3993
|
}
|
|
3663
3994
|
}
|
|
3664
3995
|
|
|
3665
|
-
export { Assertion, CancellationToken, Cli, CollectService, Color, ConfigService, DescribeResult, Diagnostic, DiagnosticCategory, Environment, EventEmitter, Expect, ExpectResult, FileResult,
|
|
3996
|
+
export { Assertion, CancellationToken, Cli, CollectService, Color, ConfigService, DescribeResult, Diagnostic, DiagnosticCategory, Environment, EventEmitter, Expect, ExpectResult, FileResult, InputService, Line, OptionBrand, OptionDefinitionsMap, OptionGroup, OutputService, Path, ProjectResult, ProjectService, Reporter, Result, ResultCount, ResultManager, ResultStatus, ResultTiming, Scribbler, SelectService, StoreService, SummaryReporter, TSTyche, TargetResult, TaskRunner, TestMember, TestMemberBrand, TestMemberFlags, TestResult, TestTree, Text, ThoroughReporter, Version, WatchModeReporter, Watcher, addsPackageStepText, describeNameText, diagnosticText, fileStatusText, fileViewText, formattedText, helpText, summaryText, testNameText, usesCompilerStepText, watchModeUsageText };
|