tstyche 1.0.0-beta.7 → 1.0.0-beta.8
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/CHANGELOG.md +16 -0
- package/build/tstyche.d.ts +80 -47
- package/build/tstyche.js +485 -486
- package/package.json +10 -10
package/build/tstyche.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { readFileSync, existsSync, writeFileSync, rmSync } from 'node:fs';
|
|
2
1
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
+
import { existsSync, writeFileSync, rmSync } from 'node:fs';
|
|
5
6
|
import fs from 'node:fs/promises';
|
|
6
|
-
import
|
|
7
|
-
import { spawn } from 'node:child_process';
|
|
7
|
+
import vm from 'node:vm';
|
|
8
8
|
import https from 'node:https';
|
|
9
|
+
import { spawn } from 'node:child_process';
|
|
9
10
|
|
|
10
11
|
class EventEmitter {
|
|
11
12
|
static #handlers = new Set();
|
|
@@ -22,10 +23,42 @@ class EventEmitter {
|
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
class Path {
|
|
27
|
+
static basename(filePath) {
|
|
28
|
+
return Path.normalizeSlashes(path.basename(filePath));
|
|
29
|
+
}
|
|
30
|
+
static dirname(filePath) {
|
|
31
|
+
return Path.normalizeSlashes(path.dirname(filePath));
|
|
32
|
+
}
|
|
33
|
+
static join(...filePaths) {
|
|
34
|
+
return Path.normalizeSlashes(path.join(...filePaths));
|
|
35
|
+
}
|
|
36
|
+
static normalizeSlashes(filePath) {
|
|
37
|
+
if (path.sep === "/") {
|
|
38
|
+
return filePath;
|
|
39
|
+
}
|
|
40
|
+
return filePath.replaceAll("\\", "/");
|
|
41
|
+
}
|
|
42
|
+
static relative(from, to) {
|
|
43
|
+
let relativePath = path.relative(from, to);
|
|
44
|
+
if (!relativePath.startsWith(".")) {
|
|
45
|
+
relativePath = `./${relativePath}`;
|
|
46
|
+
}
|
|
47
|
+
return Path.normalizeSlashes(relativePath);
|
|
48
|
+
}
|
|
49
|
+
static resolve(...filePaths) {
|
|
50
|
+
return Path.normalizeSlashes(path.resolve(...filePaths));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
25
54
|
class Environment {
|
|
55
|
+
static #isTypeScriptInstalled = Environment.#resolveIsTypeScriptInstalled();
|
|
26
56
|
static #noColor = Environment.#resolveNoColor();
|
|
27
|
-
static #storePath = Environment.#
|
|
57
|
+
static #storePath = Environment.#resolveStorePath();
|
|
28
58
|
static #timeout = Environment.#resolveTimeout();
|
|
59
|
+
static get isTypeScriptInstalled() {
|
|
60
|
+
return Environment.#isTypeScriptInstalled;
|
|
61
|
+
}
|
|
29
62
|
static get noColor() {
|
|
30
63
|
return Environment.#noColor;
|
|
31
64
|
}
|
|
@@ -35,18 +68,21 @@ class Environment {
|
|
|
35
68
|
static get timeout() {
|
|
36
69
|
return Environment.#timeout;
|
|
37
70
|
}
|
|
38
|
-
static #normalizePath(value) {
|
|
39
|
-
if (path.sep === "/") {
|
|
40
|
-
return value;
|
|
41
|
-
}
|
|
42
|
-
return value.replace(/\\/g, "/");
|
|
43
|
-
}
|
|
44
71
|
static #parseBoolean(value) {
|
|
45
72
|
if (value != null) {
|
|
46
73
|
return ["1", "on", "t", "true", "y", "yes"].includes(value.toLowerCase());
|
|
47
74
|
}
|
|
48
75
|
return false;
|
|
49
76
|
}
|
|
77
|
+
static #resolveIsTypeScriptInstalled() {
|
|
78
|
+
try {
|
|
79
|
+
createRequire(import.meta.url).resolve("typescript");
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
50
86
|
static #resolveNoColor() {
|
|
51
87
|
if (process.env["TSTYCHE_NO_COLOR"] != null) {
|
|
52
88
|
return Environment.#parseBoolean(process.env["TSTYCHE_NO_COLOR"]);
|
|
@@ -58,18 +94,18 @@ class Environment {
|
|
|
58
94
|
}
|
|
59
95
|
static #resolveStorePath() {
|
|
60
96
|
if (process.env["TSTYCHE_STORE_PATH"] != null) {
|
|
61
|
-
return
|
|
97
|
+
return Path.resolve(process.env["TSTYCHE_STORE_PATH"]);
|
|
62
98
|
}
|
|
63
99
|
if (process.platform === "darwin") {
|
|
64
|
-
return
|
|
100
|
+
return Path.resolve(os.homedir(), "Library", "TSTyche");
|
|
65
101
|
}
|
|
66
102
|
if (process.env["LocalAppData"] != null) {
|
|
67
|
-
return
|
|
103
|
+
return Path.resolve(process.env["LocalAppData"], "TSTyche");
|
|
68
104
|
}
|
|
69
105
|
if (process.env["XDG_DATA_HOME"] != null) {
|
|
70
|
-
return
|
|
106
|
+
return Path.resolve(process.env["XDG_DATA_HOME"], "TSTyche");
|
|
71
107
|
}
|
|
72
|
-
return
|
|
108
|
+
return Path.resolve(os.homedir(), ".local", "share", "TSTyche");
|
|
73
109
|
}
|
|
74
110
|
static #resolveTimeout() {
|
|
75
111
|
if (process.env["TSTYCHE_TIMEOUT"] != null) {
|
|
@@ -79,6 +115,18 @@ class Environment {
|
|
|
79
115
|
}
|
|
80
116
|
}
|
|
81
117
|
|
|
118
|
+
var Color;
|
|
119
|
+
(function (Color) {
|
|
120
|
+
Color["Reset"] = "0";
|
|
121
|
+
Color["Red"] = "31";
|
|
122
|
+
Color["Green"] = "32";
|
|
123
|
+
Color["Yellow"] = "33";
|
|
124
|
+
Color["Blue"] = "34";
|
|
125
|
+
Color["Magenta"] = "35";
|
|
126
|
+
Color["Cyan"] = "36";
|
|
127
|
+
Color["Gray"] = "90";
|
|
128
|
+
})(Color || (Color = {}));
|
|
129
|
+
|
|
82
130
|
class Scribbler {
|
|
83
131
|
#noColor;
|
|
84
132
|
constructor(options) {
|
|
@@ -93,12 +141,12 @@ class Scribbler {
|
|
|
93
141
|
};
|
|
94
142
|
}
|
|
95
143
|
#escapeSequence(attributes) {
|
|
96
|
-
return ["\
|
|
144
|
+
return ["\u001B[", Array.isArray(attributes) ? attributes.join(";") : attributes, "m"].join("");
|
|
97
145
|
}
|
|
98
146
|
#indentEachLine(lines, level) {
|
|
99
147
|
const indentStep = " ";
|
|
100
148
|
const notEmptyLineRegExp = /^(?!$)/gm;
|
|
101
|
-
return lines.
|
|
149
|
+
return lines.replaceAll(notEmptyLineRegExp, indentStep.repeat(level));
|
|
102
150
|
}
|
|
103
151
|
render(element) {
|
|
104
152
|
if (element != null) {
|
|
@@ -189,6 +237,58 @@ class Line {
|
|
|
189
237
|
}
|
|
190
238
|
}
|
|
191
239
|
|
|
240
|
+
class Logger {
|
|
241
|
+
#noColor;
|
|
242
|
+
#scribbler;
|
|
243
|
+
#stderr;
|
|
244
|
+
#stdout;
|
|
245
|
+
constructor(options) {
|
|
246
|
+
this.#noColor = options?.noColor ?? Environment.noColor;
|
|
247
|
+
this.#stderr = options?.stderr ?? process.stderr;
|
|
248
|
+
this.#stdout = options?.stdout ?? process.stdout;
|
|
249
|
+
this.#scribbler = new Scribbler({ noColors: this.#noColor });
|
|
250
|
+
}
|
|
251
|
+
eraseLastLine() {
|
|
252
|
+
if (!this.isInteractive()) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
this.#stdout.write("\u001B[1A\u001B[0K");
|
|
256
|
+
}
|
|
257
|
+
isInteractive() {
|
|
258
|
+
if ("isTTY" in this.#stdout && typeof this.#stdout.isTTY === "boolean") {
|
|
259
|
+
return this.#stdout.isTTY;
|
|
260
|
+
}
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
#write(stream, body) {
|
|
264
|
+
const elements = Array.isArray(body) ? body : [body];
|
|
265
|
+
for (const element of elements) {
|
|
266
|
+
if (element.$$typeof !== Symbol.for("tstyche:scribbler")) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
stream.write(this.#scribbler.render(element));
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
writeError(body) {
|
|
273
|
+
this.#write(this.#stderr, body);
|
|
274
|
+
}
|
|
275
|
+
writeMessage(body) {
|
|
276
|
+
this.#write(this.#stdout, body);
|
|
277
|
+
}
|
|
278
|
+
writeWarning(body) {
|
|
279
|
+
this.#write(this.#stderr, body);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
class Reporter {
|
|
284
|
+
resolvedConfig;
|
|
285
|
+
logger;
|
|
286
|
+
constructor(resolvedConfig) {
|
|
287
|
+
this.resolvedConfig = resolvedConfig;
|
|
288
|
+
this.logger = new Logger();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
192
292
|
function addsPackageStepText(compilerVersion, installationPath) {
|
|
193
293
|
return (Scribbler.createElement(Line, null,
|
|
194
294
|
Scribbler.createElement(Text, { color: "90" }, "adds"),
|
|
@@ -203,23 +303,6 @@ function describeNameText(name, indent = 0) {
|
|
|
203
303
|
return Scribbler.createElement(Line, { indent: indent + 1 }, name);
|
|
204
304
|
}
|
|
205
305
|
|
|
206
|
-
class RelativePathText {
|
|
207
|
-
props;
|
|
208
|
-
constructor(props) {
|
|
209
|
-
this.props = props;
|
|
210
|
-
}
|
|
211
|
-
render() {
|
|
212
|
-
const relativePath = path.relative("", this.props.to).replace(/\\/g, "/");
|
|
213
|
-
if (relativePath === "") {
|
|
214
|
-
return Scribbler.createElement(Text, null, "./");
|
|
215
|
-
}
|
|
216
|
-
return (Scribbler.createElement(Text, null,
|
|
217
|
-
relativePath.startsWith(".") ? "" : "./",
|
|
218
|
-
relativePath,
|
|
219
|
-
this.props.isDirectory === true ? "/" : ""));
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
306
|
class CodeSpanText {
|
|
224
307
|
props;
|
|
225
308
|
constructor(props) {
|
|
@@ -232,12 +315,14 @@ class CodeSpanText {
|
|
|
232
315
|
const lastLine = Math.min(firstLine + 5, lastLineInFile);
|
|
233
316
|
const lineNumberMaxWidth = `${lastLine + 1}`.length;
|
|
234
317
|
const codeSpan = [];
|
|
235
|
-
for (let
|
|
236
|
-
const lineStart = this.props.file.getPositionOfLineAndCharacter(
|
|
237
|
-
const lineEnd =
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
318
|
+
for (let index = firstLine; index <= lastLine; index++) {
|
|
319
|
+
const lineStart = this.props.file.getPositionOfLineAndCharacter(index, 0);
|
|
320
|
+
const lineEnd = index === lastLineInFile
|
|
321
|
+
? this.props.file.text.length
|
|
322
|
+
: this.props.file.getPositionOfLineAndCharacter(index + 1, 0);
|
|
323
|
+
const lineNumberText = String(index + 1);
|
|
324
|
+
const lineText = this.props.file.text.slice(lineStart, lineEnd).trimEnd().replaceAll("\t", " ");
|
|
325
|
+
if (index === markedLine) {
|
|
241
326
|
codeSpan.push(Scribbler.createElement(Line, null,
|
|
242
327
|
Scribbler.createElement(Text, { color: "31" }, ">"),
|
|
243
328
|
" ",
|
|
@@ -245,8 +330,7 @@ class CodeSpanText {
|
|
|
245
330
|
" ",
|
|
246
331
|
Scribbler.createElement(Text, { color: "90" }, "|"),
|
|
247
332
|
" ",
|
|
248
|
-
lineText)
|
|
249
|
-
codeSpan.push(Scribbler.createElement(Line, null,
|
|
333
|
+
lineText), Scribbler.createElement(Line, null,
|
|
250
334
|
" ".repeat(lineNumberMaxWidth + 3),
|
|
251
335
|
Scribbler.createElement(Text, { color: "90" }, "|"),
|
|
252
336
|
" ".repeat(markedCharacter + 1),
|
|
@@ -269,8 +353,7 @@ class CodeSpanText {
|
|
|
269
353
|
" ".repeat(lineNumberMaxWidth + 5),
|
|
270
354
|
Scribbler.createElement(Text, { color: "90" }, "at"),
|
|
271
355
|
" ",
|
|
272
|
-
Scribbler.createElement(Text, { color: "36" },
|
|
273
|
-
Scribbler.createElement(RelativePathText, { to: this.props.file.fileName })),
|
|
356
|
+
Scribbler.createElement(Text, { color: "36" }, Path.relative("", this.props.file.fileName)),
|
|
274
357
|
Scribbler.createElement(Text, { color: "90" },
|
|
275
358
|
":",
|
|
276
359
|
String(markedLine + 1),
|
|
@@ -331,10 +414,13 @@ class FileNameText {
|
|
|
331
414
|
this.props = props;
|
|
332
415
|
}
|
|
333
416
|
render() {
|
|
417
|
+
const relativePath = Path.relative("", this.props.filePath);
|
|
418
|
+
const lastPathSeparator = relativePath.lastIndexOf("/");
|
|
419
|
+
const directoryNameText = relativePath.slice(0, lastPathSeparator + 1);
|
|
420
|
+
const fileNameText = relativePath.slice(lastPathSeparator + 1);
|
|
334
421
|
return (Scribbler.createElement(Text, null,
|
|
335
|
-
Scribbler.createElement(Text, { color: "90" },
|
|
336
|
-
|
|
337
|
-
path.basename(this.props.filePath)));
|
|
422
|
+
Scribbler.createElement(Text, { color: "90" }, directoryNameText),
|
|
423
|
+
fileNameText));
|
|
338
424
|
}
|
|
339
425
|
}
|
|
340
426
|
function fileStatusText(status, testFile) {
|
|
@@ -395,8 +481,8 @@ function formattedText(input) {
|
|
|
395
481
|
|
|
396
482
|
const usageExamples = [
|
|
397
483
|
["tstyche", "Run all tests."],
|
|
398
|
-
["tstyche path/to/first.test.ts
|
|
399
|
-
["tstyche --target 4.7,
|
|
484
|
+
["tstyche path/to/first.test.ts", "Only run the test files with matching path."],
|
|
485
|
+
["tstyche --target 4.7,5.3.2,current", "Test on all specified versions of TypeScript."],
|
|
400
486
|
];
|
|
401
487
|
class HintText {
|
|
402
488
|
props;
|
|
@@ -426,7 +512,7 @@ class CommandText {
|
|
|
426
512
|
this.props = props;
|
|
427
513
|
}
|
|
428
514
|
render() {
|
|
429
|
-
let hint
|
|
515
|
+
let hint;
|
|
430
516
|
if (this.props.hint != null) {
|
|
431
517
|
hint = Scribbler.createElement(HintText, null, this.props.hint);
|
|
432
518
|
}
|
|
@@ -444,7 +530,7 @@ class OptionDescriptionText {
|
|
|
444
530
|
return Scribbler.createElement(Line, { indent: 1 }, this.props.text);
|
|
445
531
|
}
|
|
446
532
|
}
|
|
447
|
-
class
|
|
533
|
+
class CommandLineUsageText {
|
|
448
534
|
render() {
|
|
449
535
|
const usageText = usageExamples.map(([commandText, descriptionText]) => (Scribbler.createElement(Text, null,
|
|
450
536
|
Scribbler.createElement(CommandText, { text: commandText }),
|
|
@@ -453,7 +539,7 @@ class CliUsageText {
|
|
|
453
539
|
return Scribbler.createElement(Text, null, usageText);
|
|
454
540
|
}
|
|
455
541
|
}
|
|
456
|
-
class
|
|
542
|
+
class CommandLineOptionNameText {
|
|
457
543
|
props;
|
|
458
544
|
constructor(props) {
|
|
459
545
|
this.props = props;
|
|
@@ -464,7 +550,7 @@ class OptionNameText {
|
|
|
464
550
|
this.props.text);
|
|
465
551
|
}
|
|
466
552
|
}
|
|
467
|
-
class
|
|
553
|
+
class CommandLineOptionHintText {
|
|
468
554
|
props;
|
|
469
555
|
constructor(props) {
|
|
470
556
|
this.props = props;
|
|
@@ -480,19 +566,25 @@ class OptionHintText {
|
|
|
480
566
|
return Scribbler.createElement(Text, null, this.props.definition.brand);
|
|
481
567
|
}
|
|
482
568
|
}
|
|
483
|
-
class
|
|
569
|
+
class CommandLineOptionsText {
|
|
484
570
|
props;
|
|
485
571
|
constructor(props) {
|
|
486
572
|
this.props = props;
|
|
487
573
|
}
|
|
488
574
|
render() {
|
|
489
|
-
const definitions =
|
|
490
|
-
const optionsText = definitions.map((definition) =>
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
575
|
+
const definitions = [...this.props.optionDefinitions.values()];
|
|
576
|
+
const optionsText = definitions.map((definition) => {
|
|
577
|
+
let hint;
|
|
578
|
+
if (definition.brand !== "true") {
|
|
579
|
+
hint = Scribbler.createElement(CommandLineOptionHintText, { definition: definition });
|
|
580
|
+
}
|
|
581
|
+
return (Scribbler.createElement(Text, null,
|
|
582
|
+
Scribbler.createElement(CommandText, { text: Scribbler.createElement(CommandLineOptionNameText, { text: definition.name }), hint: hint }),
|
|
583
|
+
Scribbler.createElement(OptionDescriptionText, { text: definition.description }),
|
|
584
|
+
Scribbler.createElement(Line, null)));
|
|
585
|
+
});
|
|
494
586
|
return (Scribbler.createElement(Text, null,
|
|
495
|
-
Scribbler.createElement(Line, null, "
|
|
587
|
+
Scribbler.createElement(Line, null, "Command Line Options"),
|
|
496
588
|
Scribbler.createElement(Line, null),
|
|
497
589
|
optionsText));
|
|
498
590
|
}
|
|
@@ -506,9 +598,9 @@ function helpText(optionDefinitions, tstycheVersion) {
|
|
|
506
598
|
return (Scribbler.createElement(Text, null,
|
|
507
599
|
Scribbler.createElement(HelpHeaderText, { tstycheVersion: tstycheVersion }),
|
|
508
600
|
Scribbler.createElement(Line, null),
|
|
509
|
-
Scribbler.createElement(
|
|
601
|
+
Scribbler.createElement(CommandLineUsageText, null),
|
|
510
602
|
Scribbler.createElement(Line, null),
|
|
511
|
-
Scribbler.createElement(
|
|
603
|
+
Scribbler.createElement(CommandLineOptionsText, { optionDefinitions: optionDefinitions }),
|
|
512
604
|
Scribbler.createElement(Line, null),
|
|
513
605
|
Scribbler.createElement(HelpFooterText, null),
|
|
514
606
|
Scribbler.createElement(Line, null)));
|
|
@@ -686,11 +778,14 @@ class ProjectNameText {
|
|
|
686
778
|
render() {
|
|
687
779
|
return (Scribbler.createElement(Text, { color: "90" },
|
|
688
780
|
" with ",
|
|
689
|
-
|
|
781
|
+
Path.relative("", this.props.filePath)));
|
|
690
782
|
}
|
|
691
783
|
}
|
|
692
784
|
function usesCompilerStepText(compilerVersion, tsconfigFilePath, options) {
|
|
693
|
-
|
|
785
|
+
let projectPathText;
|
|
786
|
+
if (tsconfigFilePath != null) {
|
|
787
|
+
projectPathText = Scribbler.createElement(ProjectNameText, { filePath: tsconfigFilePath });
|
|
788
|
+
}
|
|
694
789
|
return (Scribbler.createElement(Text, null,
|
|
695
790
|
options?.prependEmptyLine === true ? Scribbler.createElement(Line, null) : undefined,
|
|
696
791
|
Scribbler.createElement(Line, null,
|
|
@@ -701,58 +796,6 @@ function usesCompilerStepText(compilerVersion, tsconfigFilePath, options) {
|
|
|
701
796
|
Scribbler.createElement(Line, null)));
|
|
702
797
|
}
|
|
703
798
|
|
|
704
|
-
class Logger {
|
|
705
|
-
#noColor;
|
|
706
|
-
#scribbler;
|
|
707
|
-
#stderr;
|
|
708
|
-
#stdout;
|
|
709
|
-
constructor(options) {
|
|
710
|
-
this.#noColor = options?.noColor ?? Environment.noColor;
|
|
711
|
-
this.#stderr = options?.stderr ?? process.stderr;
|
|
712
|
-
this.#stdout = options?.stdout ?? process.stdout;
|
|
713
|
-
this.#scribbler = new Scribbler({ noColors: this.#noColor });
|
|
714
|
-
}
|
|
715
|
-
eraseLastLine() {
|
|
716
|
-
if (!this.isInteractive()) {
|
|
717
|
-
return;
|
|
718
|
-
}
|
|
719
|
-
this.#stdout.write("\x1b[1A\x1b[0K");
|
|
720
|
-
}
|
|
721
|
-
isInteractive() {
|
|
722
|
-
if ("isTTY" in this.#stdout && typeof this.#stdout.isTTY === "boolean") {
|
|
723
|
-
return this.#stdout.isTTY;
|
|
724
|
-
}
|
|
725
|
-
return false;
|
|
726
|
-
}
|
|
727
|
-
#write(stream, body) {
|
|
728
|
-
const elements = Array.isArray(body) ? body : [body];
|
|
729
|
-
for (const element of elements) {
|
|
730
|
-
if (element.$$typeof !== Symbol.for("tstyche:scribbler")) {
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
|
-
stream.write(this.#scribbler.render(element));
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
writeError(body) {
|
|
737
|
-
this.#write(this.#stderr, body);
|
|
738
|
-
}
|
|
739
|
-
writeMessage(body) {
|
|
740
|
-
this.#write(this.#stdout, body);
|
|
741
|
-
}
|
|
742
|
-
writeWarning(body) {
|
|
743
|
-
this.#write(this.#stderr, body);
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
class Reporter {
|
|
748
|
-
resolvedConfig;
|
|
749
|
-
logger;
|
|
750
|
-
constructor(resolvedConfig) {
|
|
751
|
-
this.resolvedConfig = resolvedConfig;
|
|
752
|
-
this.logger = new Logger();
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
|
|
756
799
|
class SummaryReporter extends Reporter {
|
|
757
800
|
handleEvent([eventName, payload]) {
|
|
758
801
|
switch (eventName) {
|
|
@@ -1331,51 +1374,39 @@ class TestMember {
|
|
|
1331
1374
|
parent;
|
|
1332
1375
|
flags;
|
|
1333
1376
|
compiler;
|
|
1334
|
-
diagnostics;
|
|
1377
|
+
diagnostics = new Set();
|
|
1335
1378
|
members = [];
|
|
1336
|
-
name;
|
|
1379
|
+
name = "";
|
|
1337
1380
|
constructor(brand, node, parent, flags) {
|
|
1338
1381
|
this.brand = brand;
|
|
1339
1382
|
this.node = node;
|
|
1340
1383
|
this.parent = parent;
|
|
1341
1384
|
this.flags = flags;
|
|
1342
1385
|
this.compiler = parent.compiler;
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
}
|
|
1346
|
-
get ancestorNames() {
|
|
1347
|
-
const ancestorNames = [];
|
|
1348
|
-
let ancestor = this.parent;
|
|
1349
|
-
while ("name" in ancestor) {
|
|
1350
|
-
ancestorNames.unshift(ancestor.name);
|
|
1351
|
-
ancestor = ancestor.parent;
|
|
1386
|
+
if (node.arguments[0] != null && this.compiler.isStringLiteralLike(node.arguments[0])) {
|
|
1387
|
+
this.name = node.arguments[0].text;
|
|
1352
1388
|
}
|
|
1353
|
-
return ancestorNames;
|
|
1354
|
-
}
|
|
1355
|
-
#mapDiagnostics(node, parent) {
|
|
1356
|
-
const mapped = [];
|
|
1357
|
-
const unmapped = [];
|
|
1358
1389
|
if (node.arguments[1] != null &&
|
|
1359
1390
|
parent.compiler.isFunctionLike(node.arguments[1]) &&
|
|
1360
1391
|
parent.compiler.isBlock(node.arguments[1].body)) {
|
|
1361
1392
|
const blockStart = node.arguments[1].body.getStart();
|
|
1362
1393
|
const blockEnd = node.arguments[1].body.getEnd();
|
|
1363
|
-
parent.diagnostics
|
|
1394
|
+
for (const diagnostic of parent.diagnostics) {
|
|
1364
1395
|
if (diagnostic.start != null && diagnostic.start >= blockStart && diagnostic.start <= blockEnd) {
|
|
1365
|
-
|
|
1396
|
+
this.diagnostics.add(diagnostic);
|
|
1397
|
+
parent.diagnostics.delete(diagnostic);
|
|
1366
1398
|
}
|
|
1367
|
-
|
|
1368
|
-
unmapped.push(diagnostic);
|
|
1369
|
-
}
|
|
1370
|
-
});
|
|
1371
|
-
parent.diagnostics = unmapped;
|
|
1399
|
+
}
|
|
1372
1400
|
}
|
|
1373
|
-
return mapped;
|
|
1374
1401
|
}
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1402
|
+
get ancestorNames() {
|
|
1403
|
+
const ancestorNames = [];
|
|
1404
|
+
let ancestor = this.parent;
|
|
1405
|
+
while ("name" in ancestor) {
|
|
1406
|
+
ancestorNames.unshift(ancestor.name);
|
|
1407
|
+
ancestor = ancestor.parent;
|
|
1408
|
+
}
|
|
1409
|
+
return ancestorNames;
|
|
1379
1410
|
}
|
|
1380
1411
|
validate() {
|
|
1381
1412
|
const diagnostics = [];
|
|
@@ -1415,14 +1446,24 @@ class Assertion extends TestMember {
|
|
|
1415
1446
|
modifierNode;
|
|
1416
1447
|
notNode;
|
|
1417
1448
|
isNot;
|
|
1418
|
-
name = "";
|
|
1419
1449
|
constructor(brand, node, parent, flags, matcherNode, modifierNode, notNode) {
|
|
1420
1450
|
super(brand, node, parent, flags);
|
|
1421
1451
|
this.matcherNode = matcherNode;
|
|
1422
1452
|
this.modifierNode = modifierNode;
|
|
1423
1453
|
this.notNode = notNode;
|
|
1424
|
-
this.diagnostics = this.#mapDiagnostics();
|
|
1425
1454
|
this.isNot = notNode ? true : false;
|
|
1455
|
+
const argStart = this.source[0]?.getStart();
|
|
1456
|
+
const argEnd = this.source[0]?.getEnd();
|
|
1457
|
+
for (const diagnostic of parent.diagnostics) {
|
|
1458
|
+
if (diagnostic.start != null &&
|
|
1459
|
+
argStart != null &&
|
|
1460
|
+
argEnd != null &&
|
|
1461
|
+
diagnostic.start >= argStart &&
|
|
1462
|
+
diagnostic.start <= argEnd) {
|
|
1463
|
+
this.diagnostics.add(diagnostic);
|
|
1464
|
+
parent.diagnostics.delete(diagnostic);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1426
1467
|
}
|
|
1427
1468
|
get ancestorNames() {
|
|
1428
1469
|
const ancestorNames = [];
|
|
@@ -1449,30 +1490,6 @@ class Assertion extends TestMember {
|
|
|
1449
1490
|
}
|
|
1450
1491
|
return this.matcherNode.arguments;
|
|
1451
1492
|
}
|
|
1452
|
-
#mapDiagnostics() {
|
|
1453
|
-
const mapped = [];
|
|
1454
|
-
const unmapped = [];
|
|
1455
|
-
let argStart;
|
|
1456
|
-
let argEnd;
|
|
1457
|
-
if (this.node.typeArguments?.[0]) {
|
|
1458
|
-
argStart = this.node.typeArguments[0].getStart();
|
|
1459
|
-
argEnd = this.node.typeArguments[0].getEnd();
|
|
1460
|
-
}
|
|
1461
|
-
else if (this.node.arguments[0]) {
|
|
1462
|
-
argStart = this.node.arguments[0].getStart();
|
|
1463
|
-
argEnd = this.node.arguments[0].getEnd();
|
|
1464
|
-
}
|
|
1465
|
-
this.parent.diagnostics.forEach((diagnostic) => {
|
|
1466
|
-
if (diagnostic.start != null && diagnostic.start >= argStart && diagnostic.start <= argEnd) {
|
|
1467
|
-
mapped.push(diagnostic);
|
|
1468
|
-
}
|
|
1469
|
-
else {
|
|
1470
|
-
unmapped.push(diagnostic);
|
|
1471
|
-
}
|
|
1472
|
-
});
|
|
1473
|
-
this.parent.diagnostics = unmapped;
|
|
1474
|
-
return mapped;
|
|
1475
|
-
}
|
|
1476
1493
|
}
|
|
1477
1494
|
|
|
1478
1495
|
class IdentifierLookup {
|
|
@@ -1657,7 +1674,7 @@ class CollectService {
|
|
|
1657
1674
|
});
|
|
1658
1675
|
}
|
|
1659
1676
|
createTestTree(sourceFile, semanticDiagnostics = []) {
|
|
1660
|
-
const testTree = new TestTree(this.compiler, semanticDiagnostics, sourceFile);
|
|
1677
|
+
const testTree = new TestTree(this.compiler, new Set(semanticDiagnostics), sourceFile);
|
|
1661
1678
|
this.#collectTestMembers(sourceFile, new IdentifierLookup(this.compiler), testTree);
|
|
1662
1679
|
return testTree;
|
|
1663
1680
|
}
|
|
@@ -2038,7 +2055,7 @@ class Expect {
|
|
|
2038
2055
|
this.#onTargetArgumentsMustBeStringOrNumberLiteralTypes(assertion.target, expectResult);
|
|
2039
2056
|
return;
|
|
2040
2057
|
}
|
|
2041
|
-
return this.toRaiseError.match({ diagnostics: assertion.diagnostics, node: assertion.source[0] }, targetTypes, assertion.isNot);
|
|
2058
|
+
return this.toRaiseError.match({ diagnostics: [...assertion.diagnostics], node: assertion.source[0] }, targetTypes, assertion.isNot);
|
|
2042
2059
|
}
|
|
2043
2060
|
default:
|
|
2044
2061
|
this.#onNotSupportedMatcherName(assertion, expectResult);
|
|
@@ -2290,11 +2307,11 @@ class TestTreeWorker {
|
|
|
2290
2307
|
EventEmitter.dispatch(["expect:skip", { result: expectResult }]);
|
|
2291
2308
|
return;
|
|
2292
2309
|
}
|
|
2293
|
-
if (assertion.diagnostics.
|
|
2310
|
+
if (assertion.diagnostics.size > 0 && assertion.matcherName.getText() !== "toRaiseError") {
|
|
2294
2311
|
EventEmitter.dispatch([
|
|
2295
2312
|
"expect:error",
|
|
2296
2313
|
{
|
|
2297
|
-
diagnostics: Diagnostic.fromDiagnostics(assertion.diagnostics, this.compiler),
|
|
2314
|
+
diagnostics: Diagnostic.fromDiagnostics([...assertion.diagnostics], this.compiler),
|
|
2298
2315
|
result: expectResult,
|
|
2299
2316
|
},
|
|
2300
2317
|
]);
|
|
@@ -2348,11 +2365,11 @@ class TestTreeWorker {
|
|
|
2348
2365
|
EventEmitter.dispatch(["describe:start", { result: describeResult }]);
|
|
2349
2366
|
runMode = this.#resolveRunMode(runMode, describe);
|
|
2350
2367
|
if (!(runMode & 4 || (this.#hasOnly && !(runMode & 2)) || runMode & 8) &&
|
|
2351
|
-
describe.diagnostics.
|
|
2368
|
+
describe.diagnostics.size > 0) {
|
|
2352
2369
|
EventEmitter.dispatch([
|
|
2353
2370
|
"file:error",
|
|
2354
2371
|
{
|
|
2355
|
-
diagnostics: Diagnostic.fromDiagnostics(describe.diagnostics, this.compiler),
|
|
2372
|
+
diagnostics: Diagnostic.fromDiagnostics([...describe.diagnostics], this.compiler),
|
|
2356
2373
|
result: this.#fileResult,
|
|
2357
2374
|
},
|
|
2358
2375
|
]);
|
|
@@ -2370,11 +2387,11 @@ class TestTreeWorker {
|
|
|
2370
2387
|
EventEmitter.dispatch(["test:todo", { result: testResult }]);
|
|
2371
2388
|
return;
|
|
2372
2389
|
}
|
|
2373
|
-
if (!(runMode & 4 || (this.#hasOnly && !(runMode & 2))) && test.diagnostics.
|
|
2390
|
+
if (!(runMode & 4 || (this.#hasOnly && !(runMode & 2))) && test.diagnostics.size > 0) {
|
|
2374
2391
|
EventEmitter.dispatch([
|
|
2375
2392
|
"test:error",
|
|
2376
2393
|
{
|
|
2377
|
-
diagnostics: Diagnostic.fromDiagnostics(test.diagnostics, this.compiler),
|
|
2394
|
+
diagnostics: Diagnostic.fromDiagnostics([...test.diagnostics], this.compiler),
|
|
2378
2395
|
result: testResult,
|
|
2379
2396
|
},
|
|
2380
2397
|
]);
|
|
@@ -2444,11 +2461,11 @@ class TestFileRunner {
|
|
|
2444
2461
|
return;
|
|
2445
2462
|
}
|
|
2446
2463
|
const testTree = this.#collectService.createTestTree(sourceFile, semanticDiagnostics);
|
|
2447
|
-
if (testTree.diagnostics.
|
|
2464
|
+
if (testTree.diagnostics.size > 0) {
|
|
2448
2465
|
EventEmitter.dispatch([
|
|
2449
2466
|
"file:error",
|
|
2450
2467
|
{
|
|
2451
|
-
diagnostics: Diagnostic.fromDiagnostics(testTree.diagnostics, this.compiler),
|
|
2468
|
+
diagnostics: Diagnostic.fromDiagnostics([...testTree.diagnostics], this.compiler),
|
|
2452
2469
|
result: fileResult,
|
|
2453
2470
|
},
|
|
2454
2471
|
]);
|
|
@@ -2508,17 +2525,13 @@ class TSTyche {
|
|
|
2508
2525
|
#abortController = new AbortController();
|
|
2509
2526
|
#storeService;
|
|
2510
2527
|
#taskRunner;
|
|
2528
|
+
static version = "1.0.0-beta.8";
|
|
2511
2529
|
constructor(resolvedConfig, storeService) {
|
|
2512
2530
|
this.resolvedConfig = resolvedConfig;
|
|
2513
2531
|
this.#storeService = storeService;
|
|
2514
2532
|
this.#taskRunner = new TaskRunner(this.resolvedConfig, this.#storeService);
|
|
2515
2533
|
this.#addEventHandlers();
|
|
2516
2534
|
}
|
|
2517
|
-
static get version() {
|
|
2518
|
-
const packageConfig = readFileSync(new URL("../package.json", import.meta.url), { encoding: "utf8" });
|
|
2519
|
-
const { version } = JSON.parse(packageConfig);
|
|
2520
|
-
return version;
|
|
2521
|
-
}
|
|
2522
2535
|
#addEventHandlers() {
|
|
2523
2536
|
EventEmitter.addHandler(([eventName, payload]) => {
|
|
2524
2537
|
if (eventName.includes("error") || eventName.includes("fail")) {
|
|
@@ -2576,19 +2589,19 @@ class OptionDefinitionsMap {
|
|
|
2576
2589
|
name: "failFast",
|
|
2577
2590
|
},
|
|
2578
2591
|
{
|
|
2579
|
-
brand: "
|
|
2580
|
-
description: "Print the list of
|
|
2592
|
+
brand: "true",
|
|
2593
|
+
description: "Print the list of command line options with brief descriptions and exit.",
|
|
2581
2594
|
group: 2,
|
|
2582
2595
|
name: "help",
|
|
2583
2596
|
},
|
|
2584
2597
|
{
|
|
2585
|
-
brand: "
|
|
2598
|
+
brand: "true",
|
|
2586
2599
|
description: "Install specified versions of the 'typescript' package and exit.",
|
|
2587
2600
|
group: 2,
|
|
2588
2601
|
name: "install",
|
|
2589
2602
|
},
|
|
2590
2603
|
{
|
|
2591
|
-
brand: "
|
|
2604
|
+
brand: "true",
|
|
2592
2605
|
description: "Print the list of the selected test files and exit.",
|
|
2593
2606
|
group: 2,
|
|
2594
2607
|
name: "listFiles",
|
|
@@ -2600,7 +2613,7 @@ class OptionDefinitionsMap {
|
|
|
2600
2613
|
name: "only",
|
|
2601
2614
|
},
|
|
2602
2615
|
{
|
|
2603
|
-
brand: "
|
|
2616
|
+
brand: "true",
|
|
2604
2617
|
description: "Remove all installed versions of the 'typescript' package and exit.",
|
|
2605
2618
|
group: 2,
|
|
2606
2619
|
name: "prune",
|
|
@@ -2612,7 +2625,7 @@ class OptionDefinitionsMap {
|
|
|
2612
2625
|
name: "rootPath",
|
|
2613
2626
|
},
|
|
2614
2627
|
{
|
|
2615
|
-
brand: "
|
|
2628
|
+
brand: "true",
|
|
2616
2629
|
description: "Print the resolved configuration and exit.",
|
|
2617
2630
|
group: 2,
|
|
2618
2631
|
name: "showConfig",
|
|
@@ -2645,13 +2658,13 @@ class OptionDefinitionsMap {
|
|
|
2645
2658
|
name: "testFileMatch",
|
|
2646
2659
|
},
|
|
2647
2660
|
{
|
|
2648
|
-
brand: "
|
|
2661
|
+
brand: "true",
|
|
2649
2662
|
description: "Fetch the 'typescript' package metadata from the registry and exit.",
|
|
2650
2663
|
group: 2,
|
|
2651
2664
|
name: "update",
|
|
2652
2665
|
},
|
|
2653
2666
|
{
|
|
2654
|
-
brand: "
|
|
2667
|
+
brand: "true",
|
|
2655
2668
|
description: "Print the version number and exit.",
|
|
2656
2669
|
group: 2,
|
|
2657
2670
|
name: "version",
|
|
@@ -2718,15 +2731,15 @@ class OptionUsageText {
|
|
|
2718
2731
|
this.#optionDiagnosticText = new OptionDiagnosticText(this.#optionGroup);
|
|
2719
2732
|
this.#storeService = storeService;
|
|
2720
2733
|
}
|
|
2721
|
-
get(optionName, optionBrand) {
|
|
2734
|
+
async get(optionName, optionBrand) {
|
|
2722
2735
|
const usageText = [];
|
|
2723
2736
|
switch (optionName) {
|
|
2724
2737
|
case "target": {
|
|
2725
|
-
const
|
|
2738
|
+
const supportedTags = await this.#storeService.getSupportedTags();
|
|
2726
2739
|
const supportedTagsText = `Supported tags: ${["'", supportedTags.join("', '"), "'"].join("")}.`;
|
|
2727
2740
|
switch (this.#optionGroup) {
|
|
2728
2741
|
case 2:
|
|
2729
|
-
usageText.push("Argument for the '--target' option must be a single tag or a comma separated list.", "Usage examples: '--target 4.9', '--target 5.0.4', '--target 4.7,
|
|
2742
|
+
usageText.push("Argument for the '--target' option must be a single tag or a comma separated list.", "Usage examples: '--target 4.9', '--target 5.0.4', '--target 4.7,5.3.2,current'.", supportedTagsText);
|
|
2730
2743
|
break;
|
|
2731
2744
|
case 4:
|
|
2732
2745
|
usageText.push("Item of the 'target' list must be a supported version tag.", supportedTagsText);
|
|
@@ -2754,7 +2767,7 @@ class OptionValidator {
|
|
|
2754
2767
|
this.#optionDiagnosticText = new OptionDiagnosticText(this.#optionGroup);
|
|
2755
2768
|
this.#optionUsageText = new OptionUsageText(this.#optionGroup, this.#storeService);
|
|
2756
2769
|
}
|
|
2757
|
-
check(optionName, optionValue, optionBrand, origin) {
|
|
2770
|
+
async check(optionName, optionValue, optionBrand, origin) {
|
|
2758
2771
|
switch (optionName) {
|
|
2759
2772
|
case "config":
|
|
2760
2773
|
case "rootPath":
|
|
@@ -2764,13 +2777,11 @@ class OptionValidator {
|
|
|
2764
2777
|
}
|
|
2765
2778
|
break;
|
|
2766
2779
|
case "target":
|
|
2767
|
-
{
|
|
2768
|
-
|
|
2769
|
-
this.#
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
], origin));
|
|
2773
|
-
}
|
|
2780
|
+
if (!(await this.#storeService.validateTag(optionValue))) {
|
|
2781
|
+
this.#onDiagnostic(Diagnostic.error([
|
|
2782
|
+
this.#optionDiagnosticText.versionIsNotSupported(optionValue),
|
|
2783
|
+
...(await this.#optionUsageText.get(optionName, optionBrand)),
|
|
2784
|
+
], origin));
|
|
2774
2785
|
}
|
|
2775
2786
|
break;
|
|
2776
2787
|
}
|
|
@@ -2796,29 +2807,23 @@ class CommandLineOptionsWorker {
|
|
|
2796
2807
|
this.#optionUsageText = new OptionUsageText(2, this.#storeService);
|
|
2797
2808
|
this.#optionValidator = new OptionValidator(2, this.#storeService, this.#onDiagnostic);
|
|
2798
2809
|
}
|
|
2799
|
-
#
|
|
2800
|
-
if (path.sep === "/") {
|
|
2801
|
-
return filePath;
|
|
2802
|
-
}
|
|
2803
|
-
return filePath.replace(/\\/g, "/");
|
|
2804
|
-
}
|
|
2805
|
-
#onExpectsArgumentDiagnostic(optionDefinition) {
|
|
2810
|
+
async #onExpectsArgumentDiagnostic(optionDefinition) {
|
|
2806
2811
|
const text = [
|
|
2807
2812
|
this.#optionDiagnosticText.expectsArgument(optionDefinition.name),
|
|
2808
|
-
...this.#optionUsageText.get(optionDefinition.name, optionDefinition.brand),
|
|
2813
|
+
...(await this.#optionUsageText.get(optionDefinition.name, optionDefinition.brand)),
|
|
2809
2814
|
];
|
|
2810
2815
|
this.#onDiagnostic(Diagnostic.error(text));
|
|
2811
2816
|
}
|
|
2812
|
-
parse(commandLineArgs) {
|
|
2817
|
+
async parse(commandLineArgs) {
|
|
2813
2818
|
let index = 0;
|
|
2814
2819
|
let arg = commandLineArgs[index];
|
|
2815
|
-
while (arg
|
|
2820
|
+
while (arg != null) {
|
|
2816
2821
|
index++;
|
|
2817
2822
|
if (arg.startsWith("--")) {
|
|
2818
2823
|
const optionName = arg.slice(2);
|
|
2819
2824
|
const optionDefinition = this.#commandLineOptionDefinitions.get(optionName);
|
|
2820
2825
|
if (optionDefinition) {
|
|
2821
|
-
index = this.#parseOptionValue(commandLineArgs, index, optionDefinition);
|
|
2826
|
+
index = await this.#parseOptionValue(commandLineArgs, index, optionDefinition);
|
|
2822
2827
|
}
|
|
2823
2828
|
else {
|
|
2824
2829
|
this.#onDiagnostic(Diagnostic.error(this.#optionDiagnosticText.unknownOption(arg)));
|
|
@@ -2828,14 +2833,17 @@ class CommandLineOptionsWorker {
|
|
|
2828
2833
|
this.#onDiagnostic(Diagnostic.error(this.#optionDiagnosticText.unknownOption(arg)));
|
|
2829
2834
|
}
|
|
2830
2835
|
else {
|
|
2831
|
-
this.#pathMatch.push(arg);
|
|
2836
|
+
this.#pathMatch.push(Path.normalizeSlashes(arg));
|
|
2832
2837
|
}
|
|
2833
2838
|
arg = commandLineArgs[index];
|
|
2834
2839
|
}
|
|
2835
2840
|
}
|
|
2836
|
-
#parseOptionValue(commandLineArgs, index, optionDefinition) {
|
|
2841
|
+
async #parseOptionValue(commandLineArgs, index, optionDefinition) {
|
|
2837
2842
|
let optionValue = this.#resolveOptionValue(commandLineArgs[index]);
|
|
2838
2843
|
switch (optionDefinition.brand) {
|
|
2844
|
+
case "true":
|
|
2845
|
+
this.#commandLineOptions[optionDefinition.name] = true;
|
|
2846
|
+
break;
|
|
2839
2847
|
case "boolean":
|
|
2840
2848
|
this.#commandLineOptions[optionDefinition.name] = optionValue !== "false";
|
|
2841
2849
|
if (optionValue === "false" || optionValue === "true") {
|
|
@@ -2849,33 +2857,25 @@ class CommandLineOptionsWorker {
|
|
|
2849
2857
|
.map((value) => value.trim())
|
|
2850
2858
|
.filter((value) => value !== "");
|
|
2851
2859
|
for (const optionValue of optionValues) {
|
|
2852
|
-
this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand);
|
|
2860
|
+
await this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand);
|
|
2853
2861
|
}
|
|
2854
2862
|
this.#commandLineOptions[optionDefinition.name] = optionValues;
|
|
2855
2863
|
index++;
|
|
2856
2864
|
break;
|
|
2857
2865
|
}
|
|
2858
|
-
this.#onExpectsArgumentDiagnostic(optionDefinition);
|
|
2859
|
-
break;
|
|
2860
|
-
case "number":
|
|
2861
|
-
if (optionValue != null) {
|
|
2862
|
-
this.#commandLineOptions[optionDefinition.name] = Number(optionValue);
|
|
2863
|
-
index++;
|
|
2864
|
-
break;
|
|
2865
|
-
}
|
|
2866
|
-
this.#onExpectsArgumentDiagnostic(optionDefinition);
|
|
2866
|
+
await this.#onExpectsArgumentDiagnostic(optionDefinition);
|
|
2867
2867
|
break;
|
|
2868
2868
|
case "string":
|
|
2869
2869
|
if (optionValue != null) {
|
|
2870
2870
|
if (optionDefinition.name === "config") {
|
|
2871
|
-
optionValue =
|
|
2871
|
+
optionValue = Path.resolve(optionValue);
|
|
2872
2872
|
}
|
|
2873
|
-
this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand);
|
|
2873
|
+
await this.#optionValidator.check(optionDefinition.name, optionValue, optionDefinition.brand);
|
|
2874
2874
|
this.#commandLineOptions[optionDefinition.name] = optionValue;
|
|
2875
2875
|
index++;
|
|
2876
2876
|
break;
|
|
2877
2877
|
}
|
|
2878
|
-
this.#onExpectsArgumentDiagnostic(optionDefinition);
|
|
2878
|
+
await this.#onExpectsArgumentDiagnostic(optionDefinition);
|
|
2879
2879
|
break;
|
|
2880
2880
|
}
|
|
2881
2881
|
return index;
|
|
@@ -2911,12 +2911,6 @@ class ConfigFileOptionsWorker {
|
|
|
2911
2911
|
return (node.kind === this.compiler.SyntaxKind.StringLiteral &&
|
|
2912
2912
|
sourceFile.text.slice(this.#skipTrivia(node.pos, sourceFile), node.end).startsWith('"'));
|
|
2913
2913
|
}
|
|
2914
|
-
#normalizePath(filePath) {
|
|
2915
|
-
if (path.sep === "/") {
|
|
2916
|
-
return filePath;
|
|
2917
|
-
}
|
|
2918
|
-
return filePath.replace(/\\/g, "/");
|
|
2919
|
-
}
|
|
2920
2914
|
async parse(sourceText) {
|
|
2921
2915
|
if (sourceText === "") {
|
|
2922
2916
|
return;
|
|
@@ -2995,22 +2989,17 @@ class ConfigFileOptionsWorker {
|
|
|
2995
2989
|
if (optionDefinition.brand === "string") {
|
|
2996
2990
|
let value = valueExpression.text;
|
|
2997
2991
|
if (optionDefinition.name === "rootPath") {
|
|
2998
|
-
value =
|
|
2992
|
+
value = Path.resolve(Path.dirname(this.#configFilePath), value);
|
|
2999
2993
|
}
|
|
3000
2994
|
const origin = {
|
|
3001
2995
|
end: valueExpression.end,
|
|
3002
2996
|
file: sourceFile,
|
|
3003
2997
|
start: this.#skipTrivia(valueExpression.pos, sourceFile),
|
|
3004
2998
|
};
|
|
3005
|
-
this.#optionValidator.check(optionDefinition.name, value, optionDefinition.brand, origin);
|
|
2999
|
+
await this.#optionValidator.check(optionDefinition.name, value, optionDefinition.brand, origin);
|
|
3006
3000
|
return value;
|
|
3007
3001
|
}
|
|
3008
3002
|
break;
|
|
3009
|
-
case this.compiler.SyntaxKind.NumericLiteral:
|
|
3010
|
-
if (optionDefinition.brand === "number") {
|
|
3011
|
-
return Number(valueExpression.text);
|
|
3012
|
-
}
|
|
3013
|
-
break;
|
|
3014
3003
|
case this.compiler.SyntaxKind.ArrayLiteralExpression:
|
|
3015
3004
|
if (optionDefinition.brand === "list") {
|
|
3016
3005
|
const value = [];
|
|
@@ -3117,7 +3106,7 @@ class ConfigService {
|
|
|
3117
3106
|
allowNoTestFiles: false,
|
|
3118
3107
|
failFast: false,
|
|
3119
3108
|
rootPath: "./",
|
|
3120
|
-
target: ["latest"],
|
|
3109
|
+
target: [Environment.isTypeScriptInstalled ? "current" : "latest"],
|
|
3121
3110
|
testFileMatch: ["**/*.tst.*", "**/__typetests__/*.test.*", "**/typetests/*.test.*"],
|
|
3122
3111
|
};
|
|
3123
3112
|
#pathMatch = [];
|
|
@@ -3135,25 +3124,19 @@ class ConfigService {
|
|
|
3135
3124
|
static get defaultOptions() {
|
|
3136
3125
|
return ConfigService.#defaultOptions;
|
|
3137
3126
|
}
|
|
3138
|
-
#normalizePath(filePath) {
|
|
3139
|
-
if (path.sep === "/") {
|
|
3140
|
-
return filePath;
|
|
3141
|
-
}
|
|
3142
|
-
return filePath.replace(/\\/g, "/");
|
|
3143
|
-
}
|
|
3144
3127
|
#onDiagnostic = (diagnostic) => {
|
|
3145
3128
|
EventEmitter.dispatch(["config:error", { diagnostics: [diagnostic] }]);
|
|
3146
3129
|
};
|
|
3147
|
-
parseCommandLine(commandLineArgs) {
|
|
3130
|
+
async parseCommandLine(commandLineArgs) {
|
|
3148
3131
|
this.#commandLineOptions = {};
|
|
3149
3132
|
this.#pathMatch = [];
|
|
3150
3133
|
const commandLineWorker = new CommandLineOptionsWorker(this.#commandLineOptions, this.#pathMatch, this.#storeService, this.#onDiagnostic);
|
|
3151
|
-
commandLineWorker.parse(commandLineArgs);
|
|
3134
|
+
await commandLineWorker.parse(commandLineArgs);
|
|
3152
3135
|
}
|
|
3153
3136
|
async readConfigFile(filePath, sourceText) {
|
|
3154
|
-
const configFilePath = filePath ?? this.#commandLineOptions.config ??
|
|
3137
|
+
const configFilePath = filePath ?? this.#commandLineOptions.config ?? Path.resolve("./tstyche.config.json");
|
|
3155
3138
|
this.#configFileOptions = {
|
|
3156
|
-
rootPath:
|
|
3139
|
+
rootPath: Path.dirname(configFilePath),
|
|
3157
3140
|
};
|
|
3158
3141
|
let configFileText = sourceText ?? "";
|
|
3159
3142
|
if (sourceText == null && existsSync(configFilePath)) {
|
|
@@ -3169,7 +3152,7 @@ class ConfigService {
|
|
|
3169
3152
|
...ConfigService.#defaultOptions,
|
|
3170
3153
|
...this.#configFileOptions,
|
|
3171
3154
|
...this.#commandLineOptions,
|
|
3172
|
-
pathMatch: this.#pathMatch
|
|
3155
|
+
pathMatch: this.#pathMatch,
|
|
3173
3156
|
};
|
|
3174
3157
|
return mergedOptions;
|
|
3175
3158
|
}
|
|
@@ -3178,7 +3161,7 @@ class ConfigService {
|
|
|
3178
3161
|
let testFilePaths = this.compiler.sys.readDirectory(rootPath, undefined, undefined, testFileMatch);
|
|
3179
3162
|
if (pathMatch.length > 0) {
|
|
3180
3163
|
testFilePaths = testFilePaths.filter((testFilePath) => pathMatch.some((match) => {
|
|
3181
|
-
const relativeTestFilePath =
|
|
3164
|
+
const relativeTestFilePath = Path.relative("", testFilePath);
|
|
3182
3165
|
return relativeTestFilePath.toLowerCase().includes(match.toLowerCase());
|
|
3183
3166
|
}));
|
|
3184
3167
|
}
|
|
@@ -3202,6 +3185,7 @@ var OptionBrand;
|
|
|
3202
3185
|
OptionBrand["String"] = "string";
|
|
3203
3186
|
OptionBrand["Number"] = "number";
|
|
3204
3187
|
OptionBrand["Boolean"] = "boolean";
|
|
3188
|
+
OptionBrand["True"] = "true";
|
|
3205
3189
|
OptionBrand["List"] = "list";
|
|
3206
3190
|
OptionBrand["Object"] = "object";
|
|
3207
3191
|
})(OptionBrand || (OptionBrand = {}));
|
|
@@ -3212,165 +3196,28 @@ var OptionGroup;
|
|
|
3212
3196
|
OptionGroup[OptionGroup["ConfigFile"] = 4] = "ConfigFile";
|
|
3213
3197
|
})(OptionGroup || (OptionGroup = {}));
|
|
3214
3198
|
|
|
3215
|
-
class Lock {
|
|
3216
|
-
#lockFilePath;
|
|
3217
|
-
static #lockSuffix = "__lock__";
|
|
3218
|
-
constructor(targetPath) {
|
|
3219
|
-
this.#lockFilePath = Lock.#getLockFilePath(targetPath);
|
|
3220
|
-
writeFileSync(this.#lockFilePath, "");
|
|
3221
|
-
process.on("exit", () => {
|
|
3222
|
-
this.release();
|
|
3223
|
-
});
|
|
3224
|
-
}
|
|
3225
|
-
static #getLockFilePath(targetPath) {
|
|
3226
|
-
return `${targetPath}${Lock.#lockSuffix}`;
|
|
3227
|
-
}
|
|
3228
|
-
static async isLocked(targetPath, options) {
|
|
3229
|
-
let isLocked = existsSync(Lock.#getLockFilePath(targetPath));
|
|
3230
|
-
if (!isLocked) {
|
|
3231
|
-
return isLocked;
|
|
3232
|
-
}
|
|
3233
|
-
if (options?.timeout == null) {
|
|
3234
|
-
return isLocked;
|
|
3235
|
-
}
|
|
3236
|
-
const waitStartTime = Date.now();
|
|
3237
|
-
while (isLocked) {
|
|
3238
|
-
if (options.signal?.aborted === true) {
|
|
3239
|
-
break;
|
|
3240
|
-
}
|
|
3241
|
-
if (Date.now() - waitStartTime > options.timeout) {
|
|
3242
|
-
options.onDiagnostic?.(`Lock wait timeout of ${options.timeout / 1000}s was exceeded.`);
|
|
3243
|
-
break;
|
|
3244
|
-
}
|
|
3245
|
-
await Lock.#sleep(1000);
|
|
3246
|
-
isLocked = existsSync(Lock.#getLockFilePath(targetPath));
|
|
3247
|
-
}
|
|
3248
|
-
return isLocked;
|
|
3249
|
-
}
|
|
3250
|
-
release() {
|
|
3251
|
-
rmSync(this.#lockFilePath, { force: true });
|
|
3252
|
-
}
|
|
3253
|
-
static async #sleep(time) {
|
|
3254
|
-
return new Promise((resolve) => setTimeout(resolve, time));
|
|
3255
|
-
}
|
|
3256
|
-
}
|
|
3257
|
-
|
|
3258
|
-
class CompilerModuleWorker {
|
|
3259
|
-
#cachePath;
|
|
3260
|
-
#onDiagnostic;
|
|
3261
|
-
#readyFileName = "__ready__";
|
|
3262
|
-
#timeout = Environment.timeout * 1000;
|
|
3263
|
-
constructor(cachePath, onDiagnostic) {
|
|
3264
|
-
this.#cachePath = cachePath;
|
|
3265
|
-
this.#onDiagnostic = onDiagnostic;
|
|
3266
|
-
}
|
|
3267
|
-
async ensure(compilerVersion, signal) {
|
|
3268
|
-
const installationPath = path.join(this.#cachePath, compilerVersion);
|
|
3269
|
-
const readyFilePath = path.join(installationPath, this.#readyFileName);
|
|
3270
|
-
const tsserverFilePath = path.join(installationPath, "node_modules", "typescript", "lib", "tsserverlibrary.js");
|
|
3271
|
-
const typescriptFilePath = path.join(installationPath, "node_modules", "typescript", "lib", "typescript.js");
|
|
3272
|
-
if (await Lock.isLocked(installationPath, {
|
|
3273
|
-
onDiagnostic: (text) => {
|
|
3274
|
-
this.#onDiagnostic(Diagnostic.error([`Failed to install 'typescript@${compilerVersion}'.`, text]));
|
|
3275
|
-
},
|
|
3276
|
-
signal,
|
|
3277
|
-
timeout: this.#timeout,
|
|
3278
|
-
})) {
|
|
3279
|
-
return;
|
|
3280
|
-
}
|
|
3281
|
-
if (existsSync(readyFilePath)) {
|
|
3282
|
-
return tsserverFilePath;
|
|
3283
|
-
}
|
|
3284
|
-
EventEmitter.dispatch(["store:info", { compilerVersion, installationPath: this.#normalizePath(installationPath) }]);
|
|
3285
|
-
try {
|
|
3286
|
-
await fs.mkdir(installationPath, { recursive: true });
|
|
3287
|
-
const lock = new Lock(installationPath);
|
|
3288
|
-
await fs.writeFile(path.join(installationPath, "package.json"), this.#getPackageJson(compilerVersion));
|
|
3289
|
-
await this.#installPackage(installationPath, signal);
|
|
3290
|
-
await fs.writeFile(tsserverFilePath, await this.#getPatched(compilerVersion, tsserverFilePath));
|
|
3291
|
-
await fs.writeFile(typescriptFilePath, await this.#getPatched(compilerVersion, typescriptFilePath));
|
|
3292
|
-
await fs.writeFile(readyFilePath, "");
|
|
3293
|
-
lock.release();
|
|
3294
|
-
}
|
|
3295
|
-
catch (error) {
|
|
3296
|
-
this.#onDiagnostic(Diagnostic.fromError(`Failed to install 'typescript@${compilerVersion}'.`, error));
|
|
3297
|
-
}
|
|
3298
|
-
return tsserverFilePath;
|
|
3299
|
-
}
|
|
3300
|
-
#getPackageJson(version) {
|
|
3301
|
-
const packageJson = {
|
|
3302
|
-
name: "@tstyche/typescript",
|
|
3303
|
-
version,
|
|
3304
|
-
description: "Do not change. This package was generated by TSTyche",
|
|
3305
|
-
private: true,
|
|
3306
|
-
license: "MIT",
|
|
3307
|
-
dependencies: {
|
|
3308
|
-
typescript: version,
|
|
3309
|
-
},
|
|
3310
|
-
};
|
|
3311
|
-
return JSON.stringify(packageJson, null, 2);
|
|
3312
|
-
}
|
|
3313
|
-
async #getPatched(version, filePath) {
|
|
3314
|
-
function ts5Patch(match, indent) {
|
|
3315
|
-
return [match, indent, "isTypeIdenticalTo,", indent, "isTypeSubtypeOf,"].join("");
|
|
3316
|
-
}
|
|
3317
|
-
function ts4Patch(match, indent) {
|
|
3318
|
-
return [match, indent, "isTypeIdenticalTo: isTypeIdenticalTo,", indent, "isTypeSubtypeOf: isTypeSubtypeOf,"].join("");
|
|
3319
|
-
}
|
|
3320
|
-
const fileContent = await fs.readFile(filePath, { encoding: "utf8" });
|
|
3321
|
-
if (version.startsWith("5")) {
|
|
3322
|
-
return fileContent.replace(/(\s+)isTypeAssignableTo,/, ts5Patch);
|
|
3323
|
-
}
|
|
3324
|
-
else {
|
|
3325
|
-
return fileContent.replace(/(\s+)isTypeAssignableTo: isTypeAssignableTo,/, ts4Patch);
|
|
3326
|
-
}
|
|
3327
|
-
}
|
|
3328
|
-
async #installPackage(cwd, signal) {
|
|
3329
|
-
const args = ["install", "--ignore-scripts", "--no-bin-links", "--no-package-lock"];
|
|
3330
|
-
return new Promise((resolve, reject) => {
|
|
3331
|
-
const spawnedNpm = spawn("npm", args, {
|
|
3332
|
-
cwd,
|
|
3333
|
-
shell: true,
|
|
3334
|
-
signal,
|
|
3335
|
-
stdio: "ignore",
|
|
3336
|
-
timeout: this.#timeout,
|
|
3337
|
-
});
|
|
3338
|
-
spawnedNpm.on("error", (error) => {
|
|
3339
|
-
reject(error);
|
|
3340
|
-
});
|
|
3341
|
-
spawnedNpm.on("close", (code, signal) => {
|
|
3342
|
-
if (code === 0) {
|
|
3343
|
-
resolve();
|
|
3344
|
-
}
|
|
3345
|
-
if (signal != null) {
|
|
3346
|
-
reject(new Error(`Setup timeout of ${this.#timeout / 1000}s was exceeded.`));
|
|
3347
|
-
}
|
|
3348
|
-
reject(new Error(`Process exited with code ${String(code)}.`));
|
|
3349
|
-
});
|
|
3350
|
-
});
|
|
3351
|
-
}
|
|
3352
|
-
#normalizePath(filePath) {
|
|
3353
|
-
if (path.sep === "/") {
|
|
3354
|
-
return filePath;
|
|
3355
|
-
}
|
|
3356
|
-
return filePath.replace(/\\/g, "/");
|
|
3357
|
-
}
|
|
3358
|
-
}
|
|
3359
|
-
|
|
3360
3199
|
class ManifestWorker {
|
|
3361
|
-
#cachePath;
|
|
3362
3200
|
#manifestFileName = "store-manifest.json";
|
|
3363
3201
|
#manifestFilePath;
|
|
3364
3202
|
#onDiagnostic;
|
|
3365
3203
|
#prune;
|
|
3366
3204
|
#registryUrl = new URL("https://registry.npmjs.org");
|
|
3205
|
+
#storePath;
|
|
3367
3206
|
#timeout = Environment.timeout * 1000;
|
|
3368
|
-
|
|
3369
|
-
|
|
3207
|
+
#version = "1";
|
|
3208
|
+
constructor(storePath, onDiagnostic, prune) {
|
|
3209
|
+
this.#storePath = storePath;
|
|
3370
3210
|
this.#onDiagnostic = onDiagnostic;
|
|
3371
|
-
this.#manifestFilePath =
|
|
3211
|
+
this.#manifestFilePath = Path.join(storePath, this.#manifestFileName);
|
|
3372
3212
|
this.#prune = prune;
|
|
3373
3213
|
}
|
|
3214
|
+
async #create(signal) {
|
|
3215
|
+
const manifest = await this.#load(signal);
|
|
3216
|
+
if (manifest != null) {
|
|
3217
|
+
await this.persist(manifest);
|
|
3218
|
+
}
|
|
3219
|
+
return manifest;
|
|
3220
|
+
}
|
|
3374
3221
|
async #fetch(signal) {
|
|
3375
3222
|
return new Promise((resolve, reject) => {
|
|
3376
3223
|
const request = https.get(new URL("typescript", this.#registryUrl), {
|
|
@@ -3407,8 +3254,9 @@ class ManifestWorker {
|
|
|
3407
3254
|
}
|
|
3408
3255
|
return false;
|
|
3409
3256
|
}
|
|
3410
|
-
async #load(signal,
|
|
3257
|
+
async #load(signal, options = { quite: false }) {
|
|
3411
3258
|
const manifest = {
|
|
3259
|
+
$version: this.#version,
|
|
3412
3260
|
lastUpdated: Date.now(),
|
|
3413
3261
|
resolutions: {},
|
|
3414
3262
|
versions: [],
|
|
@@ -3420,13 +3268,13 @@ class ManifestWorker {
|
|
|
3420
3268
|
abortController.abort(`Setup timeout of ${this.#timeout / 1000}s was exceeded.`);
|
|
3421
3269
|
}, { once: true });
|
|
3422
3270
|
signal?.addEventListener("abort", () => {
|
|
3423
|
-
abortController.abort(
|
|
3271
|
+
abortController.abort("Fetch got canceled by request.");
|
|
3424
3272
|
}, { once: true });
|
|
3425
3273
|
try {
|
|
3426
3274
|
packageMetadata = await this.#fetch(abortController.signal);
|
|
3427
3275
|
}
|
|
3428
3276
|
catch (error) {
|
|
3429
|
-
if (!
|
|
3277
|
+
if (!options.quite) {
|
|
3430
3278
|
const text = [`Failed to fetch metadata of the 'typescript' package from '${this.#registryUrl.href}'.`];
|
|
3431
3279
|
if (error instanceof Error && error.name !== "AbortError") {
|
|
3432
3280
|
text.push("Might be there is an issue with the registry or the network connection.");
|
|
@@ -3455,15 +3303,10 @@ class ManifestWorker {
|
|
|
3455
3303
|
}
|
|
3456
3304
|
return manifest;
|
|
3457
3305
|
}
|
|
3458
|
-
async open(signal) {
|
|
3306
|
+
async open(signal, options) {
|
|
3459
3307
|
let manifest;
|
|
3460
3308
|
if (!existsSync(this.#manifestFilePath)) {
|
|
3461
|
-
|
|
3462
|
-
if (manifest == null) {
|
|
3463
|
-
return;
|
|
3464
|
-
}
|
|
3465
|
-
await this.persist(manifest);
|
|
3466
|
-
return manifest;
|
|
3309
|
+
return this.#create(signal);
|
|
3467
3310
|
}
|
|
3468
3311
|
let manifestText;
|
|
3469
3312
|
try {
|
|
@@ -3478,85 +3321,248 @@ class ManifestWorker {
|
|
|
3478
3321
|
try {
|
|
3479
3322
|
manifest = JSON.parse(manifestText);
|
|
3480
3323
|
}
|
|
3481
|
-
catch
|
|
3482
|
-
this.#onDiagnostic(Diagnostic.fromError([
|
|
3483
|
-
`Failed to parse '${this.#manifestFilePath}'.`,
|
|
3484
|
-
"Cached files appeared to be corrupt and got removed. Try running 'tstyche' again.",
|
|
3485
|
-
], error));
|
|
3324
|
+
catch {
|
|
3486
3325
|
}
|
|
3487
|
-
if (manifest == null) {
|
|
3326
|
+
if (manifest == null || manifest.$version !== this.#version) {
|
|
3488
3327
|
await this.#prune();
|
|
3489
|
-
return;
|
|
3490
|
-
}
|
|
3491
|
-
if (this.isOutdated(manifest)) {
|
|
3492
|
-
|
|
3328
|
+
return this.#create(signal);
|
|
3329
|
+
}
|
|
3330
|
+
if (this.isOutdated(manifest) || options?.refresh === true) {
|
|
3331
|
+
const quite = options?.refresh !== true;
|
|
3332
|
+
const freshManifest = await this.#load(signal, { quite });
|
|
3333
|
+
if (freshManifest != null) {
|
|
3334
|
+
manifest = { ...manifest, ...freshManifest };
|
|
3335
|
+
await this.persist(manifest);
|
|
3336
|
+
}
|
|
3493
3337
|
}
|
|
3494
3338
|
return manifest;
|
|
3495
3339
|
}
|
|
3496
3340
|
async persist(manifest) {
|
|
3497
|
-
if (!existsSync(this.#
|
|
3498
|
-
await fs.mkdir(this.#
|
|
3341
|
+
if (!existsSync(this.#storePath)) {
|
|
3342
|
+
await fs.mkdir(this.#storePath, { recursive: true });
|
|
3499
3343
|
}
|
|
3500
3344
|
await fs.writeFile(this.#manifestFilePath, JSON.stringify(manifest));
|
|
3501
3345
|
}
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3348
|
+
class Lock {
|
|
3349
|
+
#lockFilePath;
|
|
3350
|
+
static #lockSuffix = "__lock__";
|
|
3351
|
+
constructor(targetPath) {
|
|
3352
|
+
this.#lockFilePath = Lock.#getLockFilePath(targetPath);
|
|
3353
|
+
writeFileSync(this.#lockFilePath, "");
|
|
3354
|
+
process.on("exit", () => {
|
|
3355
|
+
this.release();
|
|
3356
|
+
});
|
|
3357
|
+
}
|
|
3358
|
+
static #getLockFilePath(targetPath) {
|
|
3359
|
+
return `${targetPath}${Lock.#lockSuffix}`;
|
|
3360
|
+
}
|
|
3361
|
+
static async isLocked(targetPath, options) {
|
|
3362
|
+
let isLocked = existsSync(Lock.#getLockFilePath(targetPath));
|
|
3363
|
+
if (!isLocked) {
|
|
3364
|
+
return isLocked;
|
|
3507
3365
|
}
|
|
3508
|
-
|
|
3366
|
+
if (options?.timeout == null) {
|
|
3367
|
+
return isLocked;
|
|
3368
|
+
}
|
|
3369
|
+
const waitStartTime = Date.now();
|
|
3370
|
+
while (isLocked) {
|
|
3371
|
+
if (options.signal?.aborted === true) {
|
|
3372
|
+
break;
|
|
3373
|
+
}
|
|
3374
|
+
if (Date.now() - waitStartTime > options.timeout) {
|
|
3375
|
+
options.onDiagnostic?.(`Lock wait timeout of ${options.timeout / 1000}s was exceeded.`);
|
|
3376
|
+
break;
|
|
3377
|
+
}
|
|
3378
|
+
await Lock.#sleep(1000);
|
|
3379
|
+
isLocked = existsSync(Lock.#getLockFilePath(targetPath));
|
|
3380
|
+
}
|
|
3381
|
+
return isLocked;
|
|
3382
|
+
}
|
|
3383
|
+
release() {
|
|
3384
|
+
rmSync(this.#lockFilePath, { force: true });
|
|
3385
|
+
}
|
|
3386
|
+
static async #sleep(time) {
|
|
3387
|
+
return new Promise((resolve) => setTimeout(resolve, time));
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
|
|
3391
|
+
class Version {
|
|
3392
|
+
static satisfies(source, target) {
|
|
3393
|
+
const sourceElements = source.split(/\.|-/);
|
|
3394
|
+
const targetElements = target.split(/\.|-/);
|
|
3395
|
+
function compare(index = 0) {
|
|
3396
|
+
const sourceElement = sourceElements[index];
|
|
3397
|
+
const targetElement = targetElements[index];
|
|
3398
|
+
if (sourceElement > targetElement) {
|
|
3399
|
+
return true;
|
|
3400
|
+
}
|
|
3401
|
+
if (sourceElement === targetElement) {
|
|
3402
|
+
if (index === targetElements.length - 1) {
|
|
3403
|
+
return true;
|
|
3404
|
+
}
|
|
3405
|
+
return compare(index + 1);
|
|
3406
|
+
}
|
|
3407
|
+
return false;
|
|
3408
|
+
}
|
|
3409
|
+
return compare();
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
|
|
3413
|
+
class PackageInstaller {
|
|
3414
|
+
#onDiagnostic;
|
|
3415
|
+
#readyFileName = "__ready__";
|
|
3416
|
+
#storePath;
|
|
3417
|
+
#timeout = Environment.timeout * 1000;
|
|
3418
|
+
constructor(storePath, onDiagnostic) {
|
|
3419
|
+
this.#storePath = storePath;
|
|
3420
|
+
this.#onDiagnostic = onDiagnostic;
|
|
3421
|
+
}
|
|
3422
|
+
async ensure(compilerVersion, signal) {
|
|
3423
|
+
const installationPath = Path.join(this.#storePath, compilerVersion);
|
|
3424
|
+
const readyFilePath = Path.join(installationPath, this.#readyFileName);
|
|
3425
|
+
if (await Lock.isLocked(installationPath, {
|
|
3426
|
+
onDiagnostic: (text) => {
|
|
3427
|
+
this.#onDiagnostic(Diagnostic.error([`Failed to install 'typescript@${compilerVersion}'.`, text]));
|
|
3428
|
+
},
|
|
3429
|
+
signal,
|
|
3430
|
+
timeout: this.#timeout,
|
|
3431
|
+
})) {
|
|
3432
|
+
return;
|
|
3433
|
+
}
|
|
3434
|
+
if (!existsSync(readyFilePath)) {
|
|
3435
|
+
EventEmitter.dispatch(["store:info", { compilerVersion, installationPath }]);
|
|
3436
|
+
try {
|
|
3437
|
+
await fs.mkdir(installationPath, { recursive: true });
|
|
3438
|
+
const lock = new Lock(installationPath);
|
|
3439
|
+
const packageJson = {
|
|
3440
|
+
name: "tstyche-typescript",
|
|
3441
|
+
version: compilerVersion,
|
|
3442
|
+
description: "Do not change. This package was generated by TSTyche",
|
|
3443
|
+
private: true,
|
|
3444
|
+
license: "MIT",
|
|
3445
|
+
dependencies: {
|
|
3446
|
+
typescript: compilerVersion,
|
|
3447
|
+
},
|
|
3448
|
+
};
|
|
3449
|
+
await fs.writeFile(Path.join(installationPath, "package.json"), JSON.stringify(packageJson, null, 2));
|
|
3450
|
+
await this.#install(installationPath, signal);
|
|
3451
|
+
await fs.writeFile(readyFilePath, "");
|
|
3452
|
+
lock.release();
|
|
3453
|
+
}
|
|
3454
|
+
catch (error) {
|
|
3455
|
+
this.#onDiagnostic(Diagnostic.fromError(`Failed to install 'typescript@${compilerVersion}'.`, error));
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
if (Version.satisfies(compilerVersion, "5.3")) {
|
|
3459
|
+
return Path.join(installationPath, "node_modules", "typescript", "lib", "typescript.js");
|
|
3460
|
+
}
|
|
3461
|
+
return Path.join(installationPath, "node_modules", "typescript", "lib", "tsserverlibrary.js");
|
|
3462
|
+
}
|
|
3463
|
+
async #install(cwd, signal) {
|
|
3464
|
+
const args = ["install", "--ignore-scripts", "--no-bin-links", "--no-package-lock"];
|
|
3465
|
+
return new Promise((resolve, reject) => {
|
|
3466
|
+
const spawnedNpm = spawn("npm", args, {
|
|
3467
|
+
cwd,
|
|
3468
|
+
shell: true,
|
|
3469
|
+
signal,
|
|
3470
|
+
stdio: "ignore",
|
|
3471
|
+
timeout: this.#timeout,
|
|
3472
|
+
});
|
|
3473
|
+
spawnedNpm.on("error", (error) => {
|
|
3474
|
+
reject(error);
|
|
3475
|
+
});
|
|
3476
|
+
spawnedNpm.on("close", (code, signal) => {
|
|
3477
|
+
if (code === 0) {
|
|
3478
|
+
resolve();
|
|
3479
|
+
}
|
|
3480
|
+
if (signal != null) {
|
|
3481
|
+
reject(new Error(`Setup timeout of ${this.#timeout / 1000}s was exceeded.`));
|
|
3482
|
+
}
|
|
3483
|
+
reject(new Error(`Process exited with code ${String(code)}.`));
|
|
3484
|
+
});
|
|
3485
|
+
});
|
|
3509
3486
|
}
|
|
3510
3487
|
}
|
|
3511
3488
|
|
|
3512
3489
|
class StoreService {
|
|
3513
|
-
#
|
|
3514
|
-
#compilerModuleWorker;
|
|
3490
|
+
#compilerInstanceCache = new Map();
|
|
3515
3491
|
#manifest;
|
|
3516
3492
|
#manifestWorker;
|
|
3517
3493
|
#nodeRequire = createRequire(import.meta.url);
|
|
3494
|
+
#packageInstaller;
|
|
3495
|
+
#storePath;
|
|
3518
3496
|
constructor() {
|
|
3519
|
-
this.#
|
|
3520
|
-
this.#
|
|
3521
|
-
this.#manifestWorker = new ManifestWorker(this.#
|
|
3497
|
+
this.#storePath = Environment.storePath;
|
|
3498
|
+
this.#packageInstaller = new PackageInstaller(this.#storePath, this.#onDiagnostic);
|
|
3499
|
+
this.#manifestWorker = new ManifestWorker(this.#storePath, this.#onDiagnostic, this.prune);
|
|
3522
3500
|
}
|
|
3523
|
-
|
|
3501
|
+
async getSupportedTags(signal) {
|
|
3502
|
+
await this.open(signal);
|
|
3524
3503
|
if (!this.#manifest) {
|
|
3525
|
-
this.#onDiagnostic(Diagnostic.error("Store manifest is not open. Call 'StoreService.open()' first."));
|
|
3526
3504
|
return [];
|
|
3527
3505
|
}
|
|
3528
3506
|
return [...Object.keys(this.#manifest.resolutions), ...this.#manifest.versions, "current"].sort();
|
|
3529
3507
|
}
|
|
3530
3508
|
async install(tag, signal) {
|
|
3531
|
-
if (
|
|
3532
|
-
this.#onDiagnostic(Diagnostic.error("Store manifest is not open. Call 'StoreService.open()' first."));
|
|
3509
|
+
if (tag === "current") {
|
|
3533
3510
|
return;
|
|
3534
3511
|
}
|
|
3535
|
-
const version = this.resolveTag(tag);
|
|
3512
|
+
const version = await this.resolveTag(tag, signal);
|
|
3536
3513
|
if (version == null) {
|
|
3537
3514
|
this.#onDiagnostic(Diagnostic.error(`Cannot add the 'typescript' package for the '${tag}' tag.`));
|
|
3538
3515
|
return;
|
|
3539
3516
|
}
|
|
3540
|
-
return this.#
|
|
3517
|
+
return this.#packageInstaller.ensure(version, signal);
|
|
3541
3518
|
}
|
|
3542
3519
|
async load(tag, signal) {
|
|
3520
|
+
let compilerInstance = this.#compilerInstanceCache.get(tag);
|
|
3521
|
+
if (compilerInstance != null) {
|
|
3522
|
+
return compilerInstance;
|
|
3523
|
+
}
|
|
3543
3524
|
let modulePath;
|
|
3544
|
-
if (tag === "
|
|
3525
|
+
if (tag === "current") {
|
|
3545
3526
|
try {
|
|
3546
|
-
modulePath = this.#nodeRequire.resolve("typescript
|
|
3527
|
+
modulePath = this.#nodeRequire.resolve("typescript");
|
|
3547
3528
|
}
|
|
3548
|
-
catch {
|
|
3549
|
-
|
|
3529
|
+
catch (error) {
|
|
3530
|
+
this.#onDiagnostic(Diagnostic.fromError("Failed to resolve locally installed 'typescript' package. It might be not installed.", error));
|
|
3550
3531
|
}
|
|
3551
3532
|
}
|
|
3552
3533
|
if (modulePath == null) {
|
|
3553
|
-
|
|
3534
|
+
if (tag === "current") {
|
|
3535
|
+
return;
|
|
3536
|
+
}
|
|
3537
|
+
const version = await this.resolveTag(tag, signal);
|
|
3538
|
+
if (version == null) {
|
|
3539
|
+
this.#onDiagnostic(Diagnostic.error(`Cannot add the 'typescript' package for the '${tag}' tag.`));
|
|
3540
|
+
return;
|
|
3541
|
+
}
|
|
3542
|
+
compilerInstance = this.#compilerInstanceCache.get(version);
|
|
3543
|
+
if (compilerInstance != null) {
|
|
3544
|
+
return compilerInstance;
|
|
3545
|
+
}
|
|
3546
|
+
modulePath = await this.#packageInstaller.ensure(version, signal);
|
|
3554
3547
|
}
|
|
3555
3548
|
if (modulePath != null) {
|
|
3556
|
-
|
|
3549
|
+
compilerInstance = await this.#loadModule(modulePath);
|
|
3550
|
+
this.#compilerInstanceCache.set(tag, compilerInstance);
|
|
3551
|
+
this.#compilerInstanceCache.set(compilerInstance.version, compilerInstance);
|
|
3552
|
+
return compilerInstance;
|
|
3557
3553
|
}
|
|
3558
3554
|
return;
|
|
3559
3555
|
}
|
|
3556
|
+
async #loadModule(modulePath) {
|
|
3557
|
+
const exports = {};
|
|
3558
|
+
const require = createRequire(modulePath);
|
|
3559
|
+
const module = { exports };
|
|
3560
|
+
let sourceText = await fs.readFile(modulePath, { encoding: "utf8" });
|
|
3561
|
+
sourceText = sourceText.replace("isTypeAssignableTo,", "isTypeAssignableTo, isTypeIdenticalTo, isTypeSubtypeOf,");
|
|
3562
|
+
const compiledWrapper = vm.compileFunction(sourceText, ["exports", "require", "module", "__filename", "__dirname"], { filename: modulePath });
|
|
3563
|
+
compiledWrapper(exports, require, module, modulePath, Path.dirname(modulePath));
|
|
3564
|
+
return module.exports;
|
|
3565
|
+
}
|
|
3560
3566
|
#onDiagnostic = (diagnostic) => {
|
|
3561
3567
|
EventEmitter.dispatch(["store:error", { diagnostics: [diagnostic] }]);
|
|
3562
3568
|
};
|
|
@@ -3567,21 +3573,16 @@ class StoreService {
|
|
|
3567
3573
|
this.#manifest = await this.#manifestWorker.open(signal);
|
|
3568
3574
|
}
|
|
3569
3575
|
prune = async () => {
|
|
3570
|
-
await fs.rm(this.#
|
|
3576
|
+
await fs.rm(this.#storePath, { force: true, recursive: true });
|
|
3571
3577
|
};
|
|
3572
|
-
resolveTag(tag) {
|
|
3578
|
+
async resolveTag(tag, signal) {
|
|
3579
|
+
if (tag === "current") {
|
|
3580
|
+
return tag;
|
|
3581
|
+
}
|
|
3582
|
+
await this.open(signal);
|
|
3573
3583
|
if (!this.#manifest) {
|
|
3574
|
-
this.#onDiagnostic(Diagnostic.error("Store manifest is not open. Call 'StoreService.open()' first."));
|
|
3575
3584
|
return;
|
|
3576
3585
|
}
|
|
3577
|
-
if (tag === "current") {
|
|
3578
|
-
try {
|
|
3579
|
-
tag = this.#nodeRequire("typescript").version;
|
|
3580
|
-
}
|
|
3581
|
-
catch (error) {
|
|
3582
|
-
this.#onDiagnostic(Diagnostic.fromError("Failed to resolve tag 'current'. The 'typescript' package might be not installed.", error));
|
|
3583
|
-
}
|
|
3584
|
-
}
|
|
3585
3586
|
if (this.#manifest.versions.includes(tag)) {
|
|
3586
3587
|
return tag;
|
|
3587
3588
|
}
|
|
@@ -3596,15 +3597,14 @@ class StoreService {
|
|
|
3596
3597
|
return version;
|
|
3597
3598
|
}
|
|
3598
3599
|
async update(signal) {
|
|
3599
|
-
|
|
3600
|
-
this.#onDiagnostic(Diagnostic.error("Store manifest is not open. Call 'StoreService.open()' first."));
|
|
3601
|
-
return;
|
|
3602
|
-
}
|
|
3603
|
-
await this.#manifestWorker.update(this.#manifest, signal);
|
|
3600
|
+
await this.#manifestWorker.open(signal, { refresh: true });
|
|
3604
3601
|
}
|
|
3605
|
-
validateTag(tag) {
|
|
3602
|
+
async validateTag(tag, signal) {
|
|
3603
|
+
if (tag === "current") {
|
|
3604
|
+
return true;
|
|
3605
|
+
}
|
|
3606
|
+
await this.open(signal);
|
|
3606
3607
|
if (!this.#manifest) {
|
|
3607
|
-
this.#onDiagnostic(Diagnostic.error("Store manifest is not open. Call 'StoreService.open()' first."));
|
|
3608
3608
|
return false;
|
|
3609
3609
|
}
|
|
3610
3610
|
if (this.#manifest.versions.includes(tag) || tag in this.#manifest.resolutions || tag === "current") {
|
|
@@ -3653,36 +3653,35 @@ class Cli {
|
|
|
3653
3653
|
break;
|
|
3654
3654
|
}
|
|
3655
3655
|
};
|
|
3656
|
-
async run(
|
|
3656
|
+
async run(commandLineArguments) {
|
|
3657
3657
|
EventEmitter.addHandler(this.#onStartupEvent);
|
|
3658
|
-
|
|
3659
|
-
|
|
3658
|
+
if (commandLineArguments.includes("--help")) {
|
|
3659
|
+
const commandLineOptionDefinitions = OptionDefinitionsMap.for(2);
|
|
3660
|
+
this.#logger.writeMessage(helpText(commandLineOptionDefinitions, TSTyche.version));
|
|
3660
3661
|
return;
|
|
3661
3662
|
}
|
|
3662
|
-
|
|
3663
|
-
|
|
3663
|
+
if (commandLineArguments.includes("--prune")) {
|
|
3664
|
+
await this.#storeService.prune();
|
|
3664
3665
|
return;
|
|
3665
3666
|
}
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
if (this.#process.exitCode === 1) {
|
|
3667
|
+
if (commandLineArguments.includes("--version")) {
|
|
3668
|
+
this.#logger.writeMessage(formattedText(TSTyche.version));
|
|
3669
3669
|
return;
|
|
3670
3670
|
}
|
|
3671
|
-
if (
|
|
3672
|
-
await this.#storeService.
|
|
3671
|
+
if (commandLineArguments.includes("--update")) {
|
|
3672
|
+
await this.#storeService.update(this.#abortController.signal);
|
|
3673
3673
|
return;
|
|
3674
3674
|
}
|
|
3675
|
-
if (
|
|
3676
|
-
const commandLineOptionDefinitions = OptionDefinitionsMap.for(2);
|
|
3677
|
-
this.#logger.writeMessage(helpText(commandLineOptionDefinitions, TSTyche.version));
|
|
3675
|
+
if (this.#process.exitCode === 1) {
|
|
3678
3676
|
return;
|
|
3679
3677
|
}
|
|
3680
|
-
|
|
3681
|
-
|
|
3678
|
+
const compiler = await this.#storeService.load(Environment.isTypeScriptInstalled ? "current" : "latest", this.#abortController.signal);
|
|
3679
|
+
if (!compiler) {
|
|
3682
3680
|
return;
|
|
3683
3681
|
}
|
|
3684
|
-
|
|
3685
|
-
|
|
3682
|
+
const configService = new ConfigService(compiler, this.#storeService);
|
|
3683
|
+
await configService.parseCommandLine(commandLineArguments);
|
|
3684
|
+
if (this.#process.exitCode === 1) {
|
|
3686
3685
|
return;
|
|
3687
3686
|
}
|
|
3688
3687
|
await configService.readConfigFile();
|
|
@@ -3719,4 +3718,4 @@ class Cli {
|
|
|
3719
3718
|
}
|
|
3720
3719
|
}
|
|
3721
3720
|
|
|
3722
|
-
export { Assertion, Cli, CollectService, ConfigService, DescribeResult, Diagnostic, DiagnosticCategory, Environment, EventEmitter, Expect, ExpectResult, FileResult, Logger, OptionBrand, OptionDefinitionsMap, OptionGroup, ProjectResult, ProjectService, Reporter, Result, ResultCount, ResultManager, ResultStatus, ResultTiming, Scribbler, StoreService, SummaryReporter, TSTyche, TargetResult, TaskRunner, TestMember, TestMemberBrand, TestMemberFlags, TestResult, TestTree, ThoroughReporter, addsPackageStepText, describeNameText, diagnosticText, fileStatusText, fileViewText, formattedText, helpText, summaryText, testNameText, usesCompilerStepText };
|
|
3721
|
+
export { Assertion, Cli, CollectService, Color, ConfigService, DescribeResult, Diagnostic, DiagnosticCategory, Environment, EventEmitter, Expect, ExpectResult, FileResult, Line, Logger, OptionBrand, OptionDefinitionsMap, OptionGroup, Path, ProjectResult, ProjectService, Reporter, Result, ResultCount, ResultManager, ResultStatus, ResultTiming, Scribbler, StoreService, SummaryReporter, TSTyche, TargetResult, TaskRunner, TestMember, TestMemberBrand, TestMemberFlags, TestResult, TestTree, Text, ThoroughReporter, addsPackageStepText, describeNameText, diagnosticText, fileStatusText, fileViewText, formattedText, helpText, summaryText, testNameText, usesCompilerStepText };
|