tstyche 1.0.0-beta.7 → 1.0.0-beta.9

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/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 { createRequire } from 'node:module';
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.#normalizePath(Environment.#resolveStorePath());
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 path.resolve(process.env["TSTYCHE_STORE_PATH"]);
97
+ return Path.resolve(process.env["TSTYCHE_STORE_PATH"]);
62
98
  }
63
99
  if (process.platform === "darwin") {
64
- return path.resolve(os.homedir(), "Library", "TSTyche");
100
+ return Path.resolve(os.homedir(), "Library", "TSTyche");
65
101
  }
66
102
  if (process.env["LocalAppData"] != null) {
67
- return path.resolve(process.env["LocalAppData"], "TSTyche");
103
+ return Path.resolve(process.env["LocalAppData"], "TSTyche");
68
104
  }
69
105
  if (process.env["XDG_DATA_HOME"] != null) {
70
- return path.resolve(process.env["XDG_DATA_HOME"], "TSTyche");
106
+ return Path.resolve(process.env["XDG_DATA_HOME"], "TSTyche");
71
107
  }
72
- return path.resolve(os.homedir(), ".local", "share", "TSTyche");
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 ["\x1b[", Array.isArray(attributes) ? attributes.join(";") : attributes, "m"].join("");
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.replace(notEmptyLineRegExp, indentStep.repeat(level));
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 i = firstLine; i <= lastLine; i++) {
236
- const lineStart = this.props.file.getPositionOfLineAndCharacter(i, 0);
237
- const lineEnd = i === lastLineInFile ? this.props.file.text.length : this.props.file.getPositionOfLineAndCharacter(i + 1, 0);
238
- const lineNumberText = String(i + 1);
239
- const lineText = this.props.file.text.slice(lineStart, lineEnd).trimEnd().replace(/\t/g, " ");
240
- if (i === markedLine) {
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
- Scribbler.createElement(RelativePathText, { isDirectory: true, to: path.dirname(this.props.filePath) })),
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 second", "Only run the test files with matching path."],
399
- ["tstyche --target 4.7,4.8,latest", "Test on all specified versions of TypeScript."],
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 = undefined;
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 CliUsageText {
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 OptionNameText {
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 OptionHintText {
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 CliOptionsText {
569
+ class CommandLineOptionsText {
484
570
  props;
485
571
  constructor(props) {
486
572
  this.props = props;
487
573
  }
488
574
  render() {
489
- const definitions = Array.from(this.props.optionDefinitions.values());
490
- const optionsText = definitions.map((definition) => (Scribbler.createElement(Text, null,
491
- Scribbler.createElement(CommandText, { text: Scribbler.createElement(OptionNameText, { text: definition.name }), hint: Scribbler.createElement(OptionHintText, { definition: definition }) }),
492
- Scribbler.createElement(OptionDescriptionText, { text: definition.description }),
493
- Scribbler.createElement(Line, null))));
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, "CLI Options"),
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(CliUsageText, null),
601
+ Scribbler.createElement(CommandLineUsageText, null),
510
602
  Scribbler.createElement(Line, null),
511
- Scribbler.createElement(CliOptionsText, { optionDefinitions: optionDefinitions }),
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
- Scribbler.createElement(RelativePathText, { to: this.props.filePath })));
781
+ Path.relative("", this.props.filePath)));
690
782
  }
691
783
  }
692
784
  function usesCompilerStepText(compilerVersion, tsconfigFilePath, options) {
693
- const projectPathText = typeof tsconfigFilePath === "string" ? Scribbler.createElement(ProjectNameText, { filePath: tsconfigFilePath }) : undefined;
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
- this.diagnostics = this.#mapDiagnostics(node, this.parent);
1344
- this.name = this.#resolveName(node);
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.forEach((diagnostic) => {
1394
+ for (const diagnostic of parent.diagnostics) {
1364
1395
  if (diagnostic.start != null && diagnostic.start >= blockStart && diagnostic.start <= blockEnd) {
1365
- mapped.push(diagnostic);
1366
- }
1367
- else {
1368
- unmapped.push(diagnostic);
1396
+ this.diagnostics.add(diagnostic);
1397
+ parent.diagnostics.delete(diagnostic);
1369
1398
  }
1370
- });
1371
- parent.diagnostics = unmapped;
1399
+ }
1372
1400
  }
1373
- return mapped;
1374
1401
  }
1375
- #resolveName(node) {
1376
- return node.arguments[0] !== undefined && this.compiler.isStringLiteral(node.arguments[0])
1377
- ? node.arguments[0].text
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.length > 0 && assertion.matcherName.getText() !== "toRaiseError") {
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.length > 0) {
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.length > 0) {
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.length > 0) {
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.9";
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: "boolean",
2580
- description: "Print the list of CLI options with brief descriptions and exit.",
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: "boolean",
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: "boolean",
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: "boolean",
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: "boolean",
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: "boolean",
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: "boolean",
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 { supportedTags } = this.#storeService;
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,4.8,latest'.", supportedTagsText);
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
- if (!this.#storeService.validateTag(optionValue)) {
2769
- this.#onDiagnostic(Diagnostic.error([
2770
- this.#optionDiagnosticText.versionIsNotSupported(optionValue),
2771
- ...this.#optionUsageText.get(optionName, optionBrand),
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
- #normalizePath(filePath) {
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 !== undefined) {
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 = this.#normalizePath(path.resolve(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 = this.#normalizePath(path.resolve(path.dirname(this.#configFilePath), 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 ?? path.resolve("./tstyche.config.json");
3137
+ const configFilePath = filePath ?? this.#commandLineOptions.config ?? Path.resolve("./tstyche.config.json");
3155
3138
  this.#configFileOptions = {
3156
- rootPath: this.#normalizePath(path.dirname(configFilePath)),
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.map((match) => this.#normalizePath(match)),
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 = this.#normalizePath(`./${path.relative("", testFilePath)}`);
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
- constructor(cachePath, onDiagnostic, prune) {
3369
- this.#cachePath = cachePath;
3207
+ #version = "1";
3208
+ constructor(storePath, onDiagnostic, prune) {
3209
+ this.#storePath = storePath;
3370
3210
  this.#onDiagnostic = onDiagnostic;
3371
- this.#manifestFilePath = path.join(cachePath, this.#manifestFileName);
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, isUpdate = false) {
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(`Fetch got canceled by request.`);
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 (!isUpdate) {
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
- manifest = await this.#load(signal);
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,257 @@ class ManifestWorker {
3478
3321
  try {
3479
3322
  manifest = JSON.parse(manifestText);
3480
3323
  }
3481
- catch (error) {
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
- manifest = await this.update(manifest, signal);
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.#cachePath)) {
3498
- await fs.mkdir(this.#cachePath, { recursive: true });
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
- async update(manifest, signal) {
3503
- const freshManifest = await this.#load(signal, true);
3504
- if (freshManifest != null) {
3505
- manifest = { ...manifest, ...freshManifest };
3506
- await this.persist(manifest);
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
- return manifest;
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
- #cachePath;
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.#cachePath = Environment.storePath;
3520
- this.#compilerModuleWorker = new CompilerModuleWorker(this.#cachePath, this.#onDiagnostic);
3521
- this.#manifestWorker = new ManifestWorker(this.#cachePath, this.#onDiagnostic, this.prune);
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
- get supportedTags() {
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 (!this.#manifest) {
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.#compilerModuleWorker.ensure(version, signal);
3517
+ return this.#packageInstaller.ensure(version, signal);
3541
3518
  }
3542
3519
  async load(tag, signal) {
3543
- let modulePath;
3544
- if (tag === "local") {
3520
+ let compilerInstance = this.#compilerInstanceCache.get(tag);
3521
+ if (compilerInstance != null) {
3522
+ return compilerInstance;
3523
+ }
3524
+ const modulePaths = [];
3525
+ if (tag === "current") {
3545
3526
  try {
3546
- modulePath = this.#nodeRequire.resolve("typescript/lib/tsserverlibrary.js");
3527
+ modulePaths.push(this.#nodeRequire.resolve("typescript/lib/tsserverlibrary.js"), this.#nodeRequire.resolve("typescript"));
3547
3528
  }
3548
- catch {
3549
- tag = "latest";
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
- if (modulePath == null) {
3553
- modulePath = await this.install(tag, signal);
3533
+ if (modulePaths.length === 0) {
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
+ const installedModulePath = await this.#packageInstaller.ensure(version, signal);
3547
+ if (installedModulePath != null) {
3548
+ modulePaths.push(installedModulePath);
3549
+ }
3554
3550
  }
3555
- if (modulePath != null) {
3556
- return this.#nodeRequire(modulePath);
3551
+ if (modulePaths.length !== 0) {
3552
+ compilerInstance = await this.#loadModule(modulePaths);
3553
+ this.#compilerInstanceCache.set(tag, compilerInstance);
3554
+ this.#compilerInstanceCache.set(compilerInstance.version, compilerInstance);
3555
+ return compilerInstance;
3557
3556
  }
3558
3557
  return;
3559
3558
  }
3559
+ async #loadModule(modulePaths) {
3560
+ const exports = {};
3561
+ const module = { exports };
3562
+ for (const modulePath of modulePaths) {
3563
+ const require = createRequire(modulePath);
3564
+ let sourceText = await fs.readFile(modulePath, { encoding: "utf8" });
3565
+ if (sourceText.length < 3000) {
3566
+ continue;
3567
+ }
3568
+ sourceText = sourceText.replace("isTypeAssignableTo,", "isTypeAssignableTo, isTypeIdenticalTo, isTypeSubtypeOf,");
3569
+ const compiledWrapper = vm.compileFunction(sourceText, ["exports", "require", "module", "__filename", "__dirname"], { filename: modulePath });
3570
+ compiledWrapper(exports, require, module, modulePath, Path.dirname(modulePath));
3571
+ break;
3572
+ }
3573
+ return module.exports;
3574
+ }
3560
3575
  #onDiagnostic = (diagnostic) => {
3561
3576
  EventEmitter.dispatch(["store:error", { diagnostics: [diagnostic] }]);
3562
3577
  };
@@ -3567,21 +3582,16 @@ class StoreService {
3567
3582
  this.#manifest = await this.#manifestWorker.open(signal);
3568
3583
  }
3569
3584
  prune = async () => {
3570
- await fs.rm(this.#cachePath, { force: true, recursive: true });
3585
+ await fs.rm(this.#storePath, { force: true, recursive: true });
3571
3586
  };
3572
- resolveTag(tag) {
3587
+ async resolveTag(tag, signal) {
3588
+ if (tag === "current") {
3589
+ return tag;
3590
+ }
3591
+ await this.open(signal);
3573
3592
  if (!this.#manifest) {
3574
- this.#onDiagnostic(Diagnostic.error("Store manifest is not open. Call 'StoreService.open()' first."));
3575
3593
  return;
3576
3594
  }
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
3595
  if (this.#manifest.versions.includes(tag)) {
3586
3596
  return tag;
3587
3597
  }
@@ -3596,15 +3606,14 @@ class StoreService {
3596
3606
  return version;
3597
3607
  }
3598
3608
  async update(signal) {
3599
- if (!this.#manifest) {
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);
3609
+ await this.#manifestWorker.open(signal, { refresh: true });
3604
3610
  }
3605
- validateTag(tag) {
3611
+ async validateTag(tag, signal) {
3612
+ if (tag === "current") {
3613
+ return true;
3614
+ }
3615
+ await this.open(signal);
3606
3616
  if (!this.#manifest) {
3607
- this.#onDiagnostic(Diagnostic.error("Store manifest is not open. Call 'StoreService.open()' first."));
3608
3617
  return false;
3609
3618
  }
3610
3619
  if (this.#manifest.versions.includes(tag) || tag in this.#manifest.resolutions || tag === "current") {
@@ -3653,36 +3662,35 @@ class Cli {
3653
3662
  break;
3654
3663
  }
3655
3664
  };
3656
- async run(commandLineArgs) {
3665
+ async run(commandLineArguments) {
3657
3666
  EventEmitter.addHandler(this.#onStartupEvent);
3658
- await this.#storeService.open(this.#abortController.signal);
3659
- if (this.#process.exitCode === 1) {
3667
+ if (commandLineArguments.includes("--help")) {
3668
+ const commandLineOptionDefinitions = OptionDefinitionsMap.for(2);
3669
+ this.#logger.writeMessage(helpText(commandLineOptionDefinitions, TSTyche.version));
3660
3670
  return;
3661
3671
  }
3662
- const compiler = await this.#storeService.load("local", this.#abortController.signal);
3663
- if (!compiler) {
3672
+ if (commandLineArguments.includes("--prune")) {
3673
+ await this.#storeService.prune();
3664
3674
  return;
3665
3675
  }
3666
- const configService = new ConfigService(compiler, this.#storeService);
3667
- configService.parseCommandLine(commandLineArgs);
3668
- if (this.#process.exitCode === 1) {
3676
+ if (commandLineArguments.includes("--version")) {
3677
+ this.#logger.writeMessage(formattedText(TSTyche.version));
3669
3678
  return;
3670
3679
  }
3671
- if (configService.commandLineOptions.prune === true) {
3672
- await this.#storeService.prune();
3680
+ if (commandLineArguments.includes("--update")) {
3681
+ await this.#storeService.update(this.#abortController.signal);
3673
3682
  return;
3674
3683
  }
3675
- if (configService.commandLineOptions.help === true) {
3676
- const commandLineOptionDefinitions = OptionDefinitionsMap.for(2);
3677
- this.#logger.writeMessage(helpText(commandLineOptionDefinitions, TSTyche.version));
3684
+ if (this.#process.exitCode === 1) {
3678
3685
  return;
3679
3686
  }
3680
- if (configService.commandLineOptions.update === true) {
3681
- await this.#storeService.update();
3687
+ const compiler = await this.#storeService.load(Environment.isTypeScriptInstalled ? "current" : "latest", this.#abortController.signal);
3688
+ if (!compiler) {
3682
3689
  return;
3683
3690
  }
3684
- if (configService.commandLineOptions.version === true) {
3685
- this.#logger.writeMessage(formattedText(TSTyche.version));
3691
+ const configService = new ConfigService(compiler, this.#storeService);
3692
+ await configService.parseCommandLine(commandLineArguments);
3693
+ if (this.#process.exitCode === 1) {
3686
3694
  return;
3687
3695
  }
3688
3696
  await configService.readConfigFile();
@@ -3719,4 +3727,4 @@ class Cli {
3719
3727
  }
3720
3728
  }
3721
3729
 
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 };
3730
+ 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 };