tstyche 2.1.0 → 3.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/tstyche.js CHANGED
@@ -6,7 +6,7 @@ import os from 'node:os';
6
6
  import { watch, existsSync, writeFileSync, rmSync } from 'node:fs';
7
7
  import fs from 'node:fs/promises';
8
8
  import vm from 'node:vm';
9
- import { spawn } from 'node:child_process';
9
+ import streamConsumers from 'node:stream/consumers';
10
10
 
11
11
  class EventEmitter {
12
12
  static #handlers = new Set();
@@ -446,6 +446,7 @@ class Environment {
446
446
  static #isCi = Environment.#resolveIsCi();
447
447
  static #noColor = Environment.#resolveNoColor();
448
448
  static #noInteractive = Environment.#resolveNoInteractive();
449
+ static #npmRegistry = Environment.#resolveNpmRegistry();
449
450
  static #storePath = Environment.#resolveStorePath();
450
451
  static #timeout = Environment.#resolveTimeout();
451
452
  static #typescriptPath = Environment.#resolveTypeScriptPath();
@@ -458,6 +459,9 @@ class Environment {
458
459
  static get noInteractive() {
459
460
  return Environment.#noInteractive;
460
461
  }
462
+ static get npmRegistry() {
463
+ return Environment.#npmRegistry;
464
+ }
461
465
  static get storePath() {
462
466
  return Environment.#storePath;
463
467
  }
@@ -488,6 +492,12 @@ class Environment {
488
492
  }
489
493
  return !process.stdout.isTTY;
490
494
  }
495
+ static #resolveNpmRegistry() {
496
+ if (process.env["TSTYCHE_NPM_REGISTRY"] != null) {
497
+ return process.env["TSTYCHE_NPM_REGISTRY"];
498
+ }
499
+ return "https://registry.npmjs.org";
500
+ }
491
501
  static #resolveStorePath() {
492
502
  if (process.env["TSTYCHE_STORE_PATH"] != null) {
493
503
  return Path.resolve(process.env["TSTYCHE_STORE_PATH"]);
@@ -505,7 +515,7 @@ class Environment {
505
515
  }
506
516
  static #resolveTimeout() {
507
517
  if (process.env["TSTYCHE_TIMEOUT"] != null) {
508
- return Number(process.env["TSTYCHE_TIMEOUT"]);
518
+ return Number.parseFloat(process.env["TSTYCHE_TIMEOUT"]);
509
519
  }
510
520
  return 30;
511
521
  }
@@ -589,7 +599,7 @@ class Scribbler {
589
599
  #visitChildren(children) {
590
600
  const text = [];
591
601
  for (const child of children) {
592
- if (typeof child === "string") {
602
+ if (typeof child === "string" || typeof child === "number") {
593
603
  text.push(child);
594
604
  continue;
595
605
  }
@@ -605,8 +615,8 @@ class Scribbler {
605
615
  }
606
616
  }
607
617
 
608
- function addsPackageStepText(compilerVersion, installationPath) {
609
- return (jsx(Line, { children: [jsx(Text, { color: "90", children: "adds" }), " TypeScript ", compilerVersion, jsx(Text, { color: "90", children: [" to ", installationPath] })] }));
618
+ function addsPackageStepText(packageVersion, packagePath) {
619
+ return (jsx(Line, { children: [jsx(Text, { color: "90", children: "adds" }), " TypeScript ", packageVersion, jsx(Text, { color: "90", children: [" to ", packagePath] })] }));
610
620
  }
611
621
 
612
622
  function describeNameText(name, indent = 0) {
@@ -616,61 +626,72 @@ function describeNameText(name, indent = 0) {
616
626
  function BreadcrumbsText({ ancestor }) {
617
627
  const text = [];
618
628
  while ("name" in ancestor) {
619
- text.unshift(" ❭ ", ancestor.name);
629
+ text.push(ancestor.name);
620
630
  ancestor = ancestor.parent;
621
631
  }
622
- return jsx(Text, { color: "90", children: text.join("") });
632
+ text.push("");
633
+ return jsx(Text, { color: "90", children: text.reverse().join(" ❭ ") });
623
634
  }
624
- function CodeLineText({ lineText, gutterWidth, lineNumberColor = "90", lineNumberText }) {
625
- return (jsx(Line, { children: [jsx(Text, { color: lineNumberColor, children: lineNumberText.padStart(gutterWidth) }), jsx(Text, { color: "90", children: " | " }), lineText] }));
635
+ function CodeLineText({ gutterWidth, lineNumber, lineNumberColor = "90", lineText }) {
636
+ return (jsx(Line, { children: [jsx(Text, { color: lineNumberColor, children: lineNumber.toString().padStart(gutterWidth) }), jsx(Text, { color: "90", children: " | " }), lineText] }));
626
637
  }
627
- function SquiggleLineText({ gutterWidth, indentWidth = 0, squiggleWidth }) {
628
- return (jsx(Line, { children: [" ".repeat(gutterWidth), jsx(Text, { color: "90", children: " | " }), " ".repeat(indentWidth), jsx(Text, { color: "31", children: "~".repeat(squiggleWidth === 0 ? 1 : squiggleWidth) })] }));
638
+ function SquiggleLineText({ gutterWidth, indentWidth = 0, squiggleColor, squiggleWidth }) {
639
+ return (jsx(Line, { children: [" ".repeat(gutterWidth), jsx(Text, { color: "90", children: " | " }), " ".repeat(indentWidth), jsx(Text, { color: squiggleColor, children: "~".repeat(squiggleWidth === 0 ? 1 : squiggleWidth) })] }));
629
640
  }
630
- function CodeSpanText({ diagnosticOrigin }) {
641
+ function CodeSpanText({ diagnosticCategory, diagnosticOrigin }) {
631
642
  const lastLineInFile = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.sourceFile.text.length).line;
632
643
  const { character: firstMarkedLineCharacter, line: firstMarkedLine } = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.start);
633
644
  const { character: lastMarkedLineCharacter, line: lastMarkedLine } = diagnosticOrigin.sourceFile.getLineAndCharacterOfPosition(diagnosticOrigin.end);
634
645
  const firstLine = Math.max(firstMarkedLine - 2, 0);
635
646
  const lastLine = Math.min(firstLine + 5, lastLineInFile);
636
- const gutterWidth = String(lastLine + 1).length + 2;
647
+ const gutterWidth = (lastLine + 1).toString().length + 2;
648
+ let highlightColor;
649
+ switch (diagnosticCategory) {
650
+ case "error": {
651
+ highlightColor = "31";
652
+ break;
653
+ }
654
+ case "warning": {
655
+ highlightColor = "33";
656
+ break;
657
+ }
658
+ }
637
659
  const codeSpan = [];
638
660
  for (let index = firstLine; index <= lastLine; index++) {
639
661
  const lineStart = diagnosticOrigin.sourceFile.getPositionOfLineAndCharacter(index, 0);
640
662
  const lineEnd = index === lastLineInFile
641
663
  ? diagnosticOrigin.sourceFile.text.length
642
664
  : diagnosticOrigin.sourceFile.getPositionOfLineAndCharacter(index + 1, 0);
643
- const lineNumberText = String(index + 1);
644
665
  const lineText = diagnosticOrigin.sourceFile.text.slice(lineStart, lineEnd).trimEnd().replace(/\t/g, " ");
645
666
  if (index >= firstMarkedLine && index <= lastMarkedLine) {
646
- codeSpan.push(jsx(CodeLineText, { gutterWidth: gutterWidth, lineNumberColor: "31", lineNumberText: lineNumberText, lineText: lineText }));
667
+ codeSpan.push(jsx(CodeLineText, { gutterWidth: gutterWidth, lineNumber: index + 1, lineNumberColor: highlightColor, lineText: lineText }));
647
668
  if (index === firstMarkedLine) {
648
669
  const squiggleLength = index === lastMarkedLine
649
670
  ? lastMarkedLineCharacter - firstMarkedLineCharacter
650
671
  : lineText.length - firstMarkedLineCharacter;
651
- codeSpan.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, indentWidth: firstMarkedLineCharacter, squiggleWidth: squiggleLength }));
672
+ codeSpan.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, indentWidth: firstMarkedLineCharacter, squiggleColor: highlightColor, squiggleWidth: squiggleLength }));
652
673
  }
653
674
  else if (index === lastMarkedLine) {
654
- codeSpan.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, squiggleWidth: lastMarkedLineCharacter }));
675
+ codeSpan.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, squiggleColor: highlightColor, squiggleWidth: lastMarkedLineCharacter }));
655
676
  }
656
677
  else {
657
- codeSpan.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, squiggleWidth: lineText.length }));
678
+ codeSpan.push(jsx(SquiggleLineText, { gutterWidth: gutterWidth, squiggleColor: highlightColor, squiggleWidth: lineText.length }));
658
679
  }
659
680
  }
660
681
  else {
661
- codeSpan.push(jsx(CodeLineText, { gutterWidth: gutterWidth, lineNumberText: lineNumberText, lineText: lineText }));
682
+ codeSpan.push(jsx(CodeLineText, { gutterWidth: gutterWidth, lineNumber: index + 1, lineText: lineText }));
662
683
  }
663
684
  }
664
- const location = (jsx(Line, { children: [" ".repeat(gutterWidth + 2), jsx(Text, { color: "90", children: " at " }), jsx(Text, { color: "36", children: Path.relative("", diagnosticOrigin.sourceFile.fileName) }), jsx(Text, { color: "90", children: [":", String(firstMarkedLine + 1), ":", String(firstMarkedLineCharacter + 1)] }), diagnosticOrigin.assertion && jsx(BreadcrumbsText, { ancestor: diagnosticOrigin.assertion.parent })] }));
685
+ const location = (jsx(Line, { children: [" ".repeat(gutterWidth + 2), jsx(Text, { color: "90", children: " at " }), jsx(Text, { color: "36", children: Path.relative("", diagnosticOrigin.sourceFile.fileName) }), jsx(Text, { color: "90", children: `:${firstMarkedLine + 1}:${firstMarkedLineCharacter + 1}` }), diagnosticOrigin.assertion && jsx(BreadcrumbsText, { ancestor: diagnosticOrigin.assertion.parent })] }));
665
686
  return (jsx(Text, { children: [codeSpan, jsx(Line, {}), location] }));
666
687
  }
667
688
 
668
689
  function DiagnosticText({ diagnostic }) {
669
- const code = typeof diagnostic.code === "string" ? jsx(Text, { color: "90", children: [" ", diagnostic.code] }) : undefined;
690
+ const code = diagnostic.code ? jsx(Text, { color: "90", children: [" ", diagnostic.code] }) : undefined;
670
691
  const text = Array.isArray(diagnostic.text) ? diagnostic.text : [diagnostic.text];
671
692
  const message = text.map((text, index) => (jsx(Text, { children: [index === 1 ? jsx(Line, {}) : undefined, jsx(Line, { children: [text, code] })] })));
672
693
  const related = diagnostic.related?.map((relatedDiagnostic) => jsx(DiagnosticText, { diagnostic: relatedDiagnostic }));
673
- const codeSpan = diagnostic.origin ? (jsx(Text, { children: [jsx(Line, {}), jsx(CodeSpanText, { diagnosticOrigin: diagnostic.origin })] })) : undefined;
694
+ const codeSpan = diagnostic.origin ? (jsx(Text, { children: [jsx(Line, {}), jsx(CodeSpanText, { diagnosticCategory: diagnostic.category, diagnosticOrigin: diagnostic.origin })] })) : undefined;
674
695
  return (jsx(Text, { children: [message, codeSpan, jsx(Line, {}), jsx(Text, { indent: 2, children: related })] }));
675
696
  }
676
697
  function diagnosticText(diagnostic) {
@@ -747,11 +768,7 @@ function HelpHeaderText({ tstycheVersion }) {
747
768
  return (jsx(Line, { children: ["The TSTyche Type Test Runner", jsx(HintText, { children: tstycheVersion })] }));
748
769
  }
749
770
  function CommandText({ hint, text }) {
750
- let hintText;
751
- if (hint != null) {
752
- hintText = jsx(HintText, { children: hint });
753
- }
754
- return (jsx(Line, { indent: 1, children: [jsx(Text, { color: "34", children: text }), hintText] }));
771
+ return (jsx(Line, { indent: 1, children: [jsx(Text, { color: "34", children: text }), hint && jsx(HintText, { children: hint })] }));
755
772
  }
756
773
  function OptionDescriptionText({ text }) {
757
774
  return jsx(Line, { indent: 1, children: text });
@@ -766,11 +783,11 @@ function CommandLineUsageText() {
766
783
  return jsx(Text, { children: usageText });
767
784
  }
768
785
  function CommandLineOptionNameText({ text }) {
769
- return jsx(Text, { children: ["--", text] });
786
+ return jsx(Text, { children: `--${text}` });
770
787
  }
771
788
  function CommandLineOptionHintText({ definition }) {
772
789
  if (definition.brand === "list") {
773
- return (jsx(Text, { children: [definition.brand, " of ", definition.items.brand, "s"] }));
790
+ return jsx(Text, { children: `${definition.brand} of ${definition.items.brand}s` });
774
791
  }
775
792
  return jsx(Text, { children: definition.brand });
776
793
  }
@@ -835,16 +852,16 @@ function RowText({ label, text }) {
835
852
  return (jsx(Line, { children: [`${label}:`.padEnd(12), text] }));
836
853
  }
837
854
  function CountText({ failed, passed, skipped, todo, total }) {
838
- return (jsx(Text, { children: [failed > 0 ? (jsx(Text, { children: [jsx(Text, { color: "31", children: [String(failed), " failed"] }), jsx(Text, { children: ", " })] })) : undefined, skipped > 0 ? (jsx(Text, { children: [jsx(Text, { color: "33", children: [String(skipped), " skipped"] }), jsx(Text, { children: ", " })] })) : undefined, todo > 0 ? (jsx(Text, { children: [jsx(Text, { color: "35", children: [String(todo), " todo"] }), jsx(Text, { children: ", " })] })) : undefined, passed > 0 ? (jsx(Text, { children: [jsx(Text, { color: "32", children: [String(passed), " passed"] }), jsx(Text, { children: ", " })] })) : undefined, jsx(Text, { children: [String(total), jsx(Text, { children: " total" })] })] }));
855
+ return (jsx(Text, { children: [failed > 0 ? (jsx(Text, { children: [jsx(Text, { color: "31", children: [failed, " failed"] }), jsx(Text, { children: ", " })] })) : undefined, skipped > 0 ? (jsx(Text, { children: [jsx(Text, { color: "33", children: [skipped, " skipped"] }), jsx(Text, { children: ", " })] })) : undefined, todo > 0 ? (jsx(Text, { children: [jsx(Text, { color: "35", children: [todo, " todo"] }), jsx(Text, { children: ", " })] })) : undefined, passed > 0 ? (jsx(Text, { children: [jsx(Text, { color: "32", children: [passed, " passed"] }), jsx(Text, { children: ", " })] })) : undefined, jsx(Text, { children: [total, " total"] })] }));
839
856
  }
840
857
  function DurationText({ seconds }) {
841
- return jsx(Text, { children: `${String(Math.round(seconds * 10) / 10)}s` });
858
+ return jsx(Text, { children: `${Math.round(seconds * 10) / 10}s` });
842
859
  }
843
860
  function MatchText({ text }) {
844
861
  if (typeof text === "string") {
845
862
  return jsx(Text, { children: ["'", text, "'"] });
846
863
  }
847
- if (text.length <= 1) {
864
+ if (text.length === 1) {
848
865
  return jsx(Text, { children: ["'", ...text, "'"] });
849
866
  }
850
867
  const lastItem = text.pop();
@@ -856,7 +873,7 @@ function RanFilesText({ onlyMatch, pathMatch, skipMatch }) {
856
873
  testNameMatchText.push(jsx(Text, { children: [jsx(Text, { color: "90", children: "matching " }), jsx(MatchText, { text: onlyMatch })] }));
857
874
  }
858
875
  if (skipMatch != null) {
859
- testNameMatchText.push(jsx(Text, { children: [onlyMatch != null ? jsx(Text, { color: "90", children: " and " }) : undefined, jsx(Text, { color: "90", children: "not matching " }), jsx(MatchText, { text: skipMatch })] }));
876
+ testNameMatchText.push(jsx(Text, { children: [onlyMatch && jsx(Text, { color: "90", children: " and " }), jsx(Text, { color: "90", children: "not matching " }), jsx(MatchText, { text: skipMatch })] }));
860
877
  }
861
878
  let pathMatchText;
862
879
  if (pathMatch.length > 0) {
@@ -987,7 +1004,7 @@ class RunReporter extends Reporter {
987
1004
  break;
988
1005
  }
989
1006
  case "store:info": {
990
- this.outputService.writeMessage(addsPackageStepText(payload.compilerVersion, payload.installationPath));
1007
+ this.outputService.writeMessage(addsPackageStepText(payload.packageVersion, payload.packagePath));
991
1008
  this.#hasReportedAdds = true;
992
1009
  break;
993
1010
  }
@@ -1111,7 +1128,7 @@ class RunReporter extends Reporter {
1111
1128
  class SetupReporter extends Reporter {
1112
1129
  handleEvent([eventName, payload]) {
1113
1130
  if (eventName === "store:info") {
1114
- this.outputService.writeMessage(addsPackageStepText(payload.compilerVersion, payload.installationPath));
1131
+ this.outputService.writeMessage(addsPackageStepText(payload.packageVersion, payload.packagePath));
1115
1132
  return;
1116
1133
  }
1117
1134
  if ("diagnostics" in payload) {
@@ -1297,7 +1314,7 @@ class Diagnostic {
1297
1314
  }
1298
1315
  static fromDiagnostics(diagnostics, compiler) {
1299
1316
  return diagnostics.map((diagnostic) => {
1300
- const code = `ts(${String(diagnostic.code)})`;
1317
+ const code = `ts(${diagnostic.code})`;
1301
1318
  let origin;
1302
1319
  if (Diagnostic.#isTsDiagnosticWithLocation(diagnostic)) {
1303
1320
  origin = new DiagnosticOrigin(diagnostic.start, diagnostic.start + diagnostic.length, diagnostic.file);
@@ -1310,17 +1327,6 @@ class Diagnostic {
1310
1327
  return new Diagnostic(text, "error", origin).add({ code, related });
1311
1328
  });
1312
1329
  }
1313
- static fromError(text, error) {
1314
- const messageText = Array.isArray(text) ? text : [text];
1315
- if (error instanceof Error && error.stack != null) {
1316
- if (messageText.length > 1) {
1317
- messageText.push("");
1318
- }
1319
- const stackLines = error.stack.split("\n").map((line) => line.trimStart());
1320
- messageText.push(...stackLines);
1321
- }
1322
- return Diagnostic.error(messageText);
1323
- }
1324
1330
  static #isTsDiagnosticWithLocation(diagnostic) {
1325
1331
  return diagnostic.file != null && diagnostic.start != null && diagnostic.length != null;
1326
1332
  }
@@ -1446,10 +1452,21 @@ class SelectService {
1446
1452
  #onDiagnostics(diagnostic) {
1447
1453
  EventEmitter.dispatch(["select:error", { diagnostics: [diagnostic] }]);
1448
1454
  }
1455
+ async #resolveEntryMeta(entry, targetPath) {
1456
+ if (!entry.isSymbolicLink()) {
1457
+ return entry;
1458
+ }
1459
+ let entryMeta;
1460
+ try {
1461
+ entryMeta = await fs.stat([targetPath, entry.name].join("/"));
1462
+ }
1463
+ catch {
1464
+ }
1465
+ return entryMeta;
1466
+ }
1449
1467
  async selectFiles() {
1450
- const currentPath = ".";
1451
1468
  const testFilePaths = [];
1452
- await this.#visitDirectory(currentPath, testFilePaths);
1469
+ await this.#visitDirectory(".", testFilePaths);
1453
1470
  if (testFilePaths.length === 0) {
1454
1471
  this.#onDiagnostics(Diagnostic.error(SelectDiagnosticText.noTestFilesWereSelected(this.#resolvedConfig)));
1455
1472
  }
@@ -1457,38 +1474,20 @@ class SelectService {
1457
1474
  }
1458
1475
  async #visitDirectory(currentPath, testFilePaths) {
1459
1476
  const targetPath = Path.join(this.#resolvedConfig.rootPath, currentPath);
1460
- let entries;
1461
1477
  try {
1462
- entries = await fs.readdir(targetPath, { withFileTypes: true });
1463
- }
1464
- catch {
1465
- }
1466
- if (!entries) {
1467
- return;
1468
- }
1469
- for (const entry of entries) {
1470
- let entryMeta;
1471
- if (entry.isSymbolicLink()) {
1472
- try {
1473
- entryMeta = await fs.stat([targetPath, entry.name].join("/"));
1478
+ const entries = await fs.readdir(targetPath, { withFileTypes: true });
1479
+ for (const entry of entries) {
1480
+ const entryMeta = await this.#resolveEntryMeta(entry, targetPath);
1481
+ const entryPath = [currentPath, entry.name].join("/");
1482
+ if (entryMeta?.isDirectory() && this.#isDirectoryIncluded(entryPath)) {
1483
+ await this.#visitDirectory(entryPath, testFilePaths);
1474
1484
  }
1475
- catch {
1485
+ else if (entryMeta?.isFile() && this.#isFileIncluded(entryPath)) {
1486
+ testFilePaths.push([targetPath, entry.name].join("/"));
1476
1487
  }
1477
1488
  }
1478
- else {
1479
- entryMeta = entry;
1480
- }
1481
- if (!entryMeta) {
1482
- continue;
1483
- }
1484
- const entryPath = [currentPath, entry.name].join("/");
1485
- if (entryMeta.isDirectory() && this.#isDirectoryIncluded(entryPath)) {
1486
- await this.#visitDirectory(entryPath, testFilePaths);
1487
- continue;
1488
- }
1489
- if (entryMeta.isFile() && this.#isFileIncluded(entryPath)) {
1490
- testFilePaths.push([targetPath, entry.name].join("/"));
1491
- }
1489
+ }
1490
+ catch {
1492
1491
  }
1493
1492
  }
1494
1493
  }
@@ -1888,6 +1887,132 @@ var TestMemberFlags;
1888
1887
  TestMemberFlags[TestMemberFlags["Todo"] = 8] = "Todo";
1889
1888
  })(TestMemberFlags || (TestMemberFlags = {}));
1890
1889
 
1890
+ class Version {
1891
+ static isGreaterThan(source, target) {
1892
+ return !(source === target) && Version.#satisfies(source, target);
1893
+ }
1894
+ static isSatisfiedWith(source, target) {
1895
+ return source === target || Version.#satisfies(source, target);
1896
+ }
1897
+ static isVersionTag(target) {
1898
+ return /^\d+/.test(target);
1899
+ }
1900
+ static #satisfies(source, target) {
1901
+ const sourceElements = source.split(/\.|-/);
1902
+ const targetElements = target.split(/\.|-/);
1903
+ function compare(index = 0) {
1904
+ const sourceElement = sourceElements[index];
1905
+ const targetElement = targetElements[index];
1906
+ if (sourceElement > targetElement) {
1907
+ return true;
1908
+ }
1909
+ if (sourceElement < targetElement) {
1910
+ return false;
1911
+ }
1912
+ if (index === sourceElements.length - 1 || index === targetElements.length - 1) {
1913
+ return true;
1914
+ }
1915
+ return compare(index + 1);
1916
+ }
1917
+ return compare();
1918
+ }
1919
+ }
1920
+
1921
+ class ProjectService {
1922
+ #compiler;
1923
+ #service;
1924
+ constructor(compiler) {
1925
+ this.#compiler = compiler;
1926
+ function doNothing() {
1927
+ }
1928
+ function returnFalse() {
1929
+ return false;
1930
+ }
1931
+ function returnUndefined() {
1932
+ return undefined;
1933
+ }
1934
+ const noopWatcher = { close: doNothing };
1935
+ const noopLogger = {
1936
+ close: doNothing,
1937
+ endGroup: doNothing,
1938
+ getLogFileName: returnUndefined,
1939
+ hasLevel: returnFalse,
1940
+ info: doNothing,
1941
+ loggingEnabled: returnFalse,
1942
+ msg: doNothing,
1943
+ perftrc: doNothing,
1944
+ startGroup: doNothing,
1945
+ };
1946
+ const host = {
1947
+ ...this.#compiler.sys,
1948
+ clearImmediate,
1949
+ clearTimeout,
1950
+ setImmediate,
1951
+ setTimeout,
1952
+ watchDirectory: () => noopWatcher,
1953
+ watchFile: () => noopWatcher,
1954
+ };
1955
+ this.#service = new this.#compiler.server.ProjectService({
1956
+ allowLocalPluginLoads: true,
1957
+ cancellationToken: this.#compiler.server.nullCancellationToken,
1958
+ host,
1959
+ logger: noopLogger,
1960
+ session: undefined,
1961
+ useInferredProjectPerProjectRoot: true,
1962
+ useSingleInferredProject: false,
1963
+ });
1964
+ this.#service.setCompilerOptionsForInferredProjects(this.#getDefaultCompilerOptions());
1965
+ }
1966
+ closeFile(filePath) {
1967
+ this.#service.closeClientFile(filePath);
1968
+ }
1969
+ #getDefaultCompilerOptions() {
1970
+ const defaultCompilerOptions = {
1971
+ allowJs: true,
1972
+ checkJs: true,
1973
+ esModuleInterop: true,
1974
+ jsx: "preserve",
1975
+ module: "esnext",
1976
+ moduleResolution: "node",
1977
+ resolveJsonModule: true,
1978
+ strictFunctionTypes: true,
1979
+ strictNullChecks: true,
1980
+ target: "esnext",
1981
+ };
1982
+ if (Version.isSatisfiedWith(this.#compiler.version, "5.4")) {
1983
+ defaultCompilerOptions.module = "preserve";
1984
+ }
1985
+ if (Version.isSatisfiedWith(this.#compiler.version, "5.0")) {
1986
+ defaultCompilerOptions.allowImportingTsExtensions = true;
1987
+ defaultCompilerOptions.moduleResolution = "bundler";
1988
+ }
1989
+ return defaultCompilerOptions;
1990
+ }
1991
+ getDefaultProject(filePath) {
1992
+ return this.#service.getDefaultProjectForFile(this.#compiler.server.toNormalizedPath(filePath), true);
1993
+ }
1994
+ getLanguageService(filePath) {
1995
+ const project = this.getDefaultProject(filePath);
1996
+ if (!project) {
1997
+ return;
1998
+ }
1999
+ return project.getLanguageService(true);
2000
+ }
2001
+ openFile(filePath, sourceText, projectRootPath) {
2002
+ const { configFileErrors, configFileName } = this.#service.openClientFile(filePath, sourceText, undefined, projectRootPath);
2003
+ EventEmitter.dispatch([
2004
+ "project:info",
2005
+ { compilerVersion: this.#compiler.version, projectConfigFilePath: configFileName },
2006
+ ]);
2007
+ if (configFileErrors && configFileErrors.length > 0) {
2008
+ EventEmitter.dispatch([
2009
+ "project:error",
2010
+ { diagnostics: Diagnostic.fromDiagnostics(configFileErrors, this.#compiler) },
2011
+ ]);
2012
+ }
2013
+ }
2014
+ }
2015
+
1891
2016
  class ExpectDiagnosticText {
1892
2017
  static argumentOrTypeArgumentMustBeProvided(argumentNameText, typeArgumentNameText) {
1893
2018
  return `An argument for '${argumentNameText}' or type argument for '${typeArgumentNameText}' must be provided.`;
@@ -1904,17 +2029,11 @@ class ExpectDiagnosticText {
1904
2029
  static componentDoesNotAcceptProps(isTypeNode) {
1905
2030
  return `${isTypeNode ? "Component type" : "Component"} does not accept props of the given type.`;
1906
2031
  }
1907
- static matcherIsDeprecated(matcherNameText) {
1908
- return [
1909
- `The '.${matcherNameText}()' matcher is deprecated and will be removed in TSTyche 3.`,
1910
- "To learn more, visit https://tstyche.org/releases/tstyche-2",
1911
- ];
1912
- }
1913
2032
  static matcherIsNotSupported(matcherNameText) {
1914
2033
  return `The '.${matcherNameText}()' matcher is not supported.`;
1915
2034
  }
1916
- static overloadGaveTheFollowingError(indexText, countText, signatureText) {
1917
- return `Overload ${indexText} of ${countText}, '${signatureText}', gave the following error.`;
2035
+ static overloadGaveTheFollowingError(index, count, signatureText) {
2036
+ return `Overload ${index} of ${count}, '${signatureText}', gave the following error.`;
1918
2037
  }
1919
2038
  static raisedTypeError(count = 1) {
1920
2039
  return `The raised type error${count === 1 ? "" : "s"}:`;
@@ -1967,7 +2086,7 @@ class ExpectDiagnosticText {
1967
2086
  static typeRaisedError(isTypeNode, count, targetCount) {
1968
2087
  let countText = "a";
1969
2088
  if (count > 1 || targetCount > 1) {
1970
- countText = count > targetCount ? String(count) : `only ${String(count)}`;
2089
+ countText = count > targetCount ? `${count}` : `only ${count}`;
1971
2090
  }
1972
2091
  return `${isTypeNode ? "Type" : "Expression type"} raised ${countText} type error${count === 1 ? "" : "s"}.`;
1973
2092
  }
@@ -2058,19 +2177,19 @@ class MatchWorker {
2058
2177
  return type;
2059
2178
  }
2060
2179
  isAnyOrNeverType(type) {
2061
- return Boolean(type.flags & (this.#compiler.TypeFlags.Any | this.#compiler.TypeFlags.Never));
2180
+ return !!(type.flags & (this.#compiler.TypeFlags.Any | this.#compiler.TypeFlags.Never));
2062
2181
  }
2063
2182
  isStringOrNumberLiteralType(type) {
2064
- return Boolean(type.flags & this.#compiler.TypeFlags.StringOrNumberLiteral);
2183
+ return !!(type.flags & this.#compiler.TypeFlags.StringOrNumberLiteral);
2065
2184
  }
2066
2185
  isObjectType(type) {
2067
- return Boolean(type.flags & this.#compiler.TypeFlags.Object);
2186
+ return !!(type.flags & this.#compiler.TypeFlags.Object);
2068
2187
  }
2069
2188
  isUnionType(type) {
2070
- return Boolean(type.flags & this.#compiler.TypeFlags.Union);
2189
+ return !!(type.flags & this.#compiler.TypeFlags.Union);
2071
2190
  }
2072
2191
  isUniqueSymbolType(type) {
2073
- return Boolean(type.flags & this.#compiler.TypeFlags.UniqueESSymbol);
2192
+ return !!(type.flags & this.#compiler.TypeFlags.UniqueESSymbol);
2074
2193
  }
2075
2194
  resolveDiagnosticOrigin(symbol, enclosingNode) {
2076
2195
  if (symbol.valueDeclaration != null &&
@@ -2097,7 +2216,7 @@ class PrimitiveTypeMatcher {
2097
2216
  }
2098
2217
  match(matchWorker, sourceNode) {
2099
2218
  const sourceType = matchWorker.getType(sourceNode);
2100
- const isMatch = Boolean(sourceType.flags & this.#targetTypeFlag);
2219
+ const isMatch = !!(sourceType.flags & this.#targetTypeFlag);
2101
2220
  return {
2102
2221
  explain: () => this.#explain(matchWorker, sourceNode),
2103
2222
  isMatch,
@@ -2122,7 +2241,7 @@ class ToAcceptProps {
2122
2241
  const origin = DiagnosticOrigin.fromNode(targetNode, matchWorker.assertion);
2123
2242
  if (signatures.length > 1) {
2124
2243
  const signatureText = this.#typeChecker.signatureToString(signature, sourceNode);
2125
- const overloadText = ExpectDiagnosticText.overloadGaveTheFollowingError(String(index + 1), String(signatures.length), signatureText);
2244
+ const overloadText = ExpectDiagnosticText.overloadGaveTheFollowingError(index + 1, signatures.length, signatureText);
2126
2245
  diagnostic = Diagnostic.error([introText, overloadText], origin);
2127
2246
  }
2128
2247
  else {
@@ -2343,7 +2462,7 @@ class ToHaveProperty {
2343
2462
  const targetType = matchWorker.getType(targetNode);
2344
2463
  let propertyNameText;
2345
2464
  if (matchWorker.isStringOrNumberLiteralType(targetType)) {
2346
- propertyNameText = String(targetType.value);
2465
+ propertyNameText = targetType.value.toString();
2347
2466
  }
2348
2467
  else {
2349
2468
  propertyNameText = `[${this.#compiler.unescapeLeadingUnderscores(targetType.symbol.escapedName)}]`;
@@ -2367,7 +2486,7 @@ class ToHaveProperty {
2367
2486
  const targetType = matchWorker.getType(targetNode);
2368
2487
  let propertyNameText;
2369
2488
  if (matchWorker.isStringOrNumberLiteralType(targetType)) {
2370
- propertyNameText = String(targetType.value);
2489
+ propertyNameText = targetType.value.toString();
2371
2490
  }
2372
2491
  else if (matchWorker.isUniqueSymbolType(targetType)) {
2373
2492
  propertyNameText = this.#compiler.unescapeLeadingUnderscores(targetType.escapedName);
@@ -2473,7 +2592,7 @@ class ToRaiseError {
2473
2592
  if (this.#compiler.isStringLiteralLike(targetNode)) {
2474
2593
  return this.#compiler.flattenDiagnosticMessageText(diagnostic.messageText, " ", 0).includes(targetNode.text);
2475
2594
  }
2476
- return Number(targetNode.text) === diagnostic.code;
2595
+ return Number.parseInt(targetNode.text) === diagnostic.code;
2477
2596
  }
2478
2597
  }
2479
2598
 
@@ -2483,7 +2602,6 @@ class ExpectService {
2483
2602
  toAcceptProps;
2484
2603
  toBe;
2485
2604
  toBeAny;
2486
- toBeAssignable;
2487
2605
  toBeAssignableTo;
2488
2606
  toBeAssignableWith;
2489
2607
  toBeBigInt;
@@ -2497,7 +2615,6 @@ class ExpectService {
2497
2615
  toBeUniqueSymbol;
2498
2616
  toBeUnknown;
2499
2617
  toBeVoid;
2500
- toEqual;
2501
2618
  toHaveProperty;
2502
2619
  toMatch;
2503
2620
  toRaiseError;
@@ -2507,7 +2624,6 @@ class ExpectService {
2507
2624
  this.toAcceptProps = new ToAcceptProps(compiler, typeChecker);
2508
2625
  this.toBe = new ToBe();
2509
2626
  this.toBeAny = new PrimitiveTypeMatcher(compiler.TypeFlags.Any);
2510
- this.toBeAssignable = new ToBeAssignableWith();
2511
2627
  this.toBeAssignableTo = new ToBeAssignableTo();
2512
2628
  this.toBeAssignableWith = new ToBeAssignableWith();
2513
2629
  this.toBeBigInt = new PrimitiveTypeMatcher(compiler.TypeFlags.BigInt);
@@ -2521,28 +2637,12 @@ class ExpectService {
2521
2637
  this.toBeUniqueSymbol = new PrimitiveTypeMatcher(compiler.TypeFlags.UniqueESSymbol);
2522
2638
  this.toBeUnknown = new PrimitiveTypeMatcher(compiler.TypeFlags.Unknown);
2523
2639
  this.toBeVoid = new PrimitiveTypeMatcher(compiler.TypeFlags.Void);
2524
- this.toEqual = new ToBe();
2525
2640
  this.toHaveProperty = new ToHaveProperty(compiler);
2526
2641
  this.toMatch = new ToMatch();
2527
2642
  this.toRaiseError = new ToRaiseError(compiler);
2528
2643
  }
2529
- static assertTypeChecker(typeChecker) {
2530
- return "isTypeRelatedTo" in typeChecker && "relation" in typeChecker;
2531
- }
2532
- #handleDeprecated(matcherNameText, assertion) {
2533
- switch (matcherNameText) {
2534
- case "toBeAssignable":
2535
- case "toEqual": {
2536
- const text = ExpectDiagnosticText.matcherIsDeprecated(matcherNameText);
2537
- const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
2538
- EventEmitter.dispatch(["deprecation:info", { diagnostics: [Diagnostic.warning(text, origin)] }]);
2539
- break;
2540
- }
2541
- }
2542
- }
2543
2644
  match(assertion, onDiagnostics) {
2544
2645
  const matcherNameText = assertion.matcherName.getText();
2545
- this.#handleDeprecated(matcherNameText, assertion);
2546
2646
  if (!assertion.source[0]) {
2547
2647
  this.#onSourceArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics);
2548
2648
  return;
@@ -2551,10 +2651,8 @@ class ExpectService {
2551
2651
  switch (matcherNameText) {
2552
2652
  case "toAcceptProps":
2553
2653
  case "toBe":
2554
- case "toBeAssignable":
2555
2654
  case "toBeAssignableTo":
2556
2655
  case "toBeAssignableWith":
2557
- case "toEqual":
2558
2656
  case "toMatch": {
2559
2657
  if (!assertion.target[0]) {
2560
2658
  this.#onTargetArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics);
@@ -2611,132 +2709,6 @@ class ExpectService {
2611
2709
  }
2612
2710
  }
2613
2711
 
2614
- class Version {
2615
- static isGreaterThan(source, target) {
2616
- return !(source === target) && Version.#satisfies(source, target);
2617
- }
2618
- static isSatisfiedWith(source, target) {
2619
- return source === target || Version.#satisfies(source, target);
2620
- }
2621
- static isVersionTag(target) {
2622
- return /^\d+/.test(target);
2623
- }
2624
- static #satisfies(source, target) {
2625
- const sourceElements = source.split(/\.|-/);
2626
- const targetElements = target.split(/\.|-/);
2627
- function compare(index = 0) {
2628
- const sourceElement = sourceElements[index];
2629
- const targetElement = targetElements[index];
2630
- if (sourceElement > targetElement) {
2631
- return true;
2632
- }
2633
- if (sourceElement < targetElement) {
2634
- return false;
2635
- }
2636
- if (index === sourceElements.length - 1 || index === targetElements.length - 1) {
2637
- return true;
2638
- }
2639
- return compare(index + 1);
2640
- }
2641
- return compare();
2642
- }
2643
- }
2644
-
2645
- class ProjectService {
2646
- #compiler;
2647
- #service;
2648
- constructor(compiler) {
2649
- this.#compiler = compiler;
2650
- function doNothing() {
2651
- }
2652
- function returnFalse() {
2653
- return false;
2654
- }
2655
- function returnUndefined() {
2656
- return undefined;
2657
- }
2658
- const noopWatcher = { close: doNothing };
2659
- const noopLogger = {
2660
- close: doNothing,
2661
- endGroup: doNothing,
2662
- getLogFileName: returnUndefined,
2663
- hasLevel: returnFalse,
2664
- info: doNothing,
2665
- loggingEnabled: returnFalse,
2666
- msg: doNothing,
2667
- perftrc: doNothing,
2668
- startGroup: doNothing,
2669
- };
2670
- const host = {
2671
- ...this.#compiler.sys,
2672
- clearImmediate,
2673
- clearTimeout,
2674
- setImmediate,
2675
- setTimeout,
2676
- watchDirectory: () => noopWatcher,
2677
- watchFile: () => noopWatcher,
2678
- };
2679
- this.#service = new this.#compiler.server.ProjectService({
2680
- allowLocalPluginLoads: true,
2681
- cancellationToken: this.#compiler.server.nullCancellationToken,
2682
- host,
2683
- logger: noopLogger,
2684
- session: undefined,
2685
- useInferredProjectPerProjectRoot: true,
2686
- useSingleInferredProject: false,
2687
- });
2688
- this.#service.setCompilerOptionsForInferredProjects(this.#getDefaultCompilerOptions());
2689
- }
2690
- closeFile(filePath) {
2691
- this.#service.closeClientFile(filePath);
2692
- }
2693
- #getDefaultCompilerOptions() {
2694
- const defaultCompilerOptions = {
2695
- allowJs: true,
2696
- checkJs: true,
2697
- esModuleInterop: true,
2698
- jsx: "preserve",
2699
- module: "esnext",
2700
- moduleResolution: "node",
2701
- resolveJsonModule: true,
2702
- strictFunctionTypes: true,
2703
- strictNullChecks: true,
2704
- target: "esnext",
2705
- };
2706
- if (Version.isSatisfiedWith(this.#compiler.version, "5.4")) {
2707
- defaultCompilerOptions.module = "preserve";
2708
- }
2709
- if (Version.isSatisfiedWith(this.#compiler.version, "5.0")) {
2710
- defaultCompilerOptions.allowImportingTsExtensions = true;
2711
- defaultCompilerOptions.moduleResolution = "bundler";
2712
- }
2713
- return defaultCompilerOptions;
2714
- }
2715
- getDefaultProject(filePath) {
2716
- return this.#service.getDefaultProjectForFile(this.#compiler.server.toNormalizedPath(filePath), true);
2717
- }
2718
- getLanguageService(filePath) {
2719
- const project = this.getDefaultProject(filePath);
2720
- if (!project) {
2721
- return;
2722
- }
2723
- return project.getLanguageService(true);
2724
- }
2725
- openFile(filePath, sourceText, projectRootPath) {
2726
- const { configFileErrors, configFileName } = this.#service.openClientFile(filePath, sourceText, undefined, projectRootPath);
2727
- EventEmitter.dispatch([
2728
- "project:info",
2729
- { compilerVersion: this.#compiler.version, projectConfigFilePath: configFileName },
2730
- ]);
2731
- if (configFileErrors && configFileErrors.length > 0) {
2732
- EventEmitter.dispatch([
2733
- "project:error",
2734
- { diagnostics: Diagnostic.fromDiagnostics(configFileErrors, this.#compiler) },
2735
- ]);
2736
- }
2737
- }
2738
- }
2739
-
2740
2712
  class TestTreeWorker {
2741
2713
  #compiler;
2742
2714
  #cancellationToken;
@@ -2951,11 +2923,6 @@ class TestFileRunner {
2951
2923
  return;
2952
2924
  }
2953
2925
  const typeChecker = program.getTypeChecker();
2954
- if (!ExpectService.assertTypeChecker(typeChecker)) {
2955
- const text = "The required 'isTypeRelatedTo()' method is missing in the provided type checker.";
2956
- EventEmitter.dispatch(["file:error", { diagnostics: [Diagnostic.error(text)], result: fileResult }]);
2957
- return;
2958
- }
2959
2926
  const testTreeWorker = new TestTreeWorker(this.#resolvedConfig, this.#compiler, typeChecker, {
2960
2927
  cancellationToken,
2961
2928
  fileResult,
@@ -3003,7 +2970,7 @@ class TaskRunner {
3003
2970
  for (const versionTag of this.#resolvedConfig.target) {
3004
2971
  const targetResult = new TargetResult(versionTag, testFiles);
3005
2972
  EventEmitter.dispatch(["target:start", { result: targetResult }]);
3006
- const compiler = await this.#storeService.load(versionTag, cancellationToken);
2973
+ const compiler = await this.#storeService.load(versionTag);
3007
2974
  if (compiler) {
3008
2975
  const testFileRunner = new TestFileRunner(this.#resolvedConfig, compiler);
3009
2976
  for (const testFile of testFiles) {
@@ -3032,7 +2999,7 @@ class TSTyche {
3032
2999
  #selectService;
3033
3000
  #storeService;
3034
3001
  #taskRunner;
3035
- static version = "2.1.0";
3002
+ static version = "3.0.0-beta.0";
3036
3003
  constructor(resolvedConfig, outputService, selectService, storeService) {
3037
3004
  this.#resolvedConfig = resolvedConfig;
3038
3005
  this.#outputService = outputService;
@@ -3612,32 +3579,124 @@ var OptionGroup;
3612
3579
  })(OptionGroup || (OptionGroup = {}));
3613
3580
 
3614
3581
  class StoreDiagnosticText {
3615
- static failedToFetchMetadata(registryUrl) {
3616
- return `Failed to fetch metadata of the 'typescript' package from '${registryUrl.toString()}'.`;
3582
+ static cannotAddTypeScriptPackage(tag) {
3583
+ return `Cannot add the 'typescript' package for the '${tag}' tag.`;
3584
+ }
3585
+ static failedToFetchMetadata(registry) {
3586
+ return `Failed to fetch metadata of the 'typescript' package from '${registry}'.`;
3617
3587
  }
3618
- static failedWithStatusCode(code) {
3619
- return `Request failed with status code ${String(code)}.`;
3588
+ static failedToInstalTypeScript(version) {
3589
+ return `Failed to install 'typescript@${version}'.`;
3590
+ }
3591
+ static failedToUpdateMetadata(registry) {
3592
+ return `Failed to update metadata of the 'typescript' package from '${registry}'.`;
3620
3593
  }
3621
3594
  static maybeNetworkConnectionIssue() {
3622
3595
  return "Might be there is an issue with the registry or the network connection.";
3623
3596
  }
3624
- static setupTimeoutExceeded(timeout) {
3625
- return `Setup timeout of ${String(timeout / 1000)}s was exceeded.`;
3597
+ static maybeOutdatedResolution(tag) {
3598
+ return `The resolution of the '${tag}' tag may be outdated.`;
3599
+ }
3600
+ static requestFailedWithStatusCode(code) {
3601
+ return `The request failed with status code ${code}.`;
3602
+ }
3603
+ static requestTimeoutWasExceeded(timeout) {
3604
+ return `The request timeout of ${timeout / 1000}s was exceeded.`;
3605
+ }
3606
+ static lockWaitTimeoutWasExceeded(timeout) {
3607
+ return `Lock wait timeout of ${timeout / 1000}s was exceeded.`;
3608
+ }
3609
+ }
3610
+
3611
+ class Fetcher {
3612
+ #onDiagnostics;
3613
+ constructor(onDiagnostics) {
3614
+ this.#onDiagnostics = onDiagnostics;
3615
+ }
3616
+ async get(request, timeout, diagnostic, options) {
3617
+ try {
3618
+ const response = await fetch(request, { signal: AbortSignal.timeout(timeout) });
3619
+ if (!response.ok) {
3620
+ !options?.suppressErrors &&
3621
+ this.#onDiagnostics(diagnostic.extendWith(StoreDiagnosticText.requestFailedWithStatusCode(response.status)));
3622
+ return;
3623
+ }
3624
+ return response;
3625
+ }
3626
+ catch (error) {
3627
+ if (error instanceof Error && error.name === "TimeoutError") {
3628
+ !options?.suppressErrors &&
3629
+ this.#onDiagnostics(diagnostic.extendWith(StoreDiagnosticText.requestTimeoutWasExceeded(timeout)));
3630
+ }
3631
+ else {
3632
+ !options?.suppressErrors &&
3633
+ this.#onDiagnostics(diagnostic.extendWith(StoreDiagnosticText.maybeNetworkConnectionIssue()));
3634
+ }
3635
+ }
3636
+ return;
3637
+ }
3638
+ }
3639
+
3640
+ class Lock {
3641
+ #lockFilePath;
3642
+ constructor(lockFilePath) {
3643
+ this.#lockFilePath = lockFilePath;
3644
+ writeFileSync(this.#lockFilePath, "");
3645
+ process.on("exit", () => {
3646
+ this.release();
3647
+ });
3648
+ }
3649
+ release() {
3650
+ rmSync(this.#lockFilePath, { force: true });
3651
+ }
3652
+ }
3653
+
3654
+ class LockService {
3655
+ #onDiagnostics;
3656
+ constructor(onDiagnostics) {
3657
+ this.#onDiagnostics = onDiagnostics;
3658
+ }
3659
+ #getLockFilePath(targetPath) {
3660
+ return `${targetPath}__lock__`;
3661
+ }
3662
+ getLock(targetPath) {
3663
+ const lockFilePath = this.#getLockFilePath(targetPath);
3664
+ return new Lock(lockFilePath);
3665
+ }
3666
+ async isLocked(targetPath, timeout, diagnostic) {
3667
+ const lockFilePath = this.#getLockFilePath(targetPath);
3668
+ let isLocked = existsSync(lockFilePath);
3669
+ if (!isLocked) {
3670
+ return isLocked;
3671
+ }
3672
+ const waitStartTime = Date.now();
3673
+ while (isLocked) {
3674
+ if (Date.now() - waitStartTime > timeout) {
3675
+ this.#onDiagnostics(diagnostic.extendWith(StoreDiagnosticText.lockWaitTimeoutWasExceeded(timeout)));
3676
+ break;
3677
+ }
3678
+ await this.#sleep(1000);
3679
+ isLocked = existsSync(lockFilePath);
3680
+ }
3681
+ return isLocked;
3682
+ }
3683
+ async #sleep(delay) {
3684
+ return new Promise((resolve) => setTimeout(resolve, delay));
3626
3685
  }
3627
3686
  }
3628
3687
 
3629
3688
  class ManifestWorker {
3630
- #manifestFileName = "store-manifest.json";
3689
+ #fetcher;
3631
3690
  #manifestFilePath;
3632
- #onDiagnostics;
3633
- #registryUrl = new URL("https://registry.npmjs.org");
3691
+ #npmRegistry;
3634
3692
  #storePath;
3635
3693
  #timeout = Environment.timeout * 1000;
3636
- #version = "1";
3637
- constructor(storePath, onDiagnostics) {
3694
+ #version = "2";
3695
+ constructor(storePath, npmRegistry, fetcher) {
3638
3696
  this.#storePath = storePath;
3639
- this.#onDiagnostics = onDiagnostics;
3640
- this.#manifestFilePath = Path.join(storePath, this.#manifestFileName);
3697
+ this.#npmRegistry = npmRegistry;
3698
+ this.#fetcher = fetcher;
3699
+ this.#manifestFilePath = Path.join(storePath, "store-manifest.json");
3641
3700
  }
3642
3701
  async #create() {
3643
3702
  const manifest = await this.#load();
@@ -3653,59 +3712,48 @@ class ManifestWorker {
3653
3712
  return false;
3654
3713
  }
3655
3714
  async #load(options) {
3715
+ const diagnostic = Diagnostic.error(StoreDiagnosticText.failedToFetchMetadata(this.#npmRegistry));
3716
+ const request = new Request(new URL("typescript", this.#npmRegistry), {
3717
+ headers: {
3718
+ ["Accept"]: "application/vnd.npm.install-v1+json;q=1.0, application/json;q=0.8, */*",
3719
+ },
3720
+ });
3721
+ const response = await this.#fetcher.get(request, this.#timeout, diagnostic, {
3722
+ suppressErrors: options?.suppressErrors,
3723
+ });
3724
+ if (!response) {
3725
+ return;
3726
+ }
3656
3727
  const manifest = {
3657
3728
  $version: this.#version,
3658
3729
  lastUpdated: Date.now(),
3730
+ npmRegistry: this.#npmRegistry,
3659
3731
  resolutions: {},
3732
+ packages: {},
3660
3733
  versions: [],
3661
3734
  };
3662
- let packageMetadata;
3663
- try {
3664
- const response = await fetch(new URL("typescript", this.#registryUrl), {
3665
- headers: { accept: "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*" },
3666
- signal: AbortSignal.timeout(this.#timeout),
3667
- });
3668
- if (!response.ok) {
3669
- this.#onDiagnostics(Diagnostic.error([
3670
- StoreDiagnosticText.failedToFetchMetadata(this.#registryUrl),
3671
- StoreDiagnosticText.failedWithStatusCode(response.status),
3672
- ]));
3673
- return;
3735
+ const packageMetadata = (await response.json());
3736
+ for (const [tag, meta] of Object.entries(packageMetadata.versions)) {
3737
+ if (/^(4|5)\.\d\.\d$/.test(tag)) {
3738
+ manifest.versions.push(tag);
3739
+ manifest.packages[tag] = { integrity: meta.dist.integrity, tarball: meta.dist.tarball };
3674
3740
  }
3675
- packageMetadata = (await response.json());
3676
3741
  }
3677
- catch (error) {
3678
- if (options?.quite === true) {
3679
- return;
3680
- }
3681
- if (error instanceof Error && error.name === "TimeoutError") {
3682
- this.#onDiagnostics(Diagnostic.error([
3683
- StoreDiagnosticText.failedToFetchMetadata(this.#registryUrl),
3684
- StoreDiagnosticText.setupTimeoutExceeded(this.#timeout),
3685
- ]));
3686
- }
3687
- else {
3688
- this.#onDiagnostics(Diagnostic.error([
3689
- StoreDiagnosticText.failedToFetchMetadata(this.#registryUrl),
3690
- StoreDiagnosticText.maybeNetworkConnectionIssue(),
3691
- ]));
3692
- }
3693
- return;
3694
- }
3695
- manifest.versions = Object.keys(packageMetadata.versions)
3696
- .filter((version) => /^(4|5)\.\d\.\d$/.test(version))
3697
- .sort();
3698
3742
  const minorVersions = [...new Set(manifest.versions.map((version) => version.slice(0, -2)))];
3699
3743
  for (const tag of minorVersions) {
3700
- const resolvedVersion = manifest.versions.filter((version) => version.startsWith(tag)).pop();
3744
+ const resolvedVersion = manifest.versions.findLast((version) => version.startsWith(tag));
3701
3745
  if (resolvedVersion != null) {
3702
3746
  manifest.resolutions[tag] = resolvedVersion;
3703
3747
  }
3704
3748
  }
3705
- for (const tagKey of ["beta", "latest", "next", "rc"]) {
3706
- const distributionTagValue = packageMetadata["dist-tags"][tagKey];
3707
- if (distributionTagValue != null) {
3708
- manifest.resolutions[tagKey] = distributionTagValue;
3749
+ for (const tag of ["beta", "latest", "next", "rc"]) {
3750
+ const version = packageMetadata["dist-tags"][tag];
3751
+ if (version != null) {
3752
+ manifest.resolutions[tag] = version;
3753
+ const meta = packageMetadata.versions[version];
3754
+ if (meta != null) {
3755
+ manifest.packages[version] = { integrity: meta.dist.integrity, tarball: meta.dist.tarball };
3756
+ }
3709
3757
  }
3710
3758
  }
3711
3759
  return manifest;
@@ -3715,28 +3763,18 @@ class ManifestWorker {
3715
3763
  if (!existsSync(this.#manifestFilePath)) {
3716
3764
  return this.#create();
3717
3765
  }
3718
- let manifestText;
3719
- try {
3720
- manifestText = await fs.readFile(this.#manifestFilePath, { encoding: "utf8" });
3721
- }
3722
- catch (error) {
3723
- this.#onDiagnostics(Diagnostic.fromError("Failed to open store manifest.", error));
3724
- }
3725
- if (!manifestText) {
3726
- return;
3727
- }
3766
+ const manifestText = await fs.readFile(this.#manifestFilePath, { encoding: "utf8" });
3728
3767
  try {
3729
3768
  manifest = JSON.parse(manifestText);
3730
3769
  }
3731
3770
  catch {
3732
3771
  }
3733
- if (!manifest || manifest.$version !== this.#version) {
3772
+ if (!manifest || manifest.$version !== this.#version || manifest.npmRegistry !== this.#npmRegistry) {
3734
3773
  await fs.rm(this.#storePath, { force: true, recursive: true });
3735
3774
  return this.#create();
3736
3775
  }
3737
3776
  if (this.isOutdated(manifest) || options?.refresh === true) {
3738
- const quite = options?.refresh !== true;
3739
- const freshManifest = await this.#load({ quite });
3777
+ const freshManifest = await this.#load({ suppressErrors: !options?.refresh });
3740
3778
  if (freshManifest != null) {
3741
3779
  await this.persist(freshManifest);
3742
3780
  return freshManifest;
@@ -3752,136 +3790,106 @@ class ManifestWorker {
3752
3790
  }
3753
3791
  }
3754
3792
 
3755
- class Lock {
3756
- #lockFilePath;
3757
- static #lockSuffix = "__lock__";
3758
- constructor(targetPath) {
3759
- this.#lockFilePath = Lock.#getLockFilePath(targetPath);
3760
- writeFileSync(this.#lockFilePath, "");
3761
- process.on("exit", () => {
3762
- this.release();
3763
- });
3764
- }
3765
- static #getLockFilePath(targetPath) {
3766
- return `${targetPath}${Lock.#lockSuffix}`;
3767
- }
3768
- static async isLocked(targetPath, options) {
3769
- let isLocked = existsSync(Lock.#getLockFilePath(targetPath));
3770
- if (!isLocked) {
3771
- return isLocked;
3772
- }
3773
- if (!options?.timeout) {
3774
- return isLocked;
3775
- }
3776
- const waitStartTime = Date.now();
3777
- while (isLocked) {
3778
- if (options.cancellationToken?.isCancellationRequested === true) {
3793
+ class TarReader {
3794
+ static #textDecoder = new TextDecoder();
3795
+ static async *extract(stream) {
3796
+ const buffer = await streamConsumers.arrayBuffer(stream);
3797
+ let offset = 0;
3798
+ while (offset < buffer.byteLength - 512) {
3799
+ const name = TarReader.#read(buffer, offset, 100);
3800
+ if (name.length === 0) {
3779
3801
  break;
3780
3802
  }
3781
- if (Date.now() - waitStartTime > options.timeout) {
3782
- options.onDiagnostics?.(`Lock wait timeout of ${String(options.timeout / 1000)}s was exceeded.`);
3783
- break;
3803
+ const size = Number.parseInt(TarReader.#read(buffer, offset + 124, 12), 8);
3804
+ const contents = new Uint8Array(buffer, offset + 512, size);
3805
+ yield { name, contents };
3806
+ offset += 512 + 512 * Math.trunc(size / 512);
3807
+ if (size % 512) {
3808
+ offset += 512;
3784
3809
  }
3785
- await Lock.#sleep(1000);
3786
- isLocked = existsSync(Lock.#getLockFilePath(targetPath));
3787
3810
  }
3788
- return isLocked;
3789
3811
  }
3790
- release() {
3791
- rmSync(this.#lockFilePath, { force: true });
3792
- }
3793
- static async #sleep(time) {
3794
- return new Promise((resolve) => setTimeout(resolve, time));
3812
+ static #read(buffer, byteOffset, length) {
3813
+ let view = new Uint8Array(buffer, byteOffset, length);
3814
+ const zeroIndex = view.indexOf(0);
3815
+ if (zeroIndex !== -1) {
3816
+ view = view.subarray(0, zeroIndex);
3817
+ }
3818
+ return TarReader.#textDecoder.decode(view);
3795
3819
  }
3796
3820
  }
3797
3821
 
3798
- class PackageInstaller {
3799
- #onDiagnostics;
3800
- #readyFileName = "__ready__";
3822
+ class PackageService {
3823
+ #fetcher;
3824
+ #lockService;
3801
3825
  #storePath;
3802
3826
  #timeout = Environment.timeout * 1000;
3803
- constructor(storePath, onDiagnostics) {
3827
+ constructor(storePath, fetcher, lockService) {
3804
3828
  this.#storePath = storePath;
3805
- this.#onDiagnostics = onDiagnostics;
3829
+ this.#fetcher = fetcher;
3830
+ this.#lockService = lockService;
3806
3831
  }
3807
- async ensure(compilerVersion, cancellationToken) {
3808
- const installationPath = Path.join(this.#storePath, compilerVersion);
3809
- const readyFilePath = Path.join(installationPath, this.#readyFileName);
3810
- const modulePath = Path.join(installationPath, "node_modules", "typescript", "lib", "typescript.js");
3832
+ async ensure(packageVersion, manifest) {
3833
+ const packagePath = Path.join(this.#storePath, `typescript@${packageVersion}`);
3834
+ const readyFilePath = Path.join(packagePath, "__ready__");
3835
+ const modulePath = Path.join(packagePath, "lib", "typescript.js");
3811
3836
  if (existsSync(readyFilePath)) {
3812
3837
  return modulePath;
3813
3838
  }
3814
- if (await Lock.isLocked(installationPath, {
3815
- cancellationToken,
3816
- onDiagnostics: (text) => {
3817
- this.#onDiagnostics(Diagnostic.error([`Failed to install 'typescript@${compilerVersion}'.`, text]));
3818
- },
3819
- timeout: this.#timeout,
3820
- })) {
3839
+ const diagnostic = Diagnostic.error(StoreDiagnosticText.failedToInstalTypeScript(packageVersion));
3840
+ if (await this.#lockService.isLocked(packagePath, this.#timeout, diagnostic)) {
3821
3841
  return;
3822
3842
  }
3823
- const lock = new Lock(installationPath);
3824
- EventEmitter.dispatch(["store:info", { compilerVersion, installationPath }]);
3825
- try {
3826
- await fs.mkdir(installationPath, { recursive: true });
3827
- const packageJson = {
3828
- name: "tstyche-typescript",
3829
- version: compilerVersion,
3830
- description: "Do not change. This package was generated by TSTyche",
3831
- private: true,
3832
- license: "MIT",
3833
- dependencies: {
3834
- typescript: compilerVersion,
3835
- },
3836
- };
3837
- await fs.writeFile(Path.join(installationPath, "package.json"), JSON.stringify(packageJson, null, 2));
3838
- await this.#install(installationPath);
3839
- await fs.writeFile(readyFilePath, "");
3843
+ EventEmitter.dispatch(["store:info", { packagePath, packageVersion }]);
3844
+ const resource = manifest.packages[packageVersion];
3845
+ if (resource != null) {
3846
+ const lock = this.#lockService.getLock(packagePath);
3847
+ try {
3848
+ await this.#add(packagePath, resource, diagnostic);
3849
+ await fs.writeFile(readyFilePath, "");
3850
+ }
3851
+ finally {
3852
+ lock.release();
3853
+ }
3840
3854
  return modulePath;
3841
3855
  }
3842
- catch (error) {
3843
- this.#onDiagnostics(Diagnostic.fromError(`Failed to install 'typescript@${compilerVersion}'.`, error));
3844
- }
3845
- finally {
3846
- lock.release();
3847
- }
3848
3856
  return;
3849
3857
  }
3850
- async #install(cwd) {
3851
- const args = ["install", "--ignore-scripts", "--no-bin-links", "--no-package-lock"];
3852
- return new Promise((resolve, reject) => {
3853
- const spawnedNpm = spawn("npm", args, {
3854
- cwd,
3855
- shell: true,
3856
- stdio: "ignore",
3857
- timeout: this.#timeout,
3858
- });
3859
- spawnedNpm.on("error", (error) => {
3860
- reject(error);
3861
- });
3862
- spawnedNpm.on("close", (code, signal) => {
3863
- if (code === 0) {
3864
- resolve();
3858
+ async #add(targetPath, resource, diagnostic) {
3859
+ const request = new Request(resource.tarball, { integrity: resource.integrity });
3860
+ const response = await this.#fetcher.get(request, this.#timeout, diagnostic);
3861
+ if (response?.body != null) {
3862
+ const decompressedStream = response.body.pipeThrough(new DecompressionStream("gzip"));
3863
+ for await (const file of TarReader.extract(decompressedStream)) {
3864
+ if (!file.name.startsWith("package/")) {
3865
+ continue;
3865
3866
  }
3866
- if (signal != null) {
3867
- reject(new Error(`setup timeout of ${String(this.#timeout / 1000)}s was exceeded`));
3867
+ const filePath = Path.join(targetPath, file.name.replace("package/", ""));
3868
+ const directoryPath = Path.dirname(filePath);
3869
+ if (!existsSync(directoryPath)) {
3870
+ await fs.mkdir(directoryPath, { recursive: true });
3868
3871
  }
3869
- reject(new Error(`process exited with code ${String(code)}`));
3870
- });
3871
- });
3872
+ await fs.writeFile(filePath, file.contents);
3873
+ }
3874
+ }
3872
3875
  }
3873
3876
  }
3874
3877
 
3875
3878
  class StoreService {
3876
3879
  #compilerInstanceCache = new Map();
3880
+ #fetcher;
3881
+ #lockService;
3877
3882
  #manifest;
3878
3883
  #manifestWorker;
3879
- #packageInstaller;
3884
+ #packageService;
3885
+ #npmRegistry = Environment.npmRegistry;
3880
3886
  #storePath;
3881
3887
  constructor() {
3882
3888
  this.#storePath = Environment.storePath;
3883
- this.#packageInstaller = new PackageInstaller(this.#storePath, this.#onDiagnostics);
3884
- this.#manifestWorker = new ManifestWorker(this.#storePath, this.#onDiagnostics);
3889
+ this.#fetcher = new Fetcher(this.#onDiagnostics);
3890
+ this.#lockService = new LockService(this.#onDiagnostics);
3891
+ this.#packageService = new PackageService(this.#storePath, this.#fetcher, this.#lockService);
3892
+ this.#manifestWorker = new ManifestWorker(this.#storePath, this.#npmRegistry, this.#fetcher);
3885
3893
  }
3886
3894
  async getSupportedTags() {
3887
3895
  await this.open();
@@ -3890,18 +3898,22 @@ class StoreService {
3890
3898
  }
3891
3899
  return [...Object.keys(this.#manifest.resolutions), ...this.#manifest.versions, "current"].sort();
3892
3900
  }
3893
- async install(tag, cancellationToken) {
3901
+ async install(tag) {
3894
3902
  if (tag === "current") {
3895
3903
  return;
3896
3904
  }
3897
- const version = await this.#resolveTag(tag);
3905
+ await this.open();
3906
+ if (!this.#manifest) {
3907
+ return;
3908
+ }
3909
+ const version = this.#resolveTag(tag, this.#manifest);
3898
3910
  if (!version) {
3899
- this.#onDiagnostics(Diagnostic.error(`Cannot add the 'typescript' package for the '${tag}' tag.`));
3911
+ this.#onDiagnostics(Diagnostic.error(StoreDiagnosticText.cannotAddTypeScriptPackage(tag)));
3900
3912
  return;
3901
3913
  }
3902
- return this.#packageInstaller.ensure(version, cancellationToken);
3914
+ return this.#packageService.ensure(version, this.#manifest);
3903
3915
  }
3904
- async load(tag, cancellationToken) {
3916
+ async load(tag) {
3905
3917
  let compilerInstance = this.#compilerInstanceCache.get(tag);
3906
3918
  if (compilerInstance != null) {
3907
3919
  return compilerInstance;
@@ -3911,16 +3923,20 @@ class StoreService {
3911
3923
  modulePath = Environment.typescriptPath;
3912
3924
  }
3913
3925
  else {
3914
- const version = await this.#resolveTag(tag);
3926
+ await this.open();
3927
+ if (!this.#manifest) {
3928
+ return;
3929
+ }
3930
+ const version = this.#resolveTag(tag, this.#manifest);
3915
3931
  if (!version) {
3916
- this.#onDiagnostics(Diagnostic.error(`Cannot add the 'typescript' package for the '${tag}' tag.`));
3932
+ this.#onDiagnostics(Diagnostic.error(StoreDiagnosticText.cannotAddTypeScriptPackage(tag)));
3917
3933
  return;
3918
3934
  }
3919
3935
  compilerInstance = this.#compilerInstanceCache.get(version);
3920
3936
  if (compilerInstance != null) {
3921
3937
  return compilerInstance;
3922
3938
  }
3923
- modulePath = await this.#packageInstaller.ensure(version, cancellationToken);
3939
+ modulePath = await this.#packageService.ensure(version, this.#manifest);
3924
3940
  }
3925
3941
  if (modulePath != null) {
3926
3942
  compilerInstance = await this.#loadModule(modulePath);
@@ -3939,6 +3955,7 @@ class StoreService {
3939
3955
  continue;
3940
3956
  }
3941
3957
  const toExpose = [
3958
+ "getTypeOfSymbol",
3942
3959
  "isTypeRelatedTo",
3943
3960
  "relation: { assignable: assignableRelation, identity: identityRelation, subtype: strictSubtypeRelation }",
3944
3961
  ];
@@ -3958,15 +3975,11 @@ class StoreService {
3958
3975
  }
3959
3976
  this.#manifest = await this.#manifestWorker.open();
3960
3977
  }
3961
- async #resolveTag(tag) {
3962
- await this.open();
3963
- if (!this.#manifest) {
3964
- return;
3965
- }
3966
- if (this.#manifest.versions.includes(tag)) {
3978
+ #resolveTag(tag, manifest) {
3979
+ if (manifest.versions.includes(tag)) {
3967
3980
  return tag;
3968
3981
  }
3969
- return this.#manifest.resolutions[tag];
3982
+ return manifest.resolutions[tag];
3970
3983
  }
3971
3984
  async update() {
3972
3985
  await this.#manifestWorker.open({ refresh: true });
@@ -3984,8 +3997,8 @@ class StoreService {
3984
3997
  (this.#manifest.resolutions["latest"] != null &&
3985
3998
  Version.isGreaterThan(tag, this.#manifest.resolutions["latest"])))) {
3986
3999
  this.#onDiagnostics(Diagnostic.warning([
3987
- "Failed to update metadata of the 'typescript' package from the registry.",
3988
- `The resolution of the '${tag}' tag may be outdated.`,
4000
+ StoreDiagnosticText.failedToUpdateMetadata(this.#npmRegistry),
4001
+ StoreDiagnosticText.maybeOutdatedResolution(tag),
3989
4002
  ]));
3990
4003
  }
3991
4004
  return tag in this.#manifest.resolutions || this.#manifest.versions.includes(tag);
@@ -4049,6 +4062,7 @@ class Cli {
4049
4062
  this.#outputService.writeMessage(formattedText({
4050
4063
  noColor: Environment.noColor,
4051
4064
  noInteractive: Environment.noInteractive,
4065
+ npmRegistry: Environment.npmRegistry,
4052
4066
  storePath: Environment.storePath,
4053
4067
  timeout: Environment.timeout,
4054
4068
  typescriptPath: Environment.typescriptPath,